glotfile 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/cli.js +418 -116
- package/dist/server/server.js +324 -94
- package/dist/ui/assets/{index-Dwn9g3g-.js → index-CrceYqHe.js} +19 -7
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
package/dist/server/cli.js
CHANGED
|
@@ -4369,6 +4369,142 @@ var init_glossary_suggest = __esm({
|
|
|
4369
4369
|
}
|
|
4370
4370
|
});
|
|
4371
4371
|
|
|
4372
|
+
// src/server/ai/pending-glossary-batch.ts
|
|
4373
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync6 } from "fs";
|
|
4374
|
+
import { join as join6 } from "path";
|
|
4375
|
+
function pendingGlossaryBatchPath(projectRoot) {
|
|
4376
|
+
return join6(projectRoot, ".glotfile", "glossary-suggest-batch.json");
|
|
4377
|
+
}
|
|
4378
|
+
function loadPendingGlossaryBatch(projectRoot) {
|
|
4379
|
+
const path = pendingGlossaryBatchPath(projectRoot);
|
|
4380
|
+
if (!existsSync10(path)) return void 0;
|
|
4381
|
+
try {
|
|
4382
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
4383
|
+
if (parsed?.version !== 1) return void 0;
|
|
4384
|
+
return parsed;
|
|
4385
|
+
} catch {
|
|
4386
|
+
return void 0;
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
function savePendingGlossaryBatch(projectRoot, pending) {
|
|
4390
|
+
const dir = join6(projectRoot, ".glotfile");
|
|
4391
|
+
mkdirSync6(dir, { recursive: true });
|
|
4392
|
+
const gitignore = join6(dir, ".gitignore");
|
|
4393
|
+
if (!existsSync10(gitignore)) writeFileSync5(gitignore, "*\n");
|
|
4394
|
+
writeFileSync5(pendingGlossaryBatchPath(projectRoot), JSON.stringify(pending, null, 2) + "\n");
|
|
4395
|
+
}
|
|
4396
|
+
function clearPendingGlossaryBatch(projectRoot) {
|
|
4397
|
+
rmSync6(pendingGlossaryBatchPath(projectRoot), { force: true });
|
|
4398
|
+
}
|
|
4399
|
+
var init_pending_glossary_batch = __esm({
|
|
4400
|
+
"src/server/ai/pending-glossary-batch.ts"() {
|
|
4401
|
+
"use strict";
|
|
4402
|
+
}
|
|
4403
|
+
});
|
|
4404
|
+
|
|
4405
|
+
// src/server/ai/glossary-batch-run.ts
|
|
4406
|
+
function completionRequestFor2(chunk2, knownTerms) {
|
|
4407
|
+
return {
|
|
4408
|
+
system: buildGlossarySuggestSystemPrompt(),
|
|
4409
|
+
content: [{ type: "text", text: buildGlossarySuggestBatchPrompt(chunk2, knownTerms) }],
|
|
4410
|
+
schema: GLOSSARY_SUGGEST_SCHEMA
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4413
|
+
async function submitGlossarySuggestBatch(provider, sources, knownTerms, batchSize, model, projectRoot) {
|
|
4414
|
+
if (loadPendingGlossaryBatch(projectRoot)) {
|
|
4415
|
+
throw new Error("A glossary suggestion batch is already pending. Apply or cancel it first.");
|
|
4416
|
+
}
|
|
4417
|
+
const chunks = [];
|
|
4418
|
+
const size = Math.max(1, batchSize);
|
|
4419
|
+
for (let i = 0; i < sources.length; i += size) chunks.push(sources.slice(i, i + size));
|
|
4420
|
+
const jobs = chunks.map((chunk2, i) => ({ customId: `gloss_${i}`, chunk: chunk2 }));
|
|
4421
|
+
const batchId = await provider.submitCompletionBatch(
|
|
4422
|
+
jobs.map((j) => ({ customId: j.customId, request: completionRequestFor2(j.chunk, knownTerms) }))
|
|
4423
|
+
);
|
|
4424
|
+
const pending = {
|
|
4425
|
+
version: 1,
|
|
4426
|
+
// Only Anthropic implements completion batches today.
|
|
4427
|
+
provider: "anthropic",
|
|
4428
|
+
model,
|
|
4429
|
+
batchId,
|
|
4430
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4431
|
+
total: sources.length,
|
|
4432
|
+
knownTerms,
|
|
4433
|
+
jobs: jobs.map((j) => ({
|
|
4434
|
+
customId: j.customId,
|
|
4435
|
+
requests: j.chunk
|
|
4436
|
+
}))
|
|
4437
|
+
};
|
|
4438
|
+
savePendingGlossaryBatch(projectRoot, pending);
|
|
4439
|
+
return pending;
|
|
4440
|
+
}
|
|
4441
|
+
async function applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, ai) {
|
|
4442
|
+
provider.takeUsage?.();
|
|
4443
|
+
const outcomes = await provider.completionBatchResults(pending.batchId);
|
|
4444
|
+
const batchUsage = provider.takeUsage?.();
|
|
4445
|
+
const allTerms = [];
|
|
4446
|
+
const errors = [];
|
|
4447
|
+
const jobFailures = [];
|
|
4448
|
+
const retryChunks = [];
|
|
4449
|
+
for (const job of pending.jobs) {
|
|
4450
|
+
const outcome = outcomes.get(job.customId);
|
|
4451
|
+
if (outcome?.type === "json") {
|
|
4452
|
+
const batch = outcome.value;
|
|
4453
|
+
allTerms.push(...batch.terms ?? []);
|
|
4454
|
+
continue;
|
|
4455
|
+
}
|
|
4456
|
+
if (!outcome) jobFailures.push({ customId: job.customId, locale: "", type: "missing" });
|
|
4457
|
+
else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: "", type: "malformed", raw: outcome.raw });
|
|
4458
|
+
else jobFailures.push({ customId: job.customId, locale: "", type: "failed", error: outcome.error });
|
|
4459
|
+
retryChunks.push(job.requests);
|
|
4460
|
+
}
|
|
4461
|
+
for (const chunk2 of retryChunks) {
|
|
4462
|
+
try {
|
|
4463
|
+
const raw = await provider.complete(completionRequestFor2(chunk2, pending.knownTerms));
|
|
4464
|
+
const batch = raw;
|
|
4465
|
+
allTerms.push(...batch.terms ?? []);
|
|
4466
|
+
} catch (e) {
|
|
4467
|
+
errors.push({ error: e.message });
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
const retryUsage = provider.takeUsage?.();
|
|
4471
|
+
const pricing = resolvePricing({ ...ai, model: pending.model });
|
|
4472
|
+
let estimatedCostUsd;
|
|
4473
|
+
if (pricing && (batchUsage || retryUsage)) {
|
|
4474
|
+
estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
|
|
4475
|
+
}
|
|
4476
|
+
let usage;
|
|
4477
|
+
if (batchUsage || retryUsage) {
|
|
4478
|
+
usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
4479
|
+
if (retryUsage) addUsage(usage, retryUsage);
|
|
4480
|
+
}
|
|
4481
|
+
const fresh = load();
|
|
4482
|
+
const added = mergeGlossarySuggestions(fresh, dedupeTerms(allTerms));
|
|
4483
|
+
persist(fresh);
|
|
4484
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
4485
|
+
const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
|
|
4486
|
+
appendLog(projectRoot, {
|
|
4487
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4488
|
+
kind: "glossary",
|
|
4489
|
+
summary: `Applied glossary suggestion batch ${pending.batchId}: ${added.length} new term(s), ${errors.length} error(s), ${retryChunks.length} job(s) retried${costSuffix}`,
|
|
4490
|
+
model: pending.model,
|
|
4491
|
+
jobFailures: jobFailures.length ? jobFailures : void 0,
|
|
4492
|
+
usage,
|
|
4493
|
+
estimatedCostUsd
|
|
4494
|
+
});
|
|
4495
|
+
return { added: added.length, errors, retried: retryChunks.length };
|
|
4496
|
+
}
|
|
4497
|
+
var init_glossary_batch_run = __esm({
|
|
4498
|
+
"src/server/ai/glossary-batch-run.ts"() {
|
|
4499
|
+
"use strict";
|
|
4500
|
+
init_glossary_suggest();
|
|
4501
|
+
init_pending_glossary_batch();
|
|
4502
|
+
init_state();
|
|
4503
|
+
init_log();
|
|
4504
|
+
init_pricing();
|
|
4505
|
+
}
|
|
4506
|
+
});
|
|
4507
|
+
|
|
4372
4508
|
// src/server/ai/estimate.ts
|
|
4373
4509
|
function estimateTokens(text) {
|
|
4374
4510
|
const cjk = text.match(CJK_RE)?.length ?? 0;
|
|
@@ -4535,13 +4671,13 @@ var init_price_fetch = __esm({
|
|
|
4535
4671
|
});
|
|
4536
4672
|
|
|
4537
4673
|
// src/server/scan.ts
|
|
4538
|
-
import { existsSync as
|
|
4674
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
4539
4675
|
import { resolve as resolve7 } from "path";
|
|
4540
4676
|
function loadUsageCache(projectRoot) {
|
|
4541
4677
|
const path = resolve7(projectRoot, ".glotfile", "usage.json");
|
|
4542
|
-
if (!
|
|
4678
|
+
if (!existsSync11(path)) return null;
|
|
4543
4679
|
try {
|
|
4544
|
-
return JSON.parse(
|
|
4680
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
4545
4681
|
} catch {
|
|
4546
4682
|
return null;
|
|
4547
4683
|
}
|
|
@@ -4606,8 +4742,8 @@ var init_scan = __esm({
|
|
|
4606
4742
|
});
|
|
4607
4743
|
|
|
4608
4744
|
// src/server/scanner.ts
|
|
4609
|
-
import { readdirSync as readdirSync3, statSync as statSync3, readFileSync as
|
|
4610
|
-
import { join as
|
|
4745
|
+
import { readdirSync as readdirSync3, statSync as statSync3, readFileSync as readFileSync13 } from "fs";
|
|
4746
|
+
import { join as join7, extname as extname2, relative } from "path";
|
|
4611
4747
|
function scannerForExt(ext) {
|
|
4612
4748
|
return EXT_SCANNER[ext] ?? null;
|
|
4613
4749
|
}
|
|
@@ -4829,7 +4965,7 @@ function* walkFiles(dir, root, exclude) {
|
|
|
4829
4965
|
}
|
|
4830
4966
|
for (const name of entries) {
|
|
4831
4967
|
if (ALWAYS_EXCLUDE.has(name)) continue;
|
|
4832
|
-
const abs =
|
|
4968
|
+
const abs = join7(dir, name);
|
|
4833
4969
|
const rel = relative(root, abs);
|
|
4834
4970
|
let st;
|
|
4835
4971
|
try {
|
|
@@ -4859,7 +4995,7 @@ function runScan(projectRoot, opts, existing) {
|
|
|
4859
4995
|
const ext = extname2(relPath);
|
|
4860
4996
|
const scanner = scannerForExt(ext);
|
|
4861
4997
|
if (!scanner) continue;
|
|
4862
|
-
const abs =
|
|
4998
|
+
const abs = join7(projectRoot, relPath);
|
|
4863
4999
|
let st;
|
|
4864
5000
|
try {
|
|
4865
5001
|
st = statSync3(abs);
|
|
@@ -4875,7 +5011,7 @@ function runScan(projectRoot, opts, existing) {
|
|
|
4875
5011
|
}
|
|
4876
5012
|
let content;
|
|
4877
5013
|
try {
|
|
4878
|
-
content =
|
|
5014
|
+
content = readFileSync13(abs, "utf8");
|
|
4879
5015
|
} catch {
|
|
4880
5016
|
continue;
|
|
4881
5017
|
}
|
|
@@ -5009,8 +5145,8 @@ var init_scanner = __esm({
|
|
|
5009
5145
|
});
|
|
5010
5146
|
|
|
5011
5147
|
// src/server/import/detect.ts
|
|
5012
|
-
import { existsSync as
|
|
5013
|
-
import { join as
|
|
5148
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync14, statSync as statSync4 } from "fs";
|
|
5149
|
+
import { join as join8 } from "path";
|
|
5014
5150
|
function safeIsDir(p) {
|
|
5015
5151
|
try {
|
|
5016
5152
|
return statSync4(p).isDirectory();
|
|
@@ -5019,7 +5155,7 @@ function safeIsDir(p) {
|
|
|
5019
5155
|
}
|
|
5020
5156
|
}
|
|
5021
5157
|
function listDirs(dir) {
|
|
5022
|
-
return readdirSync4(dir).filter((e) => safeIsDir(
|
|
5158
|
+
return readdirSync4(dir).filter((e) => safeIsDir(join8(dir, e)));
|
|
5023
5159
|
}
|
|
5024
5160
|
function fileCount(dir) {
|
|
5025
5161
|
try {
|
|
@@ -5033,23 +5169,23 @@ function pickSource(locales, sizeOf) {
|
|
|
5033
5169
|
return [...locales].sort((a, b) => sizeOf(b) - sizeOf(a) || a.localeCompare(b))[0] ?? "en";
|
|
5034
5170
|
}
|
|
5035
5171
|
function detectLaravel(root) {
|
|
5036
|
-
const localeRoot = [
|
|
5172
|
+
const localeRoot = [join8(root, "resources", "lang"), join8(root, "lang")].find(safeIsDir);
|
|
5037
5173
|
if (!localeRoot) return null;
|
|
5038
5174
|
const locales = listDirs(localeRoot).filter((d) => LOCALE_RE.test(d));
|
|
5039
5175
|
if (locales.length === 0) return null;
|
|
5040
|
-
const sourceLocale = pickSource(locales, (loc) => fileCount(
|
|
5176
|
+
const sourceLocale = pickSource(locales, (loc) => fileCount(join8(localeRoot, loc)));
|
|
5041
5177
|
return { format: "laravel-php", localeRoot, locales, sourceLocale };
|
|
5042
5178
|
}
|
|
5043
5179
|
function detectVue(root, forced = false) {
|
|
5044
5180
|
for (const rel of VUE_DIR_CANDIDATES) {
|
|
5045
|
-
const localeRoot =
|
|
5181
|
+
const localeRoot = join8(root, rel);
|
|
5046
5182
|
if (!safeIsDir(localeRoot)) continue;
|
|
5047
5183
|
const locales = readdirSync4(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
5048
5184
|
const enough = locales.length >= 2 || locales.length === 1 && (forced || locales[0] === "en" || locales[0].startsWith("en-") || locales[0].startsWith("en_"));
|
|
5049
5185
|
if (enough) {
|
|
5050
5186
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
5051
5187
|
try {
|
|
5052
|
-
return statSync4(
|
|
5188
|
+
return statSync4(join8(localeRoot, `${loc}.json`)).size;
|
|
5053
5189
|
} catch {
|
|
5054
5190
|
return 0;
|
|
5055
5191
|
}
|
|
@@ -5060,9 +5196,9 @@ function detectVue(root, forced = false) {
|
|
|
5060
5196
|
return null;
|
|
5061
5197
|
}
|
|
5062
5198
|
function hasNextIntlSignal(root) {
|
|
5063
|
-
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) =>
|
|
5199
|
+
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) => existsSync12(join8(root, rel)))) return true;
|
|
5064
5200
|
try {
|
|
5065
|
-
const pkg = JSON.parse(
|
|
5201
|
+
const pkg = JSON.parse(readFileSync14(join8(root, "package.json"), "utf8"));
|
|
5066
5202
|
if (pkg.dependencies?.["next-intl"] || pkg.devDependencies?.["next-intl"]) return true;
|
|
5067
5203
|
} catch {
|
|
5068
5204
|
}
|
|
@@ -5071,7 +5207,7 @@ function hasNextIntlSignal(root) {
|
|
|
5071
5207
|
function nextIntlDefaultLocale(root) {
|
|
5072
5208
|
for (const rel of NEXT_INTL_ROUTING_CANDIDATES) {
|
|
5073
5209
|
try {
|
|
5074
|
-
const m =
|
|
5210
|
+
const m = readFileSync14(join8(root, rel), "utf8").match(/defaultLocale\s*:\s*['"]([^'"]+)['"]/);
|
|
5075
5211
|
if (m) return m[1];
|
|
5076
5212
|
} catch {
|
|
5077
5213
|
}
|
|
@@ -5081,14 +5217,14 @@ function nextIntlDefaultLocale(root) {
|
|
|
5081
5217
|
function detectNextIntl(root, forced = false) {
|
|
5082
5218
|
if (!forced && !hasNextIntlSignal(root)) return null;
|
|
5083
5219
|
for (const rel of NEXT_INTL_DIR_CANDIDATES) {
|
|
5084
|
-
const localeRoot =
|
|
5220
|
+
const localeRoot = join8(root, rel);
|
|
5085
5221
|
if (!safeIsDir(localeRoot)) continue;
|
|
5086
5222
|
const locales = readdirSync4(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
5087
5223
|
if (locales.length === 0) continue;
|
|
5088
5224
|
const def = nextIntlDefaultLocale(root);
|
|
5089
5225
|
const sourceLocale = def && locales.includes(def) ? def : pickSource(locales, (loc) => {
|
|
5090
5226
|
try {
|
|
5091
|
-
return statSync4(
|
|
5227
|
+
return statSync4(join8(localeRoot, `${loc}.json`)).size;
|
|
5092
5228
|
} catch {
|
|
5093
5229
|
return 0;
|
|
5094
5230
|
}
|
|
@@ -5099,7 +5235,7 @@ function detectNextIntl(root, forced = false) {
|
|
|
5099
5235
|
}
|
|
5100
5236
|
function detectArb(root) {
|
|
5101
5237
|
for (const rel of ["lib/l10n", "l10n", "lib/src/l10n"]) {
|
|
5102
|
-
const localeRoot =
|
|
5238
|
+
const localeRoot = join8(root, rel);
|
|
5103
5239
|
if (!safeIsDir(localeRoot)) continue;
|
|
5104
5240
|
const locales = readdirSync4(localeRoot).map((f) => f.match(/^(?:app_)?(.+)\.arb$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l));
|
|
5105
5241
|
if (locales.length >= 1) {
|
|
@@ -5109,10 +5245,10 @@ function detectArb(root) {
|
|
|
5109
5245
|
return null;
|
|
5110
5246
|
}
|
|
5111
5247
|
function lprojLocales(dir) {
|
|
5112
|
-
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5248
|
+
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync12(join8(dir, `${l}.lproj`, "Localizable.strings")));
|
|
5113
5249
|
}
|
|
5114
5250
|
function detectApple(root) {
|
|
5115
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5251
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5116
5252
|
let best = null;
|
|
5117
5253
|
for (const dir of candidates) {
|
|
5118
5254
|
const locales = lprojLocales(dir);
|
|
@@ -5124,7 +5260,7 @@ function detectApple(root) {
|
|
|
5124
5260
|
locales,
|
|
5125
5261
|
sourceLocale: pickSource(locales, (loc) => {
|
|
5126
5262
|
try {
|
|
5127
|
-
return statSync4(
|
|
5263
|
+
return statSync4(join8(dir, `${loc}.lproj`, "Localizable.strings")).size;
|
|
5128
5264
|
} catch {
|
|
5129
5265
|
return 0;
|
|
5130
5266
|
}
|
|
@@ -5136,7 +5272,7 @@ function detectApple(root) {
|
|
|
5136
5272
|
}
|
|
5137
5273
|
function detectAngularXliff(root) {
|
|
5138
5274
|
for (const rel of ANGULAR_DIR_CANDIDATES) {
|
|
5139
|
-
const localeRoot = rel === "." ? root :
|
|
5275
|
+
const localeRoot = rel === "." ? root : join8(root, rel);
|
|
5140
5276
|
if (!safeIsDir(localeRoot)) continue;
|
|
5141
5277
|
const files = readdirSync4(localeRoot).filter((f) => /^messages(\..+)?\.xlf$/.test(f)).sort();
|
|
5142
5278
|
if (files.length === 0) continue;
|
|
@@ -5144,7 +5280,7 @@ function detectAngularXliff(root) {
|
|
|
5144
5280
|
const attrFile = files.includes("messages.xlf") ? "messages.xlf" : files[0];
|
|
5145
5281
|
let sourceLocale;
|
|
5146
5282
|
try {
|
|
5147
|
-
sourceLocale =
|
|
5283
|
+
sourceLocale = readFileSync14(join8(localeRoot, attrFile), "utf8").match(/source-language="([^"]+)"/)?.[1];
|
|
5148
5284
|
} catch {
|
|
5149
5285
|
}
|
|
5150
5286
|
if (!sourceLocale && locales.length === 0) continue;
|
|
@@ -5155,14 +5291,14 @@ function detectAngularXliff(root) {
|
|
|
5155
5291
|
return null;
|
|
5156
5292
|
}
|
|
5157
5293
|
function detectRails(root) {
|
|
5158
|
-
const localeRoot =
|
|
5294
|
+
const localeRoot = join8(root, "config", "locales");
|
|
5159
5295
|
if (!safeIsDir(localeRoot)) return null;
|
|
5160
5296
|
const locales = [];
|
|
5161
5297
|
for (const file of readdirSync4(localeRoot).sort()) {
|
|
5162
5298
|
if (!/\.ya?ml$/.test(file)) continue;
|
|
5163
5299
|
let text;
|
|
5164
5300
|
try {
|
|
5165
|
-
text =
|
|
5301
|
+
text = readFileSync14(join8(localeRoot, file), "utf8");
|
|
5166
5302
|
} catch {
|
|
5167
5303
|
continue;
|
|
5168
5304
|
}
|
|
@@ -5176,15 +5312,15 @@ function detectRails(root) {
|
|
|
5176
5312
|
}
|
|
5177
5313
|
function detectI18next(root) {
|
|
5178
5314
|
for (const rel of I18NEXT_DIR_CANDIDATES) {
|
|
5179
|
-
const localeRoot =
|
|
5315
|
+
const localeRoot = join8(root, rel);
|
|
5180
5316
|
if (!safeIsDir(localeRoot)) continue;
|
|
5181
5317
|
const locales = listDirs(localeRoot).filter(
|
|
5182
|
-
(d) => LOCALE_RE.test(d) && readdirSync4(
|
|
5318
|
+
(d) => LOCALE_RE.test(d) && readdirSync4(join8(localeRoot, d)).some((f) => f.endsWith(".json"))
|
|
5183
5319
|
);
|
|
5184
5320
|
if (locales.length === 0) continue;
|
|
5185
5321
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
5186
5322
|
try {
|
|
5187
|
-
return readdirSync4(
|
|
5323
|
+
return readdirSync4(join8(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync4(join8(localeRoot, loc, f)).size, 0);
|
|
5188
5324
|
} catch {
|
|
5189
5325
|
return 0;
|
|
5190
5326
|
}
|
|
@@ -5201,8 +5337,8 @@ function gettextLocales(dir) {
|
|
|
5201
5337
|
if (!locales.includes(flat)) locales.push(flat);
|
|
5202
5338
|
continue;
|
|
5203
5339
|
}
|
|
5204
|
-
if (!LOCALE_RE.test(entry) || !safeIsDir(
|
|
5205
|
-
const sub =
|
|
5340
|
+
if (!LOCALE_RE.test(entry) || !safeIsDir(join8(dir, entry))) continue;
|
|
5341
|
+
const sub = join8(dir, entry);
|
|
5206
5342
|
const hasPo = (d) => {
|
|
5207
5343
|
try {
|
|
5208
5344
|
return readdirSync4(d).some((f) => f.endsWith(".po"));
|
|
@@ -5210,7 +5346,7 @@ function gettextLocales(dir) {
|
|
|
5210
5346
|
return false;
|
|
5211
5347
|
}
|
|
5212
5348
|
};
|
|
5213
|
-
if (hasPo(
|
|
5349
|
+
if (hasPo(join8(sub, "LC_MESSAGES")) || hasPo(sub)) {
|
|
5214
5350
|
if (!locales.includes(entry)) locales.push(entry);
|
|
5215
5351
|
}
|
|
5216
5352
|
}
|
|
@@ -5218,7 +5354,7 @@ function gettextLocales(dir) {
|
|
|
5218
5354
|
}
|
|
5219
5355
|
function detectGettext(root) {
|
|
5220
5356
|
for (const rel of GETTEXT_DIR_CANDIDATES) {
|
|
5221
|
-
const localeRoot =
|
|
5357
|
+
const localeRoot = join8(root, rel);
|
|
5222
5358
|
if (!safeIsDir(localeRoot)) continue;
|
|
5223
5359
|
const locales = gettextLocales(localeRoot);
|
|
5224
5360
|
if (locales.length === 0) continue;
|
|
@@ -5227,10 +5363,10 @@ function detectGettext(root) {
|
|
|
5227
5363
|
return null;
|
|
5228
5364
|
}
|
|
5229
5365
|
function detectAppleStringsdict(root) {
|
|
5230
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5366
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5231
5367
|
let best = null;
|
|
5232
5368
|
for (const dir of candidates) {
|
|
5233
|
-
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5369
|
+
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync12(join8(dir, `${l}.lproj`, "Localizable.stringsdict")));
|
|
5234
5370
|
if (locales.length === 0) continue;
|
|
5235
5371
|
if (!best || locales.length > best.locales.length) {
|
|
5236
5372
|
best = { format: "apple-stringsdict", localeRoot: dir, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
@@ -5239,7 +5375,7 @@ function detectAppleStringsdict(root) {
|
|
|
5239
5375
|
return best;
|
|
5240
5376
|
}
|
|
5241
5377
|
function detect(root, formatOverride) {
|
|
5242
|
-
if (!
|
|
5378
|
+
if (!existsSync12(root)) return null;
|
|
5243
5379
|
if (formatOverride) {
|
|
5244
5380
|
const fn = BY_FORMAT[formatOverride];
|
|
5245
5381
|
if (!fn) throw new Error(`Unknown format: ${formatOverride}`);
|
|
@@ -5317,8 +5453,8 @@ var init_flatten = __esm({
|
|
|
5317
5453
|
});
|
|
5318
5454
|
|
|
5319
5455
|
// src/server/import/parsers/vue-i18n-json.ts
|
|
5320
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
5321
|
-
import { join as
|
|
5456
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
|
|
5457
|
+
import { join as join9 } from "path";
|
|
5322
5458
|
function fromVueI18n(value) {
|
|
5323
5459
|
return value.replace(/\{'([^']*)'\}/g, "'$1'");
|
|
5324
5460
|
}
|
|
@@ -5341,7 +5477,7 @@ var init_vue_i18n_json2 = __esm({
|
|
|
5341
5477
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5342
5478
|
let data;
|
|
5343
5479
|
try {
|
|
5344
|
-
data = JSON.parse(
|
|
5480
|
+
data = JSON.parse(readFileSync15(join9(localeRoot, file), "utf8"));
|
|
5345
5481
|
} catch (e) {
|
|
5346
5482
|
warnings.push(`vue-i18n-json: failed to parse ${file}: ${e.message}`);
|
|
5347
5483
|
continue;
|
|
@@ -5358,8 +5494,8 @@ var init_vue_i18n_json2 = __esm({
|
|
|
5358
5494
|
});
|
|
5359
5495
|
|
|
5360
5496
|
// src/server/import/parsers/next-intl-json.ts
|
|
5361
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
5362
|
-
import { join as
|
|
5497
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
|
|
5498
|
+
import { join as join10 } from "path";
|
|
5363
5499
|
var LOCALE_RE3, nextIntlJson2;
|
|
5364
5500
|
var init_next_intl_json2 = __esm({
|
|
5365
5501
|
"src/server/import/parsers/next-intl-json.ts"() {
|
|
@@ -5379,7 +5515,7 @@ var init_next_intl_json2 = __esm({
|
|
|
5379
5515
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5380
5516
|
let data;
|
|
5381
5517
|
try {
|
|
5382
|
-
data = JSON.parse(
|
|
5518
|
+
data = JSON.parse(readFileSync16(join10(localeRoot, file), "utf8"));
|
|
5383
5519
|
} catch (e) {
|
|
5384
5520
|
warnings.push(`next-intl-json: failed to parse ${file}: ${e.message}`);
|
|
5385
5521
|
continue;
|
|
@@ -5413,16 +5549,16 @@ var init_placeholders2 = __esm({
|
|
|
5413
5549
|
|
|
5414
5550
|
// src/server/import/parsers/laravel-php.ts
|
|
5415
5551
|
import { readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
5416
|
-
import { join as
|
|
5552
|
+
import { join as join11, relative as relative2 } from "path";
|
|
5417
5553
|
import { execFileSync } from "child_process";
|
|
5418
5554
|
function listDirs2(dir) {
|
|
5419
|
-
return readdirSync7(dir).filter((e) => statSync5(
|
|
5555
|
+
return readdirSync7(dir).filter((e) => statSync5(join11(dir, e)).isDirectory());
|
|
5420
5556
|
}
|
|
5421
5557
|
function listPhpFiles(dir) {
|
|
5422
5558
|
const out = [];
|
|
5423
5559
|
const walk = (d) => {
|
|
5424
5560
|
for (const e of readdirSync7(d)) {
|
|
5425
|
-
const full =
|
|
5561
|
+
const full = join11(d, e);
|
|
5426
5562
|
if (statSync5(full).isDirectory()) walk(full);
|
|
5427
5563
|
else if (e.endsWith(".php")) out.push(full);
|
|
5428
5564
|
}
|
|
@@ -5465,7 +5601,7 @@ var init_laravel_php2 = __esm({
|
|
|
5465
5601
|
for (const locale of listDirs2(localeRoot).sort()) {
|
|
5466
5602
|
if (locale === "vendor") continue;
|
|
5467
5603
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5468
|
-
const localeDir =
|
|
5604
|
+
const localeDir = join11(localeRoot, locale);
|
|
5469
5605
|
locales.push(locale);
|
|
5470
5606
|
for (const file of listPhpFiles(localeDir)) {
|
|
5471
5607
|
const group = relative2(localeDir, file).replace(/\\/g, "/").replace(/\.php$/, "");
|
|
@@ -5490,8 +5626,8 @@ var init_laravel_php2 = __esm({
|
|
|
5490
5626
|
});
|
|
5491
5627
|
|
|
5492
5628
|
// src/server/import/parsers/flutter-arb.ts
|
|
5493
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
5494
|
-
import { join as
|
|
5629
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
|
|
5630
|
+
import { join as join12 } from "path";
|
|
5495
5631
|
function localeFromArbName(file) {
|
|
5496
5632
|
const m = file.match(/^(.+)\.arb$/);
|
|
5497
5633
|
if (!m) return null;
|
|
@@ -5531,7 +5667,7 @@ var init_flutter_arb2 = __esm({
|
|
|
5531
5667
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5532
5668
|
let data;
|
|
5533
5669
|
try {
|
|
5534
|
-
data = JSON.parse(
|
|
5670
|
+
data = JSON.parse(readFileSync17(join12(localeRoot, file), "utf8"));
|
|
5535
5671
|
} catch (e) {
|
|
5536
5672
|
warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
|
|
5537
5673
|
continue;
|
|
@@ -5558,8 +5694,8 @@ var init_flutter_arb2 = __esm({
|
|
|
5558
5694
|
});
|
|
5559
5695
|
|
|
5560
5696
|
// src/server/import/parsers/apple-strings.ts
|
|
5561
|
-
import { readdirSync as readdirSync9, readFileSync as
|
|
5562
|
-
import { join as
|
|
5697
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync18, statSync as statSync6 } from "fs";
|
|
5698
|
+
import { join as join13 } from "path";
|
|
5563
5699
|
function localeFromLproj(dir) {
|
|
5564
5700
|
const m = dir.match(/^(.+)\.lproj$/);
|
|
5565
5701
|
if (!m) return null;
|
|
@@ -5679,16 +5815,16 @@ var init_apple_strings2 = __esm({
|
|
|
5679
5815
|
const locale = localeFromLproj(dir);
|
|
5680
5816
|
if (!locale) continue;
|
|
5681
5817
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5682
|
-
const file =
|
|
5818
|
+
const file = join13(localeRoot, dir, TABLE);
|
|
5683
5819
|
let text;
|
|
5684
5820
|
try {
|
|
5685
5821
|
if (!statSync6(file).isFile()) continue;
|
|
5686
|
-
text =
|
|
5822
|
+
text = readFileSync18(file, "utf8");
|
|
5687
5823
|
} catch {
|
|
5688
5824
|
continue;
|
|
5689
5825
|
}
|
|
5690
5826
|
locales.push(locale);
|
|
5691
|
-
const others = readdirSync9(
|
|
5827
|
+
const others = readdirSync9(join13(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
|
|
5692
5828
|
if (others.length) {
|
|
5693
5829
|
warnings.push(`apple-strings: ${dir} has other .strings tables (${others.join(", ")}); only ${TABLE} is imported`);
|
|
5694
5830
|
}
|
|
@@ -5703,8 +5839,8 @@ var init_apple_strings2 = __esm({
|
|
|
5703
5839
|
});
|
|
5704
5840
|
|
|
5705
5841
|
// src/server/import/parsers/angular-xliff.ts
|
|
5706
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
5707
|
-
import { join as
|
|
5842
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync19 } from "fs";
|
|
5843
|
+
import { join as join14 } from "path";
|
|
5708
5844
|
function decodeEntities(s) {
|
|
5709
5845
|
return s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d))).replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
5710
5846
|
}
|
|
@@ -5775,7 +5911,7 @@ var init_angular_xliff2 = __esm({
|
|
|
5775
5911
|
if (fnameLocale !== void 0 && !LOCALE_RE6.test(fnameLocale)) continue;
|
|
5776
5912
|
let xml;
|
|
5777
5913
|
try {
|
|
5778
|
-
xml =
|
|
5914
|
+
xml = readFileSync19(join14(localeRoot, file), "utf8");
|
|
5779
5915
|
} catch (e) {
|
|
5780
5916
|
warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
|
|
5781
5917
|
continue;
|
|
@@ -5822,8 +5958,8 @@ var init_angular_xliff2 = __esm({
|
|
|
5822
5958
|
});
|
|
5823
5959
|
|
|
5824
5960
|
// src/server/import/parsers/gettext-po.ts
|
|
5825
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
5826
|
-
import { join as
|
|
5961
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync20 } from "fs";
|
|
5962
|
+
import { join as join15 } from "path";
|
|
5827
5963
|
function unescapePo(s) {
|
|
5828
5964
|
return s.replace(
|
|
5829
5965
|
/\\([\\"ntr])/g,
|
|
@@ -5912,17 +6048,17 @@ function discoverPoFiles(root) {
|
|
|
5912
6048
|
for (const e of entries) {
|
|
5913
6049
|
if (e.isFile() && e.name.endsWith(".po")) {
|
|
5914
6050
|
const base = e.name.slice(0, -3);
|
|
5915
|
-
found.push({ path:
|
|
6051
|
+
found.push({ path: join15(root, e.name), rel: e.name, locale: LOCALE_RE7.test(base) ? base : null });
|
|
5916
6052
|
} else if (e.isDirectory() && LOCALE_RE7.test(e.name)) {
|
|
5917
|
-
for (const sub of [
|
|
6053
|
+
for (const sub of [join15(e.name, "LC_MESSAGES"), e.name]) {
|
|
5918
6054
|
let names;
|
|
5919
6055
|
try {
|
|
5920
|
-
names = readdirSync11(
|
|
6056
|
+
names = readdirSync11(join15(root, sub)).sort();
|
|
5921
6057
|
} catch {
|
|
5922
6058
|
continue;
|
|
5923
6059
|
}
|
|
5924
6060
|
for (const f of names) {
|
|
5925
|
-
if (f.endsWith(".po")) found.push({ path:
|
|
6061
|
+
if (f.endsWith(".po")) found.push({ path: join15(root, sub, f), rel: join15(sub, f), locale: e.name });
|
|
5926
6062
|
}
|
|
5927
6063
|
}
|
|
5928
6064
|
}
|
|
@@ -5946,7 +6082,7 @@ var init_gettext_po2 = __esm({
|
|
|
5946
6082
|
for (const file of discoverPoFiles(localeRoot)) {
|
|
5947
6083
|
let entries;
|
|
5948
6084
|
try {
|
|
5949
|
-
entries = parseEntries(
|
|
6085
|
+
entries = parseEntries(readFileSync20(file.path, "utf8"));
|
|
5950
6086
|
} catch (e) {
|
|
5951
6087
|
warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
|
|
5952
6088
|
continue;
|
|
@@ -5993,8 +6129,8 @@ var init_gettext_po2 = __esm({
|
|
|
5993
6129
|
});
|
|
5994
6130
|
|
|
5995
6131
|
// src/server/import/parsers/i18next-json.ts
|
|
5996
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
5997
|
-
import { join as
|
|
6132
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
|
|
6133
|
+
import { join as join16 } from "path";
|
|
5998
6134
|
function safeIsDir2(p) {
|
|
5999
6135
|
try {
|
|
6000
6136
|
return statSync7(p).isDirectory();
|
|
@@ -6009,7 +6145,7 @@ function fromI18next(value) {
|
|
|
6009
6145
|
function ingestFile(path, label, prefix, locale, keys, warnings) {
|
|
6010
6146
|
let data;
|
|
6011
6147
|
try {
|
|
6012
|
-
data = JSON.parse(
|
|
6148
|
+
data = JSON.parse(readFileSync21(path, "utf8"));
|
|
6013
6149
|
} catch (e) {
|
|
6014
6150
|
warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
|
|
6015
6151
|
return false;
|
|
@@ -6062,7 +6198,7 @@ var init_i18next_json2 = __esm({
|
|
|
6062
6198
|
const keys = {};
|
|
6063
6199
|
const locales = [];
|
|
6064
6200
|
for (const entry of readdirSync12(localeRoot).sort()) {
|
|
6065
|
-
const full =
|
|
6201
|
+
const full = join16(localeRoot, entry);
|
|
6066
6202
|
if (safeIsDir2(full)) {
|
|
6067
6203
|
if (!LOCALE_RE8.test(entry)) continue;
|
|
6068
6204
|
if (opts?.locales && !opts.locales.includes(entry)) continue;
|
|
@@ -6071,7 +6207,7 @@ var init_i18next_json2 = __esm({
|
|
|
6071
6207
|
if (!file.endsWith(".json")) continue;
|
|
6072
6208
|
const ns = file.slice(0, -".json".length);
|
|
6073
6209
|
const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
|
|
6074
|
-
if (ingestFile(
|
|
6210
|
+
if (ingestFile(join16(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
|
|
6075
6211
|
}
|
|
6076
6212
|
if (any && !locales.includes(entry)) locales.push(entry);
|
|
6077
6213
|
} else if (entry.endsWith(".json")) {
|
|
@@ -6090,8 +6226,8 @@ var init_i18next_json2 = __esm({
|
|
|
6090
6226
|
});
|
|
6091
6227
|
|
|
6092
6228
|
// src/server/import/parsers/rails-yaml.ts
|
|
6093
|
-
import { readdirSync as readdirSync13, readFileSync as
|
|
6094
|
-
import { join as
|
|
6229
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync22 } from "fs";
|
|
6230
|
+
import { join as join17 } from "path";
|
|
6095
6231
|
function makeNode() {
|
|
6096
6232
|
return /* @__PURE__ */ Object.create(null);
|
|
6097
6233
|
}
|
|
@@ -6315,7 +6451,7 @@ var init_rails_yaml2 = __esm({
|
|
|
6315
6451
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
6316
6452
|
let text;
|
|
6317
6453
|
try {
|
|
6318
|
-
text =
|
|
6454
|
+
text = readFileSync22(join17(localeRoot, file), "utf8");
|
|
6319
6455
|
} catch (e) {
|
|
6320
6456
|
warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
|
|
6321
6457
|
continue;
|
|
@@ -6338,8 +6474,8 @@ var init_rails_yaml2 = __esm({
|
|
|
6338
6474
|
});
|
|
6339
6475
|
|
|
6340
6476
|
// src/server/import/parsers/apple-stringsdict.ts
|
|
6341
|
-
import { readdirSync as readdirSync14, readFileSync as
|
|
6342
|
-
import { join as
|
|
6477
|
+
import { readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
|
|
6478
|
+
import { join as join18 } from "path";
|
|
6343
6479
|
function localeFromLproj2(dir) {
|
|
6344
6480
|
const m = dir.match(/^(.+)\.lproj$/);
|
|
6345
6481
|
if (!m) return null;
|
|
@@ -6499,16 +6635,16 @@ var init_apple_stringsdict2 = __esm({
|
|
|
6499
6635
|
const locale = localeFromLproj2(dir);
|
|
6500
6636
|
if (!locale) continue;
|
|
6501
6637
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
6502
|
-
const file =
|
|
6638
|
+
const file = join18(localeRoot, dir, TABLE2);
|
|
6503
6639
|
let text;
|
|
6504
6640
|
try {
|
|
6505
6641
|
if (!statSync8(file).isFile()) continue;
|
|
6506
|
-
text =
|
|
6642
|
+
text = readFileSync23(file, "utf8");
|
|
6507
6643
|
} catch {
|
|
6508
6644
|
continue;
|
|
6509
6645
|
}
|
|
6510
6646
|
locales.push(locale);
|
|
6511
|
-
const others = readdirSync14(
|
|
6647
|
+
const others = readdirSync14(join18(localeRoot, dir)).filter(
|
|
6512
6648
|
(f) => f.endsWith(".stringsdict") && f !== TABLE2
|
|
6513
6649
|
);
|
|
6514
6650
|
if (others.length) {
|
|
@@ -6982,7 +7118,7 @@ var init_run2 = __esm({
|
|
|
6982
7118
|
});
|
|
6983
7119
|
|
|
6984
7120
|
// src/server/lint/outputs.ts
|
|
6985
|
-
import { readFileSync as
|
|
7121
|
+
import { readFileSync as readFileSync24, existsSync as existsSync13 } from "fs";
|
|
6986
7122
|
import { resolve as resolve8 } from "path";
|
|
6987
7123
|
function checkOutputs(state, root) {
|
|
6988
7124
|
const out = [];
|
|
@@ -6990,7 +7126,7 @@ function checkOutputs(state, root) {
|
|
|
6990
7126
|
const result = getAdapter(output.adapter).export(state, output);
|
|
6991
7127
|
for (const file of result.files) {
|
|
6992
7128
|
const abs = resolve8(root, file.path);
|
|
6993
|
-
const current =
|
|
7129
|
+
const current = existsSync13(abs) ? readFileSync24(abs, "utf8") : null;
|
|
6994
7130
|
if (current === null) {
|
|
6995
7131
|
out.push({ ruleId: "output-stale", key: file.path, locale: "", severity: "error", message: "output file is missing; run `glotfile export`" });
|
|
6996
7132
|
} else if (current !== file.contents) {
|
|
@@ -7493,12 +7629,12 @@ var init_explain_error = __esm({
|
|
|
7493
7629
|
});
|
|
7494
7630
|
|
|
7495
7631
|
// src/server/ui-prefs.ts
|
|
7496
|
-
import { readFileSync as
|
|
7632
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
7497
7633
|
import { homedir as homedir2 } from "os";
|
|
7498
|
-
import { join as
|
|
7634
|
+
import { join as join19 } from "path";
|
|
7499
7635
|
function readJson2(path) {
|
|
7500
7636
|
try {
|
|
7501
|
-
const parsed = JSON.parse(
|
|
7637
|
+
const parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
7502
7638
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
7503
7639
|
} catch {
|
|
7504
7640
|
return {};
|
|
@@ -7523,7 +7659,7 @@ var init_ui_prefs = __esm({
|
|
|
7523
7659
|
THEMES = ["system", "light", "dark"];
|
|
7524
7660
|
isThemeMode = (v) => THEMES.includes(v);
|
|
7525
7661
|
isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
7526
|
-
defaultUiPrefsPath = () =>
|
|
7662
|
+
defaultUiPrefsPath = () => join19(homedir2(), ".glotfile", "ui.json");
|
|
7527
7663
|
DEFAULTS = { theme: "system" };
|
|
7528
7664
|
}
|
|
7529
7665
|
});
|
|
@@ -7557,7 +7693,7 @@ var init_events = __esm({
|
|
|
7557
7693
|
|
|
7558
7694
|
// src/server/watch.ts
|
|
7559
7695
|
import { statSync as statSync9, readdirSync as readdirSync15 } from "fs";
|
|
7560
|
-
import { join as
|
|
7696
|
+
import { join as join20 } from "path";
|
|
7561
7697
|
import { createHash as createHash2 } from "crypto";
|
|
7562
7698
|
function hashState(state) {
|
|
7563
7699
|
return createHash2("sha1").update(serializeJson(state, state.config.format)).digest("hex");
|
|
@@ -7573,15 +7709,15 @@ function signature(statePath) {
|
|
|
7573
7709
|
const parts = [];
|
|
7574
7710
|
for (const rel of ["config.json", "keys.json"]) {
|
|
7575
7711
|
try {
|
|
7576
|
-
const s = statSync9(
|
|
7712
|
+
const s = statSync9(join20(dir, rel));
|
|
7577
7713
|
parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
|
|
7578
7714
|
} catch {
|
|
7579
7715
|
}
|
|
7580
7716
|
}
|
|
7581
7717
|
try {
|
|
7582
|
-
for (const name of readdirSync15(
|
|
7718
|
+
for (const name of readdirSync15(join20(dir, "locales")).sort()) {
|
|
7583
7719
|
if (!name.endsWith(".json")) continue;
|
|
7584
|
-
const s = statSync9(
|
|
7720
|
+
const s = statSync9(join20(dir, "locales", name));
|
|
7585
7721
|
parts.push(`${name}:${s.size}:${s.mtimeMs}`);
|
|
7586
7722
|
}
|
|
7587
7723
|
} catch {
|
|
@@ -7660,13 +7796,13 @@ var init_watch = __esm({
|
|
|
7660
7796
|
// src/server/api.ts
|
|
7661
7797
|
import { Hono } from "hono";
|
|
7662
7798
|
import { streamSSE } from "hono/streaming";
|
|
7663
|
-
import { readFileSync as
|
|
7799
|
+
import { readFileSync as readFileSync26, existsSync as existsSync14, readdirSync as readdirSync16, statSync as statSync10, rmSync as rmSync7 } from "fs";
|
|
7664
7800
|
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
7665
7801
|
function projectName(root) {
|
|
7666
7802
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
7667
|
-
if (
|
|
7803
|
+
if (existsSync14(nameFile)) {
|
|
7668
7804
|
try {
|
|
7669
|
-
const name =
|
|
7805
|
+
const name = readFileSync26(nameFile, "utf8").trim();
|
|
7670
7806
|
if (name) return name;
|
|
7671
7807
|
} catch {
|
|
7672
7808
|
}
|
|
@@ -7880,7 +8016,7 @@ function createApi(deps) {
|
|
|
7880
8016
|
if (name.startsWith(".") || name === "node_modules") continue;
|
|
7881
8017
|
const abs = resolve9(dir, name);
|
|
7882
8018
|
let filePath = null;
|
|
7883
|
-
if ((name === "glotfile" || name.endsWith(".glotfile")) &&
|
|
8019
|
+
if ((name === "glotfile" || name.endsWith(".glotfile")) && existsSync14(resolve9(abs, "config.json"))) {
|
|
7884
8020
|
filePath = resolve9(dir, `${name}.json`);
|
|
7885
8021
|
} else if (name === "glotfile.json" || name.endsWith(".glotfile.json")) {
|
|
7886
8022
|
filePath = abs;
|
|
@@ -7914,7 +8050,7 @@ function createApi(deps) {
|
|
|
7914
8050
|
const resolved = resolve9(projectRoot, path);
|
|
7915
8051
|
const inside = resolved === projectRoot || resolved.startsWith(projectRoot + sep2);
|
|
7916
8052
|
if (!inside) return c.json({ error: "file is outside the project" }, 400);
|
|
7917
|
-
if (!
|
|
8053
|
+
if (!existsSync14(resolved)) return c.json({ error: "file not found" }, 400);
|
|
7918
8054
|
loadState(resolved);
|
|
7919
8055
|
deps.statePath = resolved;
|
|
7920
8056
|
watcher.retarget(resolved);
|
|
@@ -7976,9 +8112,9 @@ function createApi(deps) {
|
|
|
7976
8112
|
const abs = resolve9(root, screenshot);
|
|
7977
8113
|
const rel = relative4(root, abs);
|
|
7978
8114
|
const seg0 = rel.split(sep2)[0] ?? "";
|
|
7979
|
-
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") &&
|
|
8115
|
+
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") && existsSync14(abs)) {
|
|
7980
8116
|
try {
|
|
7981
|
-
|
|
8117
|
+
rmSync7(abs);
|
|
7982
8118
|
} catch {
|
|
7983
8119
|
}
|
|
7984
8120
|
}
|
|
@@ -8307,6 +8443,93 @@ function createApi(deps) {
|
|
|
8307
8443
|
await stream.writeSSE({ event: "done", data: JSON.stringify({ added: added.length, terms: added }) });
|
|
8308
8444
|
});
|
|
8309
8445
|
});
|
|
8446
|
+
app.post("/glossary/suggest/estimate", async (c) => {
|
|
8447
|
+
const body = await c.req.json().catch(() => ({}));
|
|
8448
|
+
const s = load();
|
|
8449
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
8450
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8451
|
+
return c.json(estimateGlossarySuggest(sources, knownTermList(s), aiCfg));
|
|
8452
|
+
});
|
|
8453
|
+
app.get("/glossary/suggest/batch/status", async (c) => {
|
|
8454
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8455
|
+
let supported = false;
|
|
8456
|
+
let provider;
|
|
8457
|
+
try {
|
|
8458
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8459
|
+
supported = supportsBatchComplete(provider);
|
|
8460
|
+
} catch {
|
|
8461
|
+
}
|
|
8462
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
8463
|
+
if (!pending) return c.json({ supported, pending: null });
|
|
8464
|
+
const base = { batchId: pending.batchId, createdAt: pending.createdAt, model: pending.model, total: pending.total };
|
|
8465
|
+
if (!provider || !supportsBatchComplete(provider)) {
|
|
8466
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null } });
|
|
8467
|
+
}
|
|
8468
|
+
try {
|
|
8469
|
+
const status = await provider.translationBatchStatus(pending.batchId);
|
|
8470
|
+
return c.json({ supported, pending: { ...base, status: status.status, counts: status.counts } });
|
|
8471
|
+
} catch (e) {
|
|
8472
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null, error: e.message } });
|
|
8473
|
+
}
|
|
8474
|
+
});
|
|
8475
|
+
app.post("/glossary/suggest/batch", (c) => withTranslateLock(async () => {
|
|
8476
|
+
const body = await c.req.json().catch(() => ({}));
|
|
8477
|
+
const s = load();
|
|
8478
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
8479
|
+
if (!sources.length) return c.json({ error: "No source strings to scan." }, 400);
|
|
8480
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8481
|
+
let provider;
|
|
8482
|
+
try {
|
|
8483
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8484
|
+
} catch (e) {
|
|
8485
|
+
return c.json({ error: e.message }, 400);
|
|
8486
|
+
}
|
|
8487
|
+
if (!supportsBatchComplete(provider)) {
|
|
8488
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
8489
|
+
}
|
|
8490
|
+
const batchSize = aiCfg.contextBatchSize ?? aiCfg.batchSize ?? 10;
|
|
8491
|
+
let pending;
|
|
8492
|
+
try {
|
|
8493
|
+
pending = await submitGlossarySuggestBatch(provider, sources, knownTermList(s), batchSize, aiCfg.model, projectRoot);
|
|
8494
|
+
} catch (e) {
|
|
8495
|
+
return c.json({ error: e.message }, 409);
|
|
8496
|
+
}
|
|
8497
|
+
appendLog(projectRoot, {
|
|
8498
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8499
|
+
kind: "glossary",
|
|
8500
|
+
summary: `Submitted glossary suggestion batch ${pending.batchId} (${pending.total} sources)`,
|
|
8501
|
+
model: aiCfg.model
|
|
8502
|
+
});
|
|
8503
|
+
return c.json({ batchId: pending.batchId, total: pending.total });
|
|
8504
|
+
}));
|
|
8505
|
+
app.post("/glossary/suggest/batch/apply", (c) => withTranslateLock(async () => {
|
|
8506
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
8507
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
8508
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8509
|
+
let provider;
|
|
8510
|
+
try {
|
|
8511
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8512
|
+
} catch (e) {
|
|
8513
|
+
return c.json({ error: e.message }, 400);
|
|
8514
|
+
}
|
|
8515
|
+
if (!supportsBatchComplete(provider)) {
|
|
8516
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
8517
|
+
}
|
|
8518
|
+
const outcome = await applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
|
|
8519
|
+
return c.json(outcome);
|
|
8520
|
+
}));
|
|
8521
|
+
app.post("/glossary/suggest/batch/cancel", async (c) => {
|
|
8522
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
8523
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
8524
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8525
|
+
try {
|
|
8526
|
+
const provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8527
|
+
if (supportsBatchComplete(provider)) await provider.cancelTranslationBatch(pending.batchId);
|
|
8528
|
+
} catch {
|
|
8529
|
+
}
|
|
8530
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
8531
|
+
return c.json({ canceled: pending.batchId });
|
|
8532
|
+
});
|
|
8310
8533
|
app.post("/keys/:key/screenshot", async (c) => {
|
|
8311
8534
|
const key = c.req.param("key");
|
|
8312
8535
|
const body = await c.req.parseBody();
|
|
@@ -9026,6 +9249,8 @@ var init_api = __esm({
|
|
|
9026
9249
|
init_pending_batch();
|
|
9027
9250
|
init_context_batch_run();
|
|
9028
9251
|
init_pending_context_batch();
|
|
9252
|
+
init_glossary_batch_run();
|
|
9253
|
+
init_pending_glossary_batch();
|
|
9029
9254
|
init_estimate();
|
|
9030
9255
|
init_pricing();
|
|
9031
9256
|
init_price_fetch();
|
|
@@ -9054,7 +9279,7 @@ __export(server_exports, {
|
|
|
9054
9279
|
import { Hono as Hono2 } from "hono";
|
|
9055
9280
|
import { serve } from "@hono/node-server";
|
|
9056
9281
|
import { fileURLToPath } from "url";
|
|
9057
|
-
import { dirname as dirname4, join as
|
|
9282
|
+
import { dirname as dirname4, join as join21, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
|
|
9058
9283
|
import { readFile, stat } from "fs/promises";
|
|
9059
9284
|
import { createServer } from "net";
|
|
9060
9285
|
import open from "open";
|
|
@@ -9110,7 +9335,7 @@ function buildApp(opts) {
|
|
|
9110
9335
|
const file = await readFileResponse(target);
|
|
9111
9336
|
if (file) return file;
|
|
9112
9337
|
}
|
|
9113
|
-
const index = await readFileResponse(
|
|
9338
|
+
const index = await readFileResponse(join21(root, "index.html"));
|
|
9114
9339
|
if (index) return index;
|
|
9115
9340
|
return c.notFound();
|
|
9116
9341
|
});
|
|
@@ -9180,7 +9405,7 @@ var init_server = __esm({
|
|
|
9180
9405
|
init_scanner();
|
|
9181
9406
|
init_usage();
|
|
9182
9407
|
here = dirname4(fileURLToPath(import.meta.url));
|
|
9183
|
-
DEFAULT_UI_DIR =
|
|
9408
|
+
DEFAULT_UI_DIR = join21(here, "..", "ui");
|
|
9184
9409
|
MIME = {
|
|
9185
9410
|
".html": "text/html; charset=utf-8",
|
|
9186
9411
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -9212,8 +9437,8 @@ var init_server = __esm({
|
|
|
9212
9437
|
// src/server/cli.ts
|
|
9213
9438
|
init_state();
|
|
9214
9439
|
init_stats();
|
|
9215
|
-
import { resolve as resolve11, dirname as dirname5, join as
|
|
9216
|
-
import { readFileSync as
|
|
9440
|
+
import { resolve as resolve11, dirname as dirname5, join as join22, basename as basename2 } from "path";
|
|
9441
|
+
import { readFileSync as readFileSync27, existsSync as existsSync15, mkdirSync as mkdirSync7, cpSync } from "fs";
|
|
9217
9442
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9218
9443
|
|
|
9219
9444
|
// src/server/agent-cli.ts
|
|
@@ -9344,6 +9569,8 @@ init_batch_run();
|
|
|
9344
9569
|
init_pending_batch();
|
|
9345
9570
|
init_context_batch_run();
|
|
9346
9571
|
init_pending_context_batch();
|
|
9572
|
+
init_glossary_batch_run();
|
|
9573
|
+
init_pending_glossary_batch();
|
|
9347
9574
|
init_estimate();
|
|
9348
9575
|
init_glossary_suggest();
|
|
9349
9576
|
init_pricing();
|
|
@@ -9604,7 +9831,7 @@ function translateSelection(args) {
|
|
|
9604
9831
|
}
|
|
9605
9832
|
function readStdin() {
|
|
9606
9833
|
try {
|
|
9607
|
-
return
|
|
9834
|
+
return readFileSync27(0, "utf8");
|
|
9608
9835
|
} catch {
|
|
9609
9836
|
return "";
|
|
9610
9837
|
}
|
|
@@ -9777,13 +10004,15 @@ async function runBatch(args) {
|
|
|
9777
10004
|
const projectRoot = dirname5(resolve11(args.statePath));
|
|
9778
10005
|
const pending = loadPendingBatch(projectRoot);
|
|
9779
10006
|
const ctxPending = loadPendingContextBatch(projectRoot);
|
|
9780
|
-
|
|
9781
|
-
|
|
10007
|
+
const glossPending = loadPendingGlossaryBatch(projectRoot);
|
|
10008
|
+
if (!pending && !ctxPending && !glossPending) {
|
|
10009
|
+
console.log("No pending batch. Start one with `glotfile translate --batch`, `glotfile build-context --batch`, or `glotfile suggest-glossary --batch`.");
|
|
9782
10010
|
return;
|
|
9783
10011
|
}
|
|
9784
10012
|
const action = args.batchAction ?? "status";
|
|
9785
10013
|
if (pending) await runTranslationBatchAction(args, pending, action, projectRoot);
|
|
9786
10014
|
if (ctxPending) await runContextBatchAction(args, ctxPending, action, projectRoot);
|
|
10015
|
+
if (glossPending) await runGlossaryBatchAction(args, glossPending, action, projectRoot);
|
|
9787
10016
|
}
|
|
9788
10017
|
async function runTranslationBatchAction(args, pending, action, projectRoot) {
|
|
9789
10018
|
if (action === "cancel") {
|
|
@@ -9869,18 +10098,65 @@ async function runContextBatchAction(args, pending, action, projectRoot) {
|
|
|
9869
10098
|
if (outcome.retried) console.log(`${outcome.retried} job(s) re-run synchronously (batch entries failed or were malformed).`);
|
|
9870
10099
|
for (const e of outcome.errors) console.warn(`skip ${e.key}: ${e.error}`);
|
|
9871
10100
|
}
|
|
10101
|
+
async function runGlossaryBatchAction(args, pending, action, projectRoot) {
|
|
10102
|
+
if (action === "cancel") {
|
|
10103
|
+
let remoteFailed = false;
|
|
10104
|
+
try {
|
|
10105
|
+
const ai2 = loadLocalSettings(projectRoot).ai;
|
|
10106
|
+
const provider2 = makeProvider(ai2);
|
|
10107
|
+
if (supportsBatchComplete(provider2)) {
|
|
10108
|
+
await provider2.cancelTranslationBatch(pending.batchId);
|
|
10109
|
+
} else {
|
|
10110
|
+
remoteFailed = true;
|
|
10111
|
+
}
|
|
10112
|
+
} catch {
|
|
10113
|
+
remoteFailed = true;
|
|
10114
|
+
}
|
|
10115
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
10116
|
+
const suffix = remoteFailed ? " (remote cancel failed \u2014 it will expire server-side)" : "";
|
|
10117
|
+
console.log(`Canceled glossary suggestion batch ${pending.batchId}.${suffix}`);
|
|
10118
|
+
return;
|
|
10119
|
+
}
|
|
10120
|
+
const ai = loadLocalSettings(projectRoot).ai;
|
|
10121
|
+
const provider = makeProviderOrExit(ai);
|
|
10122
|
+
if (!provider) return;
|
|
10123
|
+
if (!supportsBatchComplete(provider)) {
|
|
10124
|
+
console.error(`Pending glossary batch was submitted via anthropic, but the configured provider "${ai.provider}" has no batch support.`);
|
|
10125
|
+
process.exitCode = 1;
|
|
10126
|
+
return;
|
|
10127
|
+
}
|
|
10128
|
+
const status = await provider.translationBatchStatus(pending.batchId);
|
|
10129
|
+
const c = status.counts;
|
|
10130
|
+
console.log(`Glossary suggestion batch ${pending.batchId} (${pending.total} source(s), submitted ${pending.createdAt})`);
|
|
10131
|
+
console.log(` ${status.status} \u2014 ${c.succeeded} succeeded, ${c.processing} processing, ${c.errored} errored, ${c.expired} expired, ${c.canceled} canceled`);
|
|
10132
|
+
if (status.status !== "ended") {
|
|
10133
|
+
if (action === "apply") console.log("Not finished yet \u2014 try again later.");
|
|
10134
|
+
return;
|
|
10135
|
+
}
|
|
10136
|
+
const outcome = await applyGlossarySuggestBatchResults(
|
|
10137
|
+
() => loadState(args.statePath),
|
|
10138
|
+
(s) => saveState(args.statePath, s),
|
|
10139
|
+
provider,
|
|
10140
|
+
pending,
|
|
10141
|
+
projectRoot,
|
|
10142
|
+
ai
|
|
10143
|
+
);
|
|
10144
|
+
console.log(`Found ${outcome.added} new candidate term(s).`);
|
|
10145
|
+
if (outcome.retried) console.log(`${outcome.retried} job(s) re-run synchronously (batch entries failed or were malformed).`);
|
|
10146
|
+
for (const e of outcome.errors) console.warn(`batch job failed: ${e.error}`);
|
|
10147
|
+
}
|
|
9872
10148
|
function sarifContextFor(statePath) {
|
|
9873
10149
|
if (detectFormat(statePath) === "split") {
|
|
9874
10150
|
const dir = splitDirFor(statePath);
|
|
9875
|
-
const keysPath =
|
|
10151
|
+
const keysPath = join22(dir, "keys.json");
|
|
9876
10152
|
return {
|
|
9877
10153
|
keysUri: `${basename2(dir)}/keys.json`,
|
|
9878
|
-
keysRawText:
|
|
10154
|
+
keysRawText: existsSync15(keysPath) ? readFileSync27(keysPath, "utf8") : ""
|
|
9879
10155
|
};
|
|
9880
10156
|
}
|
|
9881
10157
|
return {
|
|
9882
10158
|
keysUri: basename2(statePath),
|
|
9883
|
-
keysRawText:
|
|
10159
|
+
keysRawText: existsSync15(statePath) ? readFileSync27(statePath, "utf8") : ""
|
|
9884
10160
|
};
|
|
9885
10161
|
}
|
|
9886
10162
|
function printReport(report, format, statePath) {
|
|
@@ -9949,7 +10225,7 @@ async function runImportCmd(args) {
|
|
|
9949
10225
|
const { runImport: runImport2 } = await Promise.resolve().then(() => (init_run3(), run_exports));
|
|
9950
10226
|
const projectRoot = args.importSource ? resolve11(args.importSource) : dirname5(resolve11(args.statePath));
|
|
9951
10227
|
const out = resolve11(projectRoot, "glotfile.json");
|
|
9952
|
-
if (
|
|
10228
|
+
if (existsSync15(out) && !args.importForce) {
|
|
9953
10229
|
console.error(`${out} already exists; pass --force to overwrite`);
|
|
9954
10230
|
process.exitCode = 1;
|
|
9955
10231
|
return;
|
|
@@ -10155,6 +10431,31 @@ async function runSuggestGlossary(args) {
|
|
|
10155
10431
|
const system = buildGlossarySuggestSystemPrompt();
|
|
10156
10432
|
const batchSize = aiCfg.contextBatchSize ?? aiCfg.batchSize ?? 10;
|
|
10157
10433
|
const concurrency = aiCfg.contextConcurrency ?? aiCfg.concurrency ?? 3;
|
|
10434
|
+
if (args.batch) {
|
|
10435
|
+
if (!supportsBatchComplete(provider)) {
|
|
10436
|
+
console.error(`Provider "${aiCfg.provider}" does not support batch mode. Currently anthropic only.`);
|
|
10437
|
+
process.exitCode = 1;
|
|
10438
|
+
return;
|
|
10439
|
+
}
|
|
10440
|
+
let pending;
|
|
10441
|
+
try {
|
|
10442
|
+
pending = await submitGlossarySuggestBatch(provider, sources, known, batchSize, aiCfg.model, projectRoot);
|
|
10443
|
+
} catch (e) {
|
|
10444
|
+
console.error(e.message);
|
|
10445
|
+
process.exitCode = 1;
|
|
10446
|
+
return;
|
|
10447
|
+
}
|
|
10448
|
+
appendLog(projectRoot, {
|
|
10449
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10450
|
+
kind: "glossary",
|
|
10451
|
+
summary: `Submitted glossary suggestion batch ${pending.batchId} (${pending.total} sources)`,
|
|
10452
|
+
model: aiCfg.model,
|
|
10453
|
+
system
|
|
10454
|
+
});
|
|
10455
|
+
console.log(`Submitted glossary suggestion batch ${pending.batchId} \u2014 ${pending.total} source string(s) at 50% batch pricing.`);
|
|
10456
|
+
console.log("Check progress with `glotfile batch`; it applies results automatically when finished.");
|
|
10457
|
+
return;
|
|
10458
|
+
}
|
|
10158
10459
|
const chunks = [];
|
|
10159
10460
|
for (let i = 0; i < sources.length; i += batchSize) chunks.push(sources.slice(i, i + batchSize));
|
|
10160
10461
|
const all = [];
|
|
@@ -10259,19 +10560,19 @@ function runSplit(args) {
|
|
|
10259
10560
|
`Split catalog into ${splitDirFor(args.statePath)}/ (config.json, keys.json, locales/ \u2014 up to ${state.config.locales.length} locale files). Removed ${args.statePath}.`
|
|
10260
10561
|
);
|
|
10261
10562
|
}
|
|
10262
|
-
var SKILL_SRC =
|
|
10563
|
+
var SKILL_SRC = join22(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
|
|
10263
10564
|
function runSkill(args) {
|
|
10264
10565
|
if (args.print) {
|
|
10265
|
-
console.log(
|
|
10566
|
+
console.log(readFileSync27(join22(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
|
|
10266
10567
|
return;
|
|
10267
10568
|
}
|
|
10268
10569
|
const dest = resolve11(process.cwd(), ".claude", "skills", "glotfile");
|
|
10269
|
-
if (
|
|
10570
|
+
if (existsSync15(dest) && !args.importForce) {
|
|
10270
10571
|
console.error(`${dest} already exists; pass --force to overwrite`);
|
|
10271
10572
|
process.exitCode = 1;
|
|
10272
10573
|
return;
|
|
10273
10574
|
}
|
|
10274
|
-
|
|
10575
|
+
mkdirSync7(dirname5(dest), { recursive: true });
|
|
10275
10576
|
cpSync(SKILL_SRC, dest, { recursive: true });
|
|
10276
10577
|
console.log(`Installed the glotfile skill to ${dest}. Restart Claude Code to pick it up.`);
|
|
10277
10578
|
}
|
|
@@ -10564,12 +10865,13 @@ var COMMAND_HELP = {
|
|
|
10564
10865
|
},
|
|
10565
10866
|
"suggest-glossary": {
|
|
10566
10867
|
summary: "AI-scan source strings for candidate glossary terms (adds a review queue; existing terms are skipped).",
|
|
10567
|
-
usage: "glotfile suggest-glossary [--key <glob>] [--limit <n>] [--since <date>] [--estimate]",
|
|
10868
|
+
usage: "glotfile suggest-glossary [--key <glob>] [--limit <n>] [--since <date>] [--estimate] [--batch]",
|
|
10568
10869
|
options: [
|
|
10569
10870
|
["--key <glob>", "Only scan keys matching this glob"],
|
|
10570
10871
|
["--limit <n>", "Scan at most n source strings"],
|
|
10571
10872
|
["--since <date>", "Only keys added since this date"],
|
|
10572
|
-
["--estimate", "Print batches, tokens and estimated cost without scanning"]
|
|
10873
|
+
["--estimate", "Print batches, tokens and estimated cost without scanning"],
|
|
10874
|
+
["--batch", "Submit via the provider's batch API (50% cost, async; anthropic only)"]
|
|
10573
10875
|
]
|
|
10574
10876
|
},
|
|
10575
10877
|
scan: {
|
|
@@ -10704,8 +11006,8 @@ ${formatOpts([...options, ...GLOBAL_OPTS])}`);
|
|
|
10704
11006
|
);
|
|
10705
11007
|
}
|
|
10706
11008
|
function printVersion() {
|
|
10707
|
-
const pkgPath =
|
|
10708
|
-
console.log(JSON.parse(
|
|
11009
|
+
const pkgPath = join22(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
|
11010
|
+
console.log(JSON.parse(readFileSync27(pkgPath, "utf8")).version);
|
|
10709
11011
|
}
|
|
10710
11012
|
async function main(argv) {
|
|
10711
11013
|
const args = parseArgs(argv);
|