glotfile 1.1.0 → 1.1.2
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 +422 -127
- package/dist/server/server.js +328 -104
- package/dist/ui/assets/{index-Dwn9g3g-.js → index-DQsfrbMP.js} +19 -7
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
package/dist/server/cli.js
CHANGED
|
@@ -274,9 +274,6 @@ function validate(raw) {
|
|
|
274
274
|
if (entry.contextSource !== void 0 && entry.contextSource !== "ai") {
|
|
275
275
|
fail(`key "${key}" contextSource must be "ai" if present`);
|
|
276
276
|
}
|
|
277
|
-
if (entry.contextAt !== void 0 && typeof entry.contextAt !== "string") {
|
|
278
|
-
fail(`key "${key}" contextAt must be a string if present`);
|
|
279
|
-
}
|
|
280
277
|
}
|
|
281
278
|
if (raw.glossary !== void 0 && !Array.isArray(raw.glossary)) fail("glossary must be an array");
|
|
282
279
|
if (raw.glossarySuggestions !== void 0 && !Array.isArray(raw.glossarySuggestions)) fail("glossarySuggestions must be an array");
|
|
@@ -772,7 +769,6 @@ function setMetadata(state, key, partial) {
|
|
|
772
769
|
delete safe.values;
|
|
773
770
|
if ("context" in safe) {
|
|
774
771
|
delete entry.contextSource;
|
|
775
|
-
delete entry.contextAt;
|
|
776
772
|
}
|
|
777
773
|
Object.assign(entry, safe);
|
|
778
774
|
if ("context" in safe && !entry.context) delete entry.context;
|
|
@@ -3098,7 +3094,7 @@ var init_bedrock = __esm({
|
|
|
3098
3094
|
if (res.stopReason === "max_tokens") {
|
|
3099
3095
|
throw new MalformedReplyError(text || JSON.stringify(tool?.input ?? {}));
|
|
3100
3096
|
}
|
|
3101
|
-
if (tool?.input?.items) return tool.input.items;
|
|
3097
|
+
if (Array.isArray(tool?.input?.items)) return tool.input.items;
|
|
3102
3098
|
return parseReplyItems(text);
|
|
3103
3099
|
}
|
|
3104
3100
|
};
|
|
@@ -4063,7 +4059,7 @@ ${s.lines}
|
|
|
4063
4059
|
});
|
|
4064
4060
|
return 'Write a context note for each key. Return JSON {"items":[{"id","context"}]}.\n' + JSON.stringify(items, null, 2);
|
|
4065
4061
|
}
|
|
4066
|
-
function applyContext(state, reqs, results,
|
|
4062
|
+
function applyContext(state, reqs, results, force = false) {
|
|
4067
4063
|
const byId = new Map(reqs.map((r) => [r.id, r]));
|
|
4068
4064
|
let written = 0;
|
|
4069
4065
|
const errors = [];
|
|
@@ -4087,7 +4083,6 @@ function applyContext(state, reqs, results, clock = systemClock, force = false)
|
|
|
4087
4083
|
if (!entry || entry.context && !force) continue;
|
|
4088
4084
|
entry.context = context;
|
|
4089
4085
|
entry.contextSource = "ai";
|
|
4090
|
-
entry.contextAt = clock();
|
|
4091
4086
|
written++;
|
|
4092
4087
|
}
|
|
4093
4088
|
return { written, errors };
|
|
@@ -4096,7 +4091,6 @@ var MAX_CONTEXT_LENGTH, SNIPPET_WINDOW, MAX_SNIPPETS, EXCLUDED_DIRS, CONTEXT_BAT
|
|
|
4096
4091
|
var init_context = __esm({
|
|
4097
4092
|
"src/server/ai/context.ts"() {
|
|
4098
4093
|
"use strict";
|
|
4099
|
-
init_state();
|
|
4100
4094
|
init_placeholders();
|
|
4101
4095
|
MAX_CONTEXT_LENGTH = 500;
|
|
4102
4096
|
SNIPPET_WINDOW = 15;
|
|
@@ -4238,7 +4232,7 @@ async function applyContextBatchResults(load, persist, provider, pending, projec
|
|
|
4238
4232
|
if (retryUsage) addUsage(usage, retryUsage);
|
|
4239
4233
|
}
|
|
4240
4234
|
const fresh = load();
|
|
4241
|
-
const { written, errors: applyErrors } = applyContext(fresh, applied, items,
|
|
4235
|
+
const { written, errors: applyErrors } = applyContext(fresh, applied, items, pending.force);
|
|
4242
4236
|
errors.push(...applyErrors);
|
|
4243
4237
|
persist(fresh);
|
|
4244
4238
|
clearPendingContextBatch(projectRoot);
|
|
@@ -4369,6 +4363,142 @@ var init_glossary_suggest = __esm({
|
|
|
4369
4363
|
}
|
|
4370
4364
|
});
|
|
4371
4365
|
|
|
4366
|
+
// src/server/ai/pending-glossary-batch.ts
|
|
4367
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync6 } from "fs";
|
|
4368
|
+
import { join as join6 } from "path";
|
|
4369
|
+
function pendingGlossaryBatchPath(projectRoot) {
|
|
4370
|
+
return join6(projectRoot, ".glotfile", "glossary-suggest-batch.json");
|
|
4371
|
+
}
|
|
4372
|
+
function loadPendingGlossaryBatch(projectRoot) {
|
|
4373
|
+
const path = pendingGlossaryBatchPath(projectRoot);
|
|
4374
|
+
if (!existsSync10(path)) return void 0;
|
|
4375
|
+
try {
|
|
4376
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
4377
|
+
if (parsed?.version !== 1) return void 0;
|
|
4378
|
+
return parsed;
|
|
4379
|
+
} catch {
|
|
4380
|
+
return void 0;
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
function savePendingGlossaryBatch(projectRoot, pending) {
|
|
4384
|
+
const dir = join6(projectRoot, ".glotfile");
|
|
4385
|
+
mkdirSync6(dir, { recursive: true });
|
|
4386
|
+
const gitignore = join6(dir, ".gitignore");
|
|
4387
|
+
if (!existsSync10(gitignore)) writeFileSync5(gitignore, "*\n");
|
|
4388
|
+
writeFileSync5(pendingGlossaryBatchPath(projectRoot), JSON.stringify(pending, null, 2) + "\n");
|
|
4389
|
+
}
|
|
4390
|
+
function clearPendingGlossaryBatch(projectRoot) {
|
|
4391
|
+
rmSync6(pendingGlossaryBatchPath(projectRoot), { force: true });
|
|
4392
|
+
}
|
|
4393
|
+
var init_pending_glossary_batch = __esm({
|
|
4394
|
+
"src/server/ai/pending-glossary-batch.ts"() {
|
|
4395
|
+
"use strict";
|
|
4396
|
+
}
|
|
4397
|
+
});
|
|
4398
|
+
|
|
4399
|
+
// src/server/ai/glossary-batch-run.ts
|
|
4400
|
+
function completionRequestFor2(chunk2, knownTerms) {
|
|
4401
|
+
return {
|
|
4402
|
+
system: buildGlossarySuggestSystemPrompt(),
|
|
4403
|
+
content: [{ type: "text", text: buildGlossarySuggestBatchPrompt(chunk2, knownTerms) }],
|
|
4404
|
+
schema: GLOSSARY_SUGGEST_SCHEMA
|
|
4405
|
+
};
|
|
4406
|
+
}
|
|
4407
|
+
async function submitGlossarySuggestBatch(provider, sources, knownTerms, batchSize, model, projectRoot) {
|
|
4408
|
+
if (loadPendingGlossaryBatch(projectRoot)) {
|
|
4409
|
+
throw new Error("A glossary suggestion batch is already pending. Apply or cancel it first.");
|
|
4410
|
+
}
|
|
4411
|
+
const chunks = [];
|
|
4412
|
+
const size = Math.max(1, batchSize);
|
|
4413
|
+
for (let i = 0; i < sources.length; i += size) chunks.push(sources.slice(i, i + size));
|
|
4414
|
+
const jobs = chunks.map((chunk2, i) => ({ customId: `gloss_${i}`, chunk: chunk2 }));
|
|
4415
|
+
const batchId = await provider.submitCompletionBatch(
|
|
4416
|
+
jobs.map((j) => ({ customId: j.customId, request: completionRequestFor2(j.chunk, knownTerms) }))
|
|
4417
|
+
);
|
|
4418
|
+
const pending = {
|
|
4419
|
+
version: 1,
|
|
4420
|
+
// Only Anthropic implements completion batches today.
|
|
4421
|
+
provider: "anthropic",
|
|
4422
|
+
model,
|
|
4423
|
+
batchId,
|
|
4424
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4425
|
+
total: sources.length,
|
|
4426
|
+
knownTerms,
|
|
4427
|
+
jobs: jobs.map((j) => ({
|
|
4428
|
+
customId: j.customId,
|
|
4429
|
+
requests: j.chunk
|
|
4430
|
+
}))
|
|
4431
|
+
};
|
|
4432
|
+
savePendingGlossaryBatch(projectRoot, pending);
|
|
4433
|
+
return pending;
|
|
4434
|
+
}
|
|
4435
|
+
async function applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, ai) {
|
|
4436
|
+
provider.takeUsage?.();
|
|
4437
|
+
const outcomes = await provider.completionBatchResults(pending.batchId);
|
|
4438
|
+
const batchUsage = provider.takeUsage?.();
|
|
4439
|
+
const allTerms = [];
|
|
4440
|
+
const errors = [];
|
|
4441
|
+
const jobFailures = [];
|
|
4442
|
+
const retryChunks = [];
|
|
4443
|
+
for (const job of pending.jobs) {
|
|
4444
|
+
const outcome = outcomes.get(job.customId);
|
|
4445
|
+
if (outcome?.type === "json") {
|
|
4446
|
+
const batch = outcome.value;
|
|
4447
|
+
allTerms.push(...batch.terms ?? []);
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
if (!outcome) jobFailures.push({ customId: job.customId, locale: "", type: "missing" });
|
|
4451
|
+
else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: "", type: "malformed", raw: outcome.raw });
|
|
4452
|
+
else jobFailures.push({ customId: job.customId, locale: "", type: "failed", error: outcome.error });
|
|
4453
|
+
retryChunks.push(job.requests);
|
|
4454
|
+
}
|
|
4455
|
+
for (const chunk2 of retryChunks) {
|
|
4456
|
+
try {
|
|
4457
|
+
const raw = await provider.complete(completionRequestFor2(chunk2, pending.knownTerms));
|
|
4458
|
+
const batch = raw;
|
|
4459
|
+
allTerms.push(...batch.terms ?? []);
|
|
4460
|
+
} catch (e) {
|
|
4461
|
+
errors.push({ error: e.message });
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
const retryUsage = provider.takeUsage?.();
|
|
4465
|
+
const pricing = resolvePricing({ ...ai, model: pending.model });
|
|
4466
|
+
let estimatedCostUsd;
|
|
4467
|
+
if (pricing && (batchUsage || retryUsage)) {
|
|
4468
|
+
estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
|
|
4469
|
+
}
|
|
4470
|
+
let usage;
|
|
4471
|
+
if (batchUsage || retryUsage) {
|
|
4472
|
+
usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
4473
|
+
if (retryUsage) addUsage(usage, retryUsage);
|
|
4474
|
+
}
|
|
4475
|
+
const fresh = load();
|
|
4476
|
+
const added = mergeGlossarySuggestions(fresh, dedupeTerms(allTerms));
|
|
4477
|
+
persist(fresh);
|
|
4478
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
4479
|
+
const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
|
|
4480
|
+
appendLog(projectRoot, {
|
|
4481
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4482
|
+
kind: "glossary",
|
|
4483
|
+
summary: `Applied glossary suggestion batch ${pending.batchId}: ${added.length} new term(s), ${errors.length} error(s), ${retryChunks.length} job(s) retried${costSuffix}`,
|
|
4484
|
+
model: pending.model,
|
|
4485
|
+
jobFailures: jobFailures.length ? jobFailures : void 0,
|
|
4486
|
+
usage,
|
|
4487
|
+
estimatedCostUsd
|
|
4488
|
+
});
|
|
4489
|
+
return { added: added.length, errors, retried: retryChunks.length };
|
|
4490
|
+
}
|
|
4491
|
+
var init_glossary_batch_run = __esm({
|
|
4492
|
+
"src/server/ai/glossary-batch-run.ts"() {
|
|
4493
|
+
"use strict";
|
|
4494
|
+
init_glossary_suggest();
|
|
4495
|
+
init_pending_glossary_batch();
|
|
4496
|
+
init_state();
|
|
4497
|
+
init_log();
|
|
4498
|
+
init_pricing();
|
|
4499
|
+
}
|
|
4500
|
+
});
|
|
4501
|
+
|
|
4372
4502
|
// src/server/ai/estimate.ts
|
|
4373
4503
|
function estimateTokens(text) {
|
|
4374
4504
|
const cjk = text.match(CJK_RE)?.length ?? 0;
|
|
@@ -4535,13 +4665,13 @@ var init_price_fetch = __esm({
|
|
|
4535
4665
|
});
|
|
4536
4666
|
|
|
4537
4667
|
// src/server/scan.ts
|
|
4538
|
-
import { existsSync as
|
|
4668
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
4539
4669
|
import { resolve as resolve7 } from "path";
|
|
4540
4670
|
function loadUsageCache(projectRoot) {
|
|
4541
4671
|
const path = resolve7(projectRoot, ".glotfile", "usage.json");
|
|
4542
|
-
if (!
|
|
4672
|
+
if (!existsSync11(path)) return null;
|
|
4543
4673
|
try {
|
|
4544
|
-
return JSON.parse(
|
|
4674
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
4545
4675
|
} catch {
|
|
4546
4676
|
return null;
|
|
4547
4677
|
}
|
|
@@ -4606,8 +4736,8 @@ var init_scan = __esm({
|
|
|
4606
4736
|
});
|
|
4607
4737
|
|
|
4608
4738
|
// src/server/scanner.ts
|
|
4609
|
-
import { readdirSync as readdirSync3, statSync as statSync3, readFileSync as
|
|
4610
|
-
import { join as
|
|
4739
|
+
import { readdirSync as readdirSync3, statSync as statSync3, readFileSync as readFileSync13 } from "fs";
|
|
4740
|
+
import { join as join7, extname as extname2, relative } from "path";
|
|
4611
4741
|
function scannerForExt(ext) {
|
|
4612
4742
|
return EXT_SCANNER[ext] ?? null;
|
|
4613
4743
|
}
|
|
@@ -4829,7 +4959,7 @@ function* walkFiles(dir, root, exclude) {
|
|
|
4829
4959
|
}
|
|
4830
4960
|
for (const name of entries) {
|
|
4831
4961
|
if (ALWAYS_EXCLUDE.has(name)) continue;
|
|
4832
|
-
const abs =
|
|
4962
|
+
const abs = join7(dir, name);
|
|
4833
4963
|
const rel = relative(root, abs);
|
|
4834
4964
|
let st;
|
|
4835
4965
|
try {
|
|
@@ -4859,7 +4989,7 @@ function runScan(projectRoot, opts, existing) {
|
|
|
4859
4989
|
const ext = extname2(relPath);
|
|
4860
4990
|
const scanner = scannerForExt(ext);
|
|
4861
4991
|
if (!scanner) continue;
|
|
4862
|
-
const abs =
|
|
4992
|
+
const abs = join7(projectRoot, relPath);
|
|
4863
4993
|
let st;
|
|
4864
4994
|
try {
|
|
4865
4995
|
st = statSync3(abs);
|
|
@@ -4875,7 +5005,7 @@ function runScan(projectRoot, opts, existing) {
|
|
|
4875
5005
|
}
|
|
4876
5006
|
let content;
|
|
4877
5007
|
try {
|
|
4878
|
-
content =
|
|
5008
|
+
content = readFileSync13(abs, "utf8");
|
|
4879
5009
|
} catch {
|
|
4880
5010
|
continue;
|
|
4881
5011
|
}
|
|
@@ -5009,8 +5139,8 @@ var init_scanner = __esm({
|
|
|
5009
5139
|
});
|
|
5010
5140
|
|
|
5011
5141
|
// src/server/import/detect.ts
|
|
5012
|
-
import { existsSync as
|
|
5013
|
-
import { join as
|
|
5142
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync14, statSync as statSync4 } from "fs";
|
|
5143
|
+
import { join as join8 } from "path";
|
|
5014
5144
|
function safeIsDir(p) {
|
|
5015
5145
|
try {
|
|
5016
5146
|
return statSync4(p).isDirectory();
|
|
@@ -5019,7 +5149,7 @@ function safeIsDir(p) {
|
|
|
5019
5149
|
}
|
|
5020
5150
|
}
|
|
5021
5151
|
function listDirs(dir) {
|
|
5022
|
-
return readdirSync4(dir).filter((e) => safeIsDir(
|
|
5152
|
+
return readdirSync4(dir).filter((e) => safeIsDir(join8(dir, e)));
|
|
5023
5153
|
}
|
|
5024
5154
|
function fileCount(dir) {
|
|
5025
5155
|
try {
|
|
@@ -5033,23 +5163,23 @@ function pickSource(locales, sizeOf) {
|
|
|
5033
5163
|
return [...locales].sort((a, b) => sizeOf(b) - sizeOf(a) || a.localeCompare(b))[0] ?? "en";
|
|
5034
5164
|
}
|
|
5035
5165
|
function detectLaravel(root) {
|
|
5036
|
-
const localeRoot = [
|
|
5166
|
+
const localeRoot = [join8(root, "resources", "lang"), join8(root, "lang")].find(safeIsDir);
|
|
5037
5167
|
if (!localeRoot) return null;
|
|
5038
5168
|
const locales = listDirs(localeRoot).filter((d) => LOCALE_RE.test(d));
|
|
5039
5169
|
if (locales.length === 0) return null;
|
|
5040
|
-
const sourceLocale = pickSource(locales, (loc) => fileCount(
|
|
5170
|
+
const sourceLocale = pickSource(locales, (loc) => fileCount(join8(localeRoot, loc)));
|
|
5041
5171
|
return { format: "laravel-php", localeRoot, locales, sourceLocale };
|
|
5042
5172
|
}
|
|
5043
5173
|
function detectVue(root, forced = false) {
|
|
5044
5174
|
for (const rel of VUE_DIR_CANDIDATES) {
|
|
5045
|
-
const localeRoot =
|
|
5175
|
+
const localeRoot = join8(root, rel);
|
|
5046
5176
|
if (!safeIsDir(localeRoot)) continue;
|
|
5047
5177
|
const locales = readdirSync4(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
5048
5178
|
const enough = locales.length >= 2 || locales.length === 1 && (forced || locales[0] === "en" || locales[0].startsWith("en-") || locales[0].startsWith("en_"));
|
|
5049
5179
|
if (enough) {
|
|
5050
5180
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
5051
5181
|
try {
|
|
5052
|
-
return statSync4(
|
|
5182
|
+
return statSync4(join8(localeRoot, `${loc}.json`)).size;
|
|
5053
5183
|
} catch {
|
|
5054
5184
|
return 0;
|
|
5055
5185
|
}
|
|
@@ -5060,9 +5190,9 @@ function detectVue(root, forced = false) {
|
|
|
5060
5190
|
return null;
|
|
5061
5191
|
}
|
|
5062
5192
|
function hasNextIntlSignal(root) {
|
|
5063
|
-
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) =>
|
|
5193
|
+
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) => existsSync12(join8(root, rel)))) return true;
|
|
5064
5194
|
try {
|
|
5065
|
-
const pkg = JSON.parse(
|
|
5195
|
+
const pkg = JSON.parse(readFileSync14(join8(root, "package.json"), "utf8"));
|
|
5066
5196
|
if (pkg.dependencies?.["next-intl"] || pkg.devDependencies?.["next-intl"]) return true;
|
|
5067
5197
|
} catch {
|
|
5068
5198
|
}
|
|
@@ -5071,7 +5201,7 @@ function hasNextIntlSignal(root) {
|
|
|
5071
5201
|
function nextIntlDefaultLocale(root) {
|
|
5072
5202
|
for (const rel of NEXT_INTL_ROUTING_CANDIDATES) {
|
|
5073
5203
|
try {
|
|
5074
|
-
const m =
|
|
5204
|
+
const m = readFileSync14(join8(root, rel), "utf8").match(/defaultLocale\s*:\s*['"]([^'"]+)['"]/);
|
|
5075
5205
|
if (m) return m[1];
|
|
5076
5206
|
} catch {
|
|
5077
5207
|
}
|
|
@@ -5081,14 +5211,14 @@ function nextIntlDefaultLocale(root) {
|
|
|
5081
5211
|
function detectNextIntl(root, forced = false) {
|
|
5082
5212
|
if (!forced && !hasNextIntlSignal(root)) return null;
|
|
5083
5213
|
for (const rel of NEXT_INTL_DIR_CANDIDATES) {
|
|
5084
|
-
const localeRoot =
|
|
5214
|
+
const localeRoot = join8(root, rel);
|
|
5085
5215
|
if (!safeIsDir(localeRoot)) continue;
|
|
5086
5216
|
const locales = readdirSync4(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
5087
5217
|
if (locales.length === 0) continue;
|
|
5088
5218
|
const def = nextIntlDefaultLocale(root);
|
|
5089
5219
|
const sourceLocale = def && locales.includes(def) ? def : pickSource(locales, (loc) => {
|
|
5090
5220
|
try {
|
|
5091
|
-
return statSync4(
|
|
5221
|
+
return statSync4(join8(localeRoot, `${loc}.json`)).size;
|
|
5092
5222
|
} catch {
|
|
5093
5223
|
return 0;
|
|
5094
5224
|
}
|
|
@@ -5099,7 +5229,7 @@ function detectNextIntl(root, forced = false) {
|
|
|
5099
5229
|
}
|
|
5100
5230
|
function detectArb(root) {
|
|
5101
5231
|
for (const rel of ["lib/l10n", "l10n", "lib/src/l10n"]) {
|
|
5102
|
-
const localeRoot =
|
|
5232
|
+
const localeRoot = join8(root, rel);
|
|
5103
5233
|
if (!safeIsDir(localeRoot)) continue;
|
|
5104
5234
|
const locales = readdirSync4(localeRoot).map((f) => f.match(/^(?:app_)?(.+)\.arb$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l));
|
|
5105
5235
|
if (locales.length >= 1) {
|
|
@@ -5109,10 +5239,10 @@ function detectArb(root) {
|
|
|
5109
5239
|
return null;
|
|
5110
5240
|
}
|
|
5111
5241
|
function lprojLocales(dir) {
|
|
5112
|
-
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5242
|
+
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync12(join8(dir, `${l}.lproj`, "Localizable.strings")));
|
|
5113
5243
|
}
|
|
5114
5244
|
function detectApple(root) {
|
|
5115
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5245
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5116
5246
|
let best = null;
|
|
5117
5247
|
for (const dir of candidates) {
|
|
5118
5248
|
const locales = lprojLocales(dir);
|
|
@@ -5124,7 +5254,7 @@ function detectApple(root) {
|
|
|
5124
5254
|
locales,
|
|
5125
5255
|
sourceLocale: pickSource(locales, (loc) => {
|
|
5126
5256
|
try {
|
|
5127
|
-
return statSync4(
|
|
5257
|
+
return statSync4(join8(dir, `${loc}.lproj`, "Localizable.strings")).size;
|
|
5128
5258
|
} catch {
|
|
5129
5259
|
return 0;
|
|
5130
5260
|
}
|
|
@@ -5136,7 +5266,7 @@ function detectApple(root) {
|
|
|
5136
5266
|
}
|
|
5137
5267
|
function detectAngularXliff(root) {
|
|
5138
5268
|
for (const rel of ANGULAR_DIR_CANDIDATES) {
|
|
5139
|
-
const localeRoot = rel === "." ? root :
|
|
5269
|
+
const localeRoot = rel === "." ? root : join8(root, rel);
|
|
5140
5270
|
if (!safeIsDir(localeRoot)) continue;
|
|
5141
5271
|
const files = readdirSync4(localeRoot).filter((f) => /^messages(\..+)?\.xlf$/.test(f)).sort();
|
|
5142
5272
|
if (files.length === 0) continue;
|
|
@@ -5144,7 +5274,7 @@ function detectAngularXliff(root) {
|
|
|
5144
5274
|
const attrFile = files.includes("messages.xlf") ? "messages.xlf" : files[0];
|
|
5145
5275
|
let sourceLocale;
|
|
5146
5276
|
try {
|
|
5147
|
-
sourceLocale =
|
|
5277
|
+
sourceLocale = readFileSync14(join8(localeRoot, attrFile), "utf8").match(/source-language="([^"]+)"/)?.[1];
|
|
5148
5278
|
} catch {
|
|
5149
5279
|
}
|
|
5150
5280
|
if (!sourceLocale && locales.length === 0) continue;
|
|
@@ -5155,14 +5285,14 @@ function detectAngularXliff(root) {
|
|
|
5155
5285
|
return null;
|
|
5156
5286
|
}
|
|
5157
5287
|
function detectRails(root) {
|
|
5158
|
-
const localeRoot =
|
|
5288
|
+
const localeRoot = join8(root, "config", "locales");
|
|
5159
5289
|
if (!safeIsDir(localeRoot)) return null;
|
|
5160
5290
|
const locales = [];
|
|
5161
5291
|
for (const file of readdirSync4(localeRoot).sort()) {
|
|
5162
5292
|
if (!/\.ya?ml$/.test(file)) continue;
|
|
5163
5293
|
let text;
|
|
5164
5294
|
try {
|
|
5165
|
-
text =
|
|
5295
|
+
text = readFileSync14(join8(localeRoot, file), "utf8");
|
|
5166
5296
|
} catch {
|
|
5167
5297
|
continue;
|
|
5168
5298
|
}
|
|
@@ -5176,15 +5306,15 @@ function detectRails(root) {
|
|
|
5176
5306
|
}
|
|
5177
5307
|
function detectI18next(root) {
|
|
5178
5308
|
for (const rel of I18NEXT_DIR_CANDIDATES) {
|
|
5179
|
-
const localeRoot =
|
|
5309
|
+
const localeRoot = join8(root, rel);
|
|
5180
5310
|
if (!safeIsDir(localeRoot)) continue;
|
|
5181
5311
|
const locales = listDirs(localeRoot).filter(
|
|
5182
|
-
(d) => LOCALE_RE.test(d) && readdirSync4(
|
|
5312
|
+
(d) => LOCALE_RE.test(d) && readdirSync4(join8(localeRoot, d)).some((f) => f.endsWith(".json"))
|
|
5183
5313
|
);
|
|
5184
5314
|
if (locales.length === 0) continue;
|
|
5185
5315
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
5186
5316
|
try {
|
|
5187
|
-
return readdirSync4(
|
|
5317
|
+
return readdirSync4(join8(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync4(join8(localeRoot, loc, f)).size, 0);
|
|
5188
5318
|
} catch {
|
|
5189
5319
|
return 0;
|
|
5190
5320
|
}
|
|
@@ -5201,8 +5331,8 @@ function gettextLocales(dir) {
|
|
|
5201
5331
|
if (!locales.includes(flat)) locales.push(flat);
|
|
5202
5332
|
continue;
|
|
5203
5333
|
}
|
|
5204
|
-
if (!LOCALE_RE.test(entry) || !safeIsDir(
|
|
5205
|
-
const sub =
|
|
5334
|
+
if (!LOCALE_RE.test(entry) || !safeIsDir(join8(dir, entry))) continue;
|
|
5335
|
+
const sub = join8(dir, entry);
|
|
5206
5336
|
const hasPo = (d) => {
|
|
5207
5337
|
try {
|
|
5208
5338
|
return readdirSync4(d).some((f) => f.endsWith(".po"));
|
|
@@ -5210,7 +5340,7 @@ function gettextLocales(dir) {
|
|
|
5210
5340
|
return false;
|
|
5211
5341
|
}
|
|
5212
5342
|
};
|
|
5213
|
-
if (hasPo(
|
|
5343
|
+
if (hasPo(join8(sub, "LC_MESSAGES")) || hasPo(sub)) {
|
|
5214
5344
|
if (!locales.includes(entry)) locales.push(entry);
|
|
5215
5345
|
}
|
|
5216
5346
|
}
|
|
@@ -5218,7 +5348,7 @@ function gettextLocales(dir) {
|
|
|
5218
5348
|
}
|
|
5219
5349
|
function detectGettext(root) {
|
|
5220
5350
|
for (const rel of GETTEXT_DIR_CANDIDATES) {
|
|
5221
|
-
const localeRoot =
|
|
5351
|
+
const localeRoot = join8(root, rel);
|
|
5222
5352
|
if (!safeIsDir(localeRoot)) continue;
|
|
5223
5353
|
const locales = gettextLocales(localeRoot);
|
|
5224
5354
|
if (locales.length === 0) continue;
|
|
@@ -5227,10 +5357,10 @@ function detectGettext(root) {
|
|
|
5227
5357
|
return null;
|
|
5228
5358
|
}
|
|
5229
5359
|
function detectAppleStringsdict(root) {
|
|
5230
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5360
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5231
5361
|
let best = null;
|
|
5232
5362
|
for (const dir of candidates) {
|
|
5233
|
-
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5363
|
+
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
5364
|
if (locales.length === 0) continue;
|
|
5235
5365
|
if (!best || locales.length > best.locales.length) {
|
|
5236
5366
|
best = { format: "apple-stringsdict", localeRoot: dir, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
@@ -5239,7 +5369,7 @@ function detectAppleStringsdict(root) {
|
|
|
5239
5369
|
return best;
|
|
5240
5370
|
}
|
|
5241
5371
|
function detect(root, formatOverride) {
|
|
5242
|
-
if (!
|
|
5372
|
+
if (!existsSync12(root)) return null;
|
|
5243
5373
|
if (formatOverride) {
|
|
5244
5374
|
const fn = BY_FORMAT[formatOverride];
|
|
5245
5375
|
if (!fn) throw new Error(`Unknown format: ${formatOverride}`);
|
|
@@ -5317,8 +5447,8 @@ var init_flatten = __esm({
|
|
|
5317
5447
|
});
|
|
5318
5448
|
|
|
5319
5449
|
// src/server/import/parsers/vue-i18n-json.ts
|
|
5320
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
5321
|
-
import { join as
|
|
5450
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
|
|
5451
|
+
import { join as join9 } from "path";
|
|
5322
5452
|
function fromVueI18n(value) {
|
|
5323
5453
|
return value.replace(/\{'([^']*)'\}/g, "'$1'");
|
|
5324
5454
|
}
|
|
@@ -5341,7 +5471,7 @@ var init_vue_i18n_json2 = __esm({
|
|
|
5341
5471
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5342
5472
|
let data;
|
|
5343
5473
|
try {
|
|
5344
|
-
data = JSON.parse(
|
|
5474
|
+
data = JSON.parse(readFileSync15(join9(localeRoot, file), "utf8"));
|
|
5345
5475
|
} catch (e) {
|
|
5346
5476
|
warnings.push(`vue-i18n-json: failed to parse ${file}: ${e.message}`);
|
|
5347
5477
|
continue;
|
|
@@ -5358,8 +5488,8 @@ var init_vue_i18n_json2 = __esm({
|
|
|
5358
5488
|
});
|
|
5359
5489
|
|
|
5360
5490
|
// src/server/import/parsers/next-intl-json.ts
|
|
5361
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
5362
|
-
import { join as
|
|
5491
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
|
|
5492
|
+
import { join as join10 } from "path";
|
|
5363
5493
|
var LOCALE_RE3, nextIntlJson2;
|
|
5364
5494
|
var init_next_intl_json2 = __esm({
|
|
5365
5495
|
"src/server/import/parsers/next-intl-json.ts"() {
|
|
@@ -5379,7 +5509,7 @@ var init_next_intl_json2 = __esm({
|
|
|
5379
5509
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5380
5510
|
let data;
|
|
5381
5511
|
try {
|
|
5382
|
-
data = JSON.parse(
|
|
5512
|
+
data = JSON.parse(readFileSync16(join10(localeRoot, file), "utf8"));
|
|
5383
5513
|
} catch (e) {
|
|
5384
5514
|
warnings.push(`next-intl-json: failed to parse ${file}: ${e.message}`);
|
|
5385
5515
|
continue;
|
|
@@ -5413,16 +5543,16 @@ var init_placeholders2 = __esm({
|
|
|
5413
5543
|
|
|
5414
5544
|
// src/server/import/parsers/laravel-php.ts
|
|
5415
5545
|
import { readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
5416
|
-
import { join as
|
|
5546
|
+
import { join as join11, relative as relative2 } from "path";
|
|
5417
5547
|
import { execFileSync } from "child_process";
|
|
5418
5548
|
function listDirs2(dir) {
|
|
5419
|
-
return readdirSync7(dir).filter((e) => statSync5(
|
|
5549
|
+
return readdirSync7(dir).filter((e) => statSync5(join11(dir, e)).isDirectory());
|
|
5420
5550
|
}
|
|
5421
5551
|
function listPhpFiles(dir) {
|
|
5422
5552
|
const out = [];
|
|
5423
5553
|
const walk = (d) => {
|
|
5424
5554
|
for (const e of readdirSync7(d)) {
|
|
5425
|
-
const full =
|
|
5555
|
+
const full = join11(d, e);
|
|
5426
5556
|
if (statSync5(full).isDirectory()) walk(full);
|
|
5427
5557
|
else if (e.endsWith(".php")) out.push(full);
|
|
5428
5558
|
}
|
|
@@ -5465,7 +5595,7 @@ var init_laravel_php2 = __esm({
|
|
|
5465
5595
|
for (const locale of listDirs2(localeRoot).sort()) {
|
|
5466
5596
|
if (locale === "vendor") continue;
|
|
5467
5597
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5468
|
-
const localeDir =
|
|
5598
|
+
const localeDir = join11(localeRoot, locale);
|
|
5469
5599
|
locales.push(locale);
|
|
5470
5600
|
for (const file of listPhpFiles(localeDir)) {
|
|
5471
5601
|
const group = relative2(localeDir, file).replace(/\\/g, "/").replace(/\.php$/, "");
|
|
@@ -5490,8 +5620,8 @@ var init_laravel_php2 = __esm({
|
|
|
5490
5620
|
});
|
|
5491
5621
|
|
|
5492
5622
|
// src/server/import/parsers/flutter-arb.ts
|
|
5493
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
5494
|
-
import { join as
|
|
5623
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
|
|
5624
|
+
import { join as join12 } from "path";
|
|
5495
5625
|
function localeFromArbName(file) {
|
|
5496
5626
|
const m = file.match(/^(.+)\.arb$/);
|
|
5497
5627
|
if (!m) return null;
|
|
@@ -5531,7 +5661,7 @@ var init_flutter_arb2 = __esm({
|
|
|
5531
5661
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5532
5662
|
let data;
|
|
5533
5663
|
try {
|
|
5534
|
-
data = JSON.parse(
|
|
5664
|
+
data = JSON.parse(readFileSync17(join12(localeRoot, file), "utf8"));
|
|
5535
5665
|
} catch (e) {
|
|
5536
5666
|
warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
|
|
5537
5667
|
continue;
|
|
@@ -5558,8 +5688,8 @@ var init_flutter_arb2 = __esm({
|
|
|
5558
5688
|
});
|
|
5559
5689
|
|
|
5560
5690
|
// src/server/import/parsers/apple-strings.ts
|
|
5561
|
-
import { readdirSync as readdirSync9, readFileSync as
|
|
5562
|
-
import { join as
|
|
5691
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync18, statSync as statSync6 } from "fs";
|
|
5692
|
+
import { join as join13 } from "path";
|
|
5563
5693
|
function localeFromLproj(dir) {
|
|
5564
5694
|
const m = dir.match(/^(.+)\.lproj$/);
|
|
5565
5695
|
if (!m) return null;
|
|
@@ -5679,16 +5809,16 @@ var init_apple_strings2 = __esm({
|
|
|
5679
5809
|
const locale = localeFromLproj(dir);
|
|
5680
5810
|
if (!locale) continue;
|
|
5681
5811
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5682
|
-
const file =
|
|
5812
|
+
const file = join13(localeRoot, dir, TABLE);
|
|
5683
5813
|
let text;
|
|
5684
5814
|
try {
|
|
5685
5815
|
if (!statSync6(file).isFile()) continue;
|
|
5686
|
-
text =
|
|
5816
|
+
text = readFileSync18(file, "utf8");
|
|
5687
5817
|
} catch {
|
|
5688
5818
|
continue;
|
|
5689
5819
|
}
|
|
5690
5820
|
locales.push(locale);
|
|
5691
|
-
const others = readdirSync9(
|
|
5821
|
+
const others = readdirSync9(join13(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
|
|
5692
5822
|
if (others.length) {
|
|
5693
5823
|
warnings.push(`apple-strings: ${dir} has other .strings tables (${others.join(", ")}); only ${TABLE} is imported`);
|
|
5694
5824
|
}
|
|
@@ -5703,8 +5833,8 @@ var init_apple_strings2 = __esm({
|
|
|
5703
5833
|
});
|
|
5704
5834
|
|
|
5705
5835
|
// src/server/import/parsers/angular-xliff.ts
|
|
5706
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
5707
|
-
import { join as
|
|
5836
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync19 } from "fs";
|
|
5837
|
+
import { join as join14 } from "path";
|
|
5708
5838
|
function decodeEntities(s) {
|
|
5709
5839
|
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
5840
|
}
|
|
@@ -5775,7 +5905,7 @@ var init_angular_xliff2 = __esm({
|
|
|
5775
5905
|
if (fnameLocale !== void 0 && !LOCALE_RE6.test(fnameLocale)) continue;
|
|
5776
5906
|
let xml;
|
|
5777
5907
|
try {
|
|
5778
|
-
xml =
|
|
5908
|
+
xml = readFileSync19(join14(localeRoot, file), "utf8");
|
|
5779
5909
|
} catch (e) {
|
|
5780
5910
|
warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
|
|
5781
5911
|
continue;
|
|
@@ -5822,8 +5952,8 @@ var init_angular_xliff2 = __esm({
|
|
|
5822
5952
|
});
|
|
5823
5953
|
|
|
5824
5954
|
// src/server/import/parsers/gettext-po.ts
|
|
5825
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
5826
|
-
import { join as
|
|
5955
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync20 } from "fs";
|
|
5956
|
+
import { join as join15 } from "path";
|
|
5827
5957
|
function unescapePo(s) {
|
|
5828
5958
|
return s.replace(
|
|
5829
5959
|
/\\([\\"ntr])/g,
|
|
@@ -5912,17 +6042,17 @@ function discoverPoFiles(root) {
|
|
|
5912
6042
|
for (const e of entries) {
|
|
5913
6043
|
if (e.isFile() && e.name.endsWith(".po")) {
|
|
5914
6044
|
const base = e.name.slice(0, -3);
|
|
5915
|
-
found.push({ path:
|
|
6045
|
+
found.push({ path: join15(root, e.name), rel: e.name, locale: LOCALE_RE7.test(base) ? base : null });
|
|
5916
6046
|
} else if (e.isDirectory() && LOCALE_RE7.test(e.name)) {
|
|
5917
|
-
for (const sub of [
|
|
6047
|
+
for (const sub of [join15(e.name, "LC_MESSAGES"), e.name]) {
|
|
5918
6048
|
let names;
|
|
5919
6049
|
try {
|
|
5920
|
-
names = readdirSync11(
|
|
6050
|
+
names = readdirSync11(join15(root, sub)).sort();
|
|
5921
6051
|
} catch {
|
|
5922
6052
|
continue;
|
|
5923
6053
|
}
|
|
5924
6054
|
for (const f of names) {
|
|
5925
|
-
if (f.endsWith(".po")) found.push({ path:
|
|
6055
|
+
if (f.endsWith(".po")) found.push({ path: join15(root, sub, f), rel: join15(sub, f), locale: e.name });
|
|
5926
6056
|
}
|
|
5927
6057
|
}
|
|
5928
6058
|
}
|
|
@@ -5946,7 +6076,7 @@ var init_gettext_po2 = __esm({
|
|
|
5946
6076
|
for (const file of discoverPoFiles(localeRoot)) {
|
|
5947
6077
|
let entries;
|
|
5948
6078
|
try {
|
|
5949
|
-
entries = parseEntries(
|
|
6079
|
+
entries = parseEntries(readFileSync20(file.path, "utf8"));
|
|
5950
6080
|
} catch (e) {
|
|
5951
6081
|
warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
|
|
5952
6082
|
continue;
|
|
@@ -5993,8 +6123,8 @@ var init_gettext_po2 = __esm({
|
|
|
5993
6123
|
});
|
|
5994
6124
|
|
|
5995
6125
|
// src/server/import/parsers/i18next-json.ts
|
|
5996
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
5997
|
-
import { join as
|
|
6126
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
|
|
6127
|
+
import { join as join16 } from "path";
|
|
5998
6128
|
function safeIsDir2(p) {
|
|
5999
6129
|
try {
|
|
6000
6130
|
return statSync7(p).isDirectory();
|
|
@@ -6009,7 +6139,7 @@ function fromI18next(value) {
|
|
|
6009
6139
|
function ingestFile(path, label, prefix, locale, keys, warnings) {
|
|
6010
6140
|
let data;
|
|
6011
6141
|
try {
|
|
6012
|
-
data = JSON.parse(
|
|
6142
|
+
data = JSON.parse(readFileSync21(path, "utf8"));
|
|
6013
6143
|
} catch (e) {
|
|
6014
6144
|
warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
|
|
6015
6145
|
return false;
|
|
@@ -6062,7 +6192,7 @@ var init_i18next_json2 = __esm({
|
|
|
6062
6192
|
const keys = {};
|
|
6063
6193
|
const locales = [];
|
|
6064
6194
|
for (const entry of readdirSync12(localeRoot).sort()) {
|
|
6065
|
-
const full =
|
|
6195
|
+
const full = join16(localeRoot, entry);
|
|
6066
6196
|
if (safeIsDir2(full)) {
|
|
6067
6197
|
if (!LOCALE_RE8.test(entry)) continue;
|
|
6068
6198
|
if (opts?.locales && !opts.locales.includes(entry)) continue;
|
|
@@ -6071,7 +6201,7 @@ var init_i18next_json2 = __esm({
|
|
|
6071
6201
|
if (!file.endsWith(".json")) continue;
|
|
6072
6202
|
const ns = file.slice(0, -".json".length);
|
|
6073
6203
|
const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
|
|
6074
|
-
if (ingestFile(
|
|
6204
|
+
if (ingestFile(join16(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
|
|
6075
6205
|
}
|
|
6076
6206
|
if (any && !locales.includes(entry)) locales.push(entry);
|
|
6077
6207
|
} else if (entry.endsWith(".json")) {
|
|
@@ -6090,8 +6220,8 @@ var init_i18next_json2 = __esm({
|
|
|
6090
6220
|
});
|
|
6091
6221
|
|
|
6092
6222
|
// src/server/import/parsers/rails-yaml.ts
|
|
6093
|
-
import { readdirSync as readdirSync13, readFileSync as
|
|
6094
|
-
import { join as
|
|
6223
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync22 } from "fs";
|
|
6224
|
+
import { join as join17 } from "path";
|
|
6095
6225
|
function makeNode() {
|
|
6096
6226
|
return /* @__PURE__ */ Object.create(null);
|
|
6097
6227
|
}
|
|
@@ -6315,7 +6445,7 @@ var init_rails_yaml2 = __esm({
|
|
|
6315
6445
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
6316
6446
|
let text;
|
|
6317
6447
|
try {
|
|
6318
|
-
text =
|
|
6448
|
+
text = readFileSync22(join17(localeRoot, file), "utf8");
|
|
6319
6449
|
} catch (e) {
|
|
6320
6450
|
warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
|
|
6321
6451
|
continue;
|
|
@@ -6338,8 +6468,8 @@ var init_rails_yaml2 = __esm({
|
|
|
6338
6468
|
});
|
|
6339
6469
|
|
|
6340
6470
|
// src/server/import/parsers/apple-stringsdict.ts
|
|
6341
|
-
import { readdirSync as readdirSync14, readFileSync as
|
|
6342
|
-
import { join as
|
|
6471
|
+
import { readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
|
|
6472
|
+
import { join as join18 } from "path";
|
|
6343
6473
|
function localeFromLproj2(dir) {
|
|
6344
6474
|
const m = dir.match(/^(.+)\.lproj$/);
|
|
6345
6475
|
if (!m) return null;
|
|
@@ -6499,16 +6629,16 @@ var init_apple_stringsdict2 = __esm({
|
|
|
6499
6629
|
const locale = localeFromLproj2(dir);
|
|
6500
6630
|
if (!locale) continue;
|
|
6501
6631
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
6502
|
-
const file =
|
|
6632
|
+
const file = join18(localeRoot, dir, TABLE2);
|
|
6503
6633
|
let text;
|
|
6504
6634
|
try {
|
|
6505
6635
|
if (!statSync8(file).isFile()) continue;
|
|
6506
|
-
text =
|
|
6636
|
+
text = readFileSync23(file, "utf8");
|
|
6507
6637
|
} catch {
|
|
6508
6638
|
continue;
|
|
6509
6639
|
}
|
|
6510
6640
|
locales.push(locale);
|
|
6511
|
-
const others = readdirSync14(
|
|
6641
|
+
const others = readdirSync14(join18(localeRoot, dir)).filter(
|
|
6512
6642
|
(f) => f.endsWith(".stringsdict") && f !== TABLE2
|
|
6513
6643
|
);
|
|
6514
6644
|
if (others.length) {
|
|
@@ -6982,7 +7112,7 @@ var init_run2 = __esm({
|
|
|
6982
7112
|
});
|
|
6983
7113
|
|
|
6984
7114
|
// src/server/lint/outputs.ts
|
|
6985
|
-
import { readFileSync as
|
|
7115
|
+
import { readFileSync as readFileSync24, existsSync as existsSync13 } from "fs";
|
|
6986
7116
|
import { resolve as resolve8 } from "path";
|
|
6987
7117
|
function checkOutputs(state, root) {
|
|
6988
7118
|
const out = [];
|
|
@@ -6990,7 +7120,7 @@ function checkOutputs(state, root) {
|
|
|
6990
7120
|
const result = getAdapter(output.adapter).export(state, output);
|
|
6991
7121
|
for (const file of result.files) {
|
|
6992
7122
|
const abs = resolve8(root, file.path);
|
|
6993
|
-
const current =
|
|
7123
|
+
const current = existsSync13(abs) ? readFileSync24(abs, "utf8") : null;
|
|
6994
7124
|
if (current === null) {
|
|
6995
7125
|
out.push({ ruleId: "output-stale", key: file.path, locale: "", severity: "error", message: "output file is missing; run `glotfile export`" });
|
|
6996
7126
|
} else if (current !== file.contents) {
|
|
@@ -7493,12 +7623,12 @@ var init_explain_error = __esm({
|
|
|
7493
7623
|
});
|
|
7494
7624
|
|
|
7495
7625
|
// src/server/ui-prefs.ts
|
|
7496
|
-
import { readFileSync as
|
|
7626
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
7497
7627
|
import { homedir as homedir2 } from "os";
|
|
7498
|
-
import { join as
|
|
7628
|
+
import { join as join19 } from "path";
|
|
7499
7629
|
function readJson2(path) {
|
|
7500
7630
|
try {
|
|
7501
|
-
const parsed = JSON.parse(
|
|
7631
|
+
const parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
7502
7632
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
7503
7633
|
} catch {
|
|
7504
7634
|
return {};
|
|
@@ -7523,7 +7653,7 @@ var init_ui_prefs = __esm({
|
|
|
7523
7653
|
THEMES = ["system", "light", "dark"];
|
|
7524
7654
|
isThemeMode = (v) => THEMES.includes(v);
|
|
7525
7655
|
isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
7526
|
-
defaultUiPrefsPath = () =>
|
|
7656
|
+
defaultUiPrefsPath = () => join19(homedir2(), ".glotfile", "ui.json");
|
|
7527
7657
|
DEFAULTS = { theme: "system" };
|
|
7528
7658
|
}
|
|
7529
7659
|
});
|
|
@@ -7557,7 +7687,7 @@ var init_events = __esm({
|
|
|
7557
7687
|
|
|
7558
7688
|
// src/server/watch.ts
|
|
7559
7689
|
import { statSync as statSync9, readdirSync as readdirSync15 } from "fs";
|
|
7560
|
-
import { join as
|
|
7690
|
+
import { join as join20 } from "path";
|
|
7561
7691
|
import { createHash as createHash2 } from "crypto";
|
|
7562
7692
|
function hashState(state) {
|
|
7563
7693
|
return createHash2("sha1").update(serializeJson(state, state.config.format)).digest("hex");
|
|
@@ -7573,15 +7703,15 @@ function signature(statePath) {
|
|
|
7573
7703
|
const parts = [];
|
|
7574
7704
|
for (const rel of ["config.json", "keys.json"]) {
|
|
7575
7705
|
try {
|
|
7576
|
-
const s = statSync9(
|
|
7706
|
+
const s = statSync9(join20(dir, rel));
|
|
7577
7707
|
parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
|
|
7578
7708
|
} catch {
|
|
7579
7709
|
}
|
|
7580
7710
|
}
|
|
7581
7711
|
try {
|
|
7582
|
-
for (const name of readdirSync15(
|
|
7712
|
+
for (const name of readdirSync15(join20(dir, "locales")).sort()) {
|
|
7583
7713
|
if (!name.endsWith(".json")) continue;
|
|
7584
|
-
const s = statSync9(
|
|
7714
|
+
const s = statSync9(join20(dir, "locales", name));
|
|
7585
7715
|
parts.push(`${name}:${s.size}:${s.mtimeMs}`);
|
|
7586
7716
|
}
|
|
7587
7717
|
} catch {
|
|
@@ -7660,13 +7790,13 @@ var init_watch = __esm({
|
|
|
7660
7790
|
// src/server/api.ts
|
|
7661
7791
|
import { Hono } from "hono";
|
|
7662
7792
|
import { streamSSE } from "hono/streaming";
|
|
7663
|
-
import { readFileSync as
|
|
7793
|
+
import { readFileSync as readFileSync26, existsSync as existsSync14, readdirSync as readdirSync16, statSync as statSync10, rmSync as rmSync7 } from "fs";
|
|
7664
7794
|
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
7665
7795
|
function projectName(root) {
|
|
7666
7796
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
7667
|
-
if (
|
|
7797
|
+
if (existsSync14(nameFile)) {
|
|
7668
7798
|
try {
|
|
7669
|
-
const name =
|
|
7799
|
+
const name = readFileSync26(nameFile, "utf8").trim();
|
|
7670
7800
|
if (name) return name;
|
|
7671
7801
|
} catch {
|
|
7672
7802
|
}
|
|
@@ -7880,7 +8010,7 @@ function createApi(deps) {
|
|
|
7880
8010
|
if (name.startsWith(".") || name === "node_modules") continue;
|
|
7881
8011
|
const abs = resolve9(dir, name);
|
|
7882
8012
|
let filePath = null;
|
|
7883
|
-
if ((name === "glotfile" || name.endsWith(".glotfile")) &&
|
|
8013
|
+
if ((name === "glotfile" || name.endsWith(".glotfile")) && existsSync14(resolve9(abs, "config.json"))) {
|
|
7884
8014
|
filePath = resolve9(dir, `${name}.json`);
|
|
7885
8015
|
} else if (name === "glotfile.json" || name.endsWith(".glotfile.json")) {
|
|
7886
8016
|
filePath = abs;
|
|
@@ -7914,7 +8044,7 @@ function createApi(deps) {
|
|
|
7914
8044
|
const resolved = resolve9(projectRoot, path);
|
|
7915
8045
|
const inside = resolved === projectRoot || resolved.startsWith(projectRoot + sep2);
|
|
7916
8046
|
if (!inside) return c.json({ error: "file is outside the project" }, 400);
|
|
7917
|
-
if (!
|
|
8047
|
+
if (!existsSync14(resolved)) return c.json({ error: "file not found" }, 400);
|
|
7918
8048
|
loadState(resolved);
|
|
7919
8049
|
deps.statePath = resolved;
|
|
7920
8050
|
watcher.retarget(resolved);
|
|
@@ -7976,9 +8106,9 @@ function createApi(deps) {
|
|
|
7976
8106
|
const abs = resolve9(root, screenshot);
|
|
7977
8107
|
const rel = relative4(root, abs);
|
|
7978
8108
|
const seg0 = rel.split(sep2)[0] ?? "";
|
|
7979
|
-
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") &&
|
|
8109
|
+
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") && existsSync14(abs)) {
|
|
7980
8110
|
try {
|
|
7981
|
-
|
|
8111
|
+
rmSync7(abs);
|
|
7982
8112
|
} catch {
|
|
7983
8113
|
}
|
|
7984
8114
|
}
|
|
@@ -8059,7 +8189,6 @@ function createApi(deps) {
|
|
|
8059
8189
|
if (clearContext === true) {
|
|
8060
8190
|
delete entry.context;
|
|
8061
8191
|
delete entry.contextSource;
|
|
8062
|
-
delete entry.contextAt;
|
|
8063
8192
|
}
|
|
8064
8193
|
updated++;
|
|
8065
8194
|
}
|
|
@@ -8307,6 +8436,93 @@ function createApi(deps) {
|
|
|
8307
8436
|
await stream.writeSSE({ event: "done", data: JSON.stringify({ added: added.length, terms: added }) });
|
|
8308
8437
|
});
|
|
8309
8438
|
});
|
|
8439
|
+
app.post("/glossary/suggest/estimate", async (c) => {
|
|
8440
|
+
const body = await c.req.json().catch(() => ({}));
|
|
8441
|
+
const s = load();
|
|
8442
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
8443
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8444
|
+
return c.json(estimateGlossarySuggest(sources, knownTermList(s), aiCfg));
|
|
8445
|
+
});
|
|
8446
|
+
app.get("/glossary/suggest/batch/status", async (c) => {
|
|
8447
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8448
|
+
let supported = false;
|
|
8449
|
+
let provider;
|
|
8450
|
+
try {
|
|
8451
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8452
|
+
supported = supportsBatchComplete(provider);
|
|
8453
|
+
} catch {
|
|
8454
|
+
}
|
|
8455
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
8456
|
+
if (!pending) return c.json({ supported, pending: null });
|
|
8457
|
+
const base = { batchId: pending.batchId, createdAt: pending.createdAt, model: pending.model, total: pending.total };
|
|
8458
|
+
if (!provider || !supportsBatchComplete(provider)) {
|
|
8459
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null } });
|
|
8460
|
+
}
|
|
8461
|
+
try {
|
|
8462
|
+
const status = await provider.translationBatchStatus(pending.batchId);
|
|
8463
|
+
return c.json({ supported, pending: { ...base, status: status.status, counts: status.counts } });
|
|
8464
|
+
} catch (e) {
|
|
8465
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null, error: e.message } });
|
|
8466
|
+
}
|
|
8467
|
+
});
|
|
8468
|
+
app.post("/glossary/suggest/batch", (c) => withTranslateLock(async () => {
|
|
8469
|
+
const body = await c.req.json().catch(() => ({}));
|
|
8470
|
+
const s = load();
|
|
8471
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
8472
|
+
if (!sources.length) return c.json({ error: "No source strings to scan." }, 400);
|
|
8473
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8474
|
+
let provider;
|
|
8475
|
+
try {
|
|
8476
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8477
|
+
} catch (e) {
|
|
8478
|
+
return c.json({ error: e.message }, 400);
|
|
8479
|
+
}
|
|
8480
|
+
if (!supportsBatchComplete(provider)) {
|
|
8481
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
8482
|
+
}
|
|
8483
|
+
const batchSize = aiCfg.contextBatchSize ?? aiCfg.batchSize ?? 10;
|
|
8484
|
+
let pending;
|
|
8485
|
+
try {
|
|
8486
|
+
pending = await submitGlossarySuggestBatch(provider, sources, knownTermList(s), batchSize, aiCfg.model, projectRoot);
|
|
8487
|
+
} catch (e) {
|
|
8488
|
+
return c.json({ error: e.message }, 409);
|
|
8489
|
+
}
|
|
8490
|
+
appendLog(projectRoot, {
|
|
8491
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8492
|
+
kind: "glossary",
|
|
8493
|
+
summary: `Submitted glossary suggestion batch ${pending.batchId} (${pending.total} sources)`,
|
|
8494
|
+
model: aiCfg.model
|
|
8495
|
+
});
|
|
8496
|
+
return c.json({ batchId: pending.batchId, total: pending.total });
|
|
8497
|
+
}));
|
|
8498
|
+
app.post("/glossary/suggest/batch/apply", (c) => withTranslateLock(async () => {
|
|
8499
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
8500
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
8501
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8502
|
+
let provider;
|
|
8503
|
+
try {
|
|
8504
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8505
|
+
} catch (e) {
|
|
8506
|
+
return c.json({ error: e.message }, 400);
|
|
8507
|
+
}
|
|
8508
|
+
if (!supportsBatchComplete(provider)) {
|
|
8509
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
8510
|
+
}
|
|
8511
|
+
const outcome = await applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
|
|
8512
|
+
return c.json(outcome);
|
|
8513
|
+
}));
|
|
8514
|
+
app.post("/glossary/suggest/batch/cancel", async (c) => {
|
|
8515
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
8516
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
8517
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
8518
|
+
try {
|
|
8519
|
+
const provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
8520
|
+
if (supportsBatchComplete(provider)) await provider.cancelTranslationBatch(pending.batchId);
|
|
8521
|
+
} catch {
|
|
8522
|
+
}
|
|
8523
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
8524
|
+
return c.json({ canceled: pending.batchId });
|
|
8525
|
+
});
|
|
8310
8526
|
app.post("/keys/:key/screenshot", async (c) => {
|
|
8311
8527
|
const key = c.req.param("key");
|
|
8312
8528
|
const body = await c.req.parseBody();
|
|
@@ -8860,7 +9076,7 @@ function createApi(deps) {
|
|
|
8860
9076
|
if (signal?.aborted) break;
|
|
8861
9077
|
const batch = raw;
|
|
8862
9078
|
const fresh = load();
|
|
8863
|
-
const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [],
|
|
9079
|
+
const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], body.force === true);
|
|
8864
9080
|
const usage = provider.takeUsage?.();
|
|
8865
9081
|
appendLog(projectRoot, {
|
|
8866
9082
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9026,6 +9242,8 @@ var init_api = __esm({
|
|
|
9026
9242
|
init_pending_batch();
|
|
9027
9243
|
init_context_batch_run();
|
|
9028
9244
|
init_pending_context_batch();
|
|
9245
|
+
init_glossary_batch_run();
|
|
9246
|
+
init_pending_glossary_batch();
|
|
9029
9247
|
init_estimate();
|
|
9030
9248
|
init_pricing();
|
|
9031
9249
|
init_price_fetch();
|
|
@@ -9054,7 +9272,7 @@ __export(server_exports, {
|
|
|
9054
9272
|
import { Hono as Hono2 } from "hono";
|
|
9055
9273
|
import { serve } from "@hono/node-server";
|
|
9056
9274
|
import { fileURLToPath } from "url";
|
|
9057
|
-
import { dirname as dirname4, join as
|
|
9275
|
+
import { dirname as dirname4, join as join21, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
|
|
9058
9276
|
import { readFile, stat } from "fs/promises";
|
|
9059
9277
|
import { createServer } from "net";
|
|
9060
9278
|
import open from "open";
|
|
@@ -9110,7 +9328,7 @@ function buildApp(opts) {
|
|
|
9110
9328
|
const file = await readFileResponse(target);
|
|
9111
9329
|
if (file) return file;
|
|
9112
9330
|
}
|
|
9113
|
-
const index = await readFileResponse(
|
|
9331
|
+
const index = await readFileResponse(join21(root, "index.html"));
|
|
9114
9332
|
if (index) return index;
|
|
9115
9333
|
return c.notFound();
|
|
9116
9334
|
});
|
|
@@ -9180,7 +9398,7 @@ var init_server = __esm({
|
|
|
9180
9398
|
init_scanner();
|
|
9181
9399
|
init_usage();
|
|
9182
9400
|
here = dirname4(fileURLToPath(import.meta.url));
|
|
9183
|
-
DEFAULT_UI_DIR =
|
|
9401
|
+
DEFAULT_UI_DIR = join21(here, "..", "ui");
|
|
9184
9402
|
MIME = {
|
|
9185
9403
|
".html": "text/html; charset=utf-8",
|
|
9186
9404
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -9212,8 +9430,8 @@ var init_server = __esm({
|
|
|
9212
9430
|
// src/server/cli.ts
|
|
9213
9431
|
init_state();
|
|
9214
9432
|
init_stats();
|
|
9215
|
-
import { resolve as resolve11, dirname as dirname5, join as
|
|
9216
|
-
import { readFileSync as
|
|
9433
|
+
import { resolve as resolve11, dirname as dirname5, join as join22, basename as basename2 } from "path";
|
|
9434
|
+
import { readFileSync as readFileSync27, existsSync as existsSync15, mkdirSync as mkdirSync7, cpSync } from "fs";
|
|
9217
9435
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9218
9436
|
|
|
9219
9437
|
// src/server/agent-cli.ts
|
|
@@ -9344,6 +9562,8 @@ init_batch_run();
|
|
|
9344
9562
|
init_pending_batch();
|
|
9345
9563
|
init_context_batch_run();
|
|
9346
9564
|
init_pending_context_batch();
|
|
9565
|
+
init_glossary_batch_run();
|
|
9566
|
+
init_pending_glossary_batch();
|
|
9347
9567
|
init_estimate();
|
|
9348
9568
|
init_glossary_suggest();
|
|
9349
9569
|
init_pricing();
|
|
@@ -9604,7 +9824,7 @@ function translateSelection(args) {
|
|
|
9604
9824
|
}
|
|
9605
9825
|
function readStdin() {
|
|
9606
9826
|
try {
|
|
9607
|
-
return
|
|
9827
|
+
return readFileSync27(0, "utf8");
|
|
9608
9828
|
} catch {
|
|
9609
9829
|
return "";
|
|
9610
9830
|
}
|
|
@@ -9777,13 +9997,15 @@ async function runBatch(args) {
|
|
|
9777
9997
|
const projectRoot = dirname5(resolve11(args.statePath));
|
|
9778
9998
|
const pending = loadPendingBatch(projectRoot);
|
|
9779
9999
|
const ctxPending = loadPendingContextBatch(projectRoot);
|
|
9780
|
-
|
|
9781
|
-
|
|
10000
|
+
const glossPending = loadPendingGlossaryBatch(projectRoot);
|
|
10001
|
+
if (!pending && !ctxPending && !glossPending) {
|
|
10002
|
+
console.log("No pending batch. Start one with `glotfile translate --batch`, `glotfile build-context --batch`, or `glotfile suggest-glossary --batch`.");
|
|
9782
10003
|
return;
|
|
9783
10004
|
}
|
|
9784
10005
|
const action = args.batchAction ?? "status";
|
|
9785
10006
|
if (pending) await runTranslationBatchAction(args, pending, action, projectRoot);
|
|
9786
10007
|
if (ctxPending) await runContextBatchAction(args, ctxPending, action, projectRoot);
|
|
10008
|
+
if (glossPending) await runGlossaryBatchAction(args, glossPending, action, projectRoot);
|
|
9787
10009
|
}
|
|
9788
10010
|
async function runTranslationBatchAction(args, pending, action, projectRoot) {
|
|
9789
10011
|
if (action === "cancel") {
|
|
@@ -9869,18 +10091,65 @@ async function runContextBatchAction(args, pending, action, projectRoot) {
|
|
|
9869
10091
|
if (outcome.retried) console.log(`${outcome.retried} job(s) re-run synchronously (batch entries failed or were malformed).`);
|
|
9870
10092
|
for (const e of outcome.errors) console.warn(`skip ${e.key}: ${e.error}`);
|
|
9871
10093
|
}
|
|
10094
|
+
async function runGlossaryBatchAction(args, pending, action, projectRoot) {
|
|
10095
|
+
if (action === "cancel") {
|
|
10096
|
+
let remoteFailed = false;
|
|
10097
|
+
try {
|
|
10098
|
+
const ai2 = loadLocalSettings(projectRoot).ai;
|
|
10099
|
+
const provider2 = makeProvider(ai2);
|
|
10100
|
+
if (supportsBatchComplete(provider2)) {
|
|
10101
|
+
await provider2.cancelTranslationBatch(pending.batchId);
|
|
10102
|
+
} else {
|
|
10103
|
+
remoteFailed = true;
|
|
10104
|
+
}
|
|
10105
|
+
} catch {
|
|
10106
|
+
remoteFailed = true;
|
|
10107
|
+
}
|
|
10108
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
10109
|
+
const suffix = remoteFailed ? " (remote cancel failed \u2014 it will expire server-side)" : "";
|
|
10110
|
+
console.log(`Canceled glossary suggestion batch ${pending.batchId}.${suffix}`);
|
|
10111
|
+
return;
|
|
10112
|
+
}
|
|
10113
|
+
const ai = loadLocalSettings(projectRoot).ai;
|
|
10114
|
+
const provider = makeProviderOrExit(ai);
|
|
10115
|
+
if (!provider) return;
|
|
10116
|
+
if (!supportsBatchComplete(provider)) {
|
|
10117
|
+
console.error(`Pending glossary batch was submitted via anthropic, but the configured provider "${ai.provider}" has no batch support.`);
|
|
10118
|
+
process.exitCode = 1;
|
|
10119
|
+
return;
|
|
10120
|
+
}
|
|
10121
|
+
const status = await provider.translationBatchStatus(pending.batchId);
|
|
10122
|
+
const c = status.counts;
|
|
10123
|
+
console.log(`Glossary suggestion batch ${pending.batchId} (${pending.total} source(s), submitted ${pending.createdAt})`);
|
|
10124
|
+
console.log(` ${status.status} \u2014 ${c.succeeded} succeeded, ${c.processing} processing, ${c.errored} errored, ${c.expired} expired, ${c.canceled} canceled`);
|
|
10125
|
+
if (status.status !== "ended") {
|
|
10126
|
+
if (action === "apply") console.log("Not finished yet \u2014 try again later.");
|
|
10127
|
+
return;
|
|
10128
|
+
}
|
|
10129
|
+
const outcome = await applyGlossarySuggestBatchResults(
|
|
10130
|
+
() => loadState(args.statePath),
|
|
10131
|
+
(s) => saveState(args.statePath, s),
|
|
10132
|
+
provider,
|
|
10133
|
+
pending,
|
|
10134
|
+
projectRoot,
|
|
10135
|
+
ai
|
|
10136
|
+
);
|
|
10137
|
+
console.log(`Found ${outcome.added} new candidate term(s).`);
|
|
10138
|
+
if (outcome.retried) console.log(`${outcome.retried} job(s) re-run synchronously (batch entries failed or were malformed).`);
|
|
10139
|
+
for (const e of outcome.errors) console.warn(`batch job failed: ${e.error}`);
|
|
10140
|
+
}
|
|
9872
10141
|
function sarifContextFor(statePath) {
|
|
9873
10142
|
if (detectFormat(statePath) === "split") {
|
|
9874
10143
|
const dir = splitDirFor(statePath);
|
|
9875
|
-
const keysPath =
|
|
10144
|
+
const keysPath = join22(dir, "keys.json");
|
|
9876
10145
|
return {
|
|
9877
10146
|
keysUri: `${basename2(dir)}/keys.json`,
|
|
9878
|
-
keysRawText:
|
|
10147
|
+
keysRawText: existsSync15(keysPath) ? readFileSync27(keysPath, "utf8") : ""
|
|
9879
10148
|
};
|
|
9880
10149
|
}
|
|
9881
10150
|
return {
|
|
9882
10151
|
keysUri: basename2(statePath),
|
|
9883
|
-
keysRawText:
|
|
10152
|
+
keysRawText: existsSync15(statePath) ? readFileSync27(statePath, "utf8") : ""
|
|
9884
10153
|
};
|
|
9885
10154
|
}
|
|
9886
10155
|
function printReport(report, format, statePath) {
|
|
@@ -9949,7 +10218,7 @@ async function runImportCmd(args) {
|
|
|
9949
10218
|
const { runImport: runImport2 } = await Promise.resolve().then(() => (init_run3(), run_exports));
|
|
9950
10219
|
const projectRoot = args.importSource ? resolve11(args.importSource) : dirname5(resolve11(args.statePath));
|
|
9951
10220
|
const out = resolve11(projectRoot, "glotfile.json");
|
|
9952
|
-
if (
|
|
10221
|
+
if (existsSync15(out) && !args.importForce) {
|
|
9953
10222
|
console.error(`${out} already exists; pass --force to overwrite`);
|
|
9954
10223
|
process.exitCode = 1;
|
|
9955
10224
|
return;
|
|
@@ -10155,6 +10424,31 @@ async function runSuggestGlossary(args) {
|
|
|
10155
10424
|
const system = buildGlossarySuggestSystemPrompt();
|
|
10156
10425
|
const batchSize = aiCfg.contextBatchSize ?? aiCfg.batchSize ?? 10;
|
|
10157
10426
|
const concurrency = aiCfg.contextConcurrency ?? aiCfg.concurrency ?? 3;
|
|
10427
|
+
if (args.batch) {
|
|
10428
|
+
if (!supportsBatchComplete(provider)) {
|
|
10429
|
+
console.error(`Provider "${aiCfg.provider}" does not support batch mode. Currently anthropic only.`);
|
|
10430
|
+
process.exitCode = 1;
|
|
10431
|
+
return;
|
|
10432
|
+
}
|
|
10433
|
+
let pending;
|
|
10434
|
+
try {
|
|
10435
|
+
pending = await submitGlossarySuggestBatch(provider, sources, known, batchSize, aiCfg.model, projectRoot);
|
|
10436
|
+
} catch (e) {
|
|
10437
|
+
console.error(e.message);
|
|
10438
|
+
process.exitCode = 1;
|
|
10439
|
+
return;
|
|
10440
|
+
}
|
|
10441
|
+
appendLog(projectRoot, {
|
|
10442
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10443
|
+
kind: "glossary",
|
|
10444
|
+
summary: `Submitted glossary suggestion batch ${pending.batchId} (${pending.total} sources)`,
|
|
10445
|
+
model: aiCfg.model,
|
|
10446
|
+
system
|
|
10447
|
+
});
|
|
10448
|
+
console.log(`Submitted glossary suggestion batch ${pending.batchId} \u2014 ${pending.total} source string(s) at 50% batch pricing.`);
|
|
10449
|
+
console.log("Check progress with `glotfile batch`; it applies results automatically when finished.");
|
|
10450
|
+
return;
|
|
10451
|
+
}
|
|
10158
10452
|
const chunks = [];
|
|
10159
10453
|
for (let i = 0; i < sources.length; i += batchSize) chunks.push(sources.slice(i, i + batchSize));
|
|
10160
10454
|
const all = [];
|
|
@@ -10259,19 +10553,19 @@ function runSplit(args) {
|
|
|
10259
10553
|
`Split catalog into ${splitDirFor(args.statePath)}/ (config.json, keys.json, locales/ \u2014 up to ${state.config.locales.length} locale files). Removed ${args.statePath}.`
|
|
10260
10554
|
);
|
|
10261
10555
|
}
|
|
10262
|
-
var SKILL_SRC =
|
|
10556
|
+
var SKILL_SRC = join22(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
|
|
10263
10557
|
function runSkill(args) {
|
|
10264
10558
|
if (args.print) {
|
|
10265
|
-
console.log(
|
|
10559
|
+
console.log(readFileSync27(join22(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
|
|
10266
10560
|
return;
|
|
10267
10561
|
}
|
|
10268
10562
|
const dest = resolve11(process.cwd(), ".claude", "skills", "glotfile");
|
|
10269
|
-
if (
|
|
10563
|
+
if (existsSync15(dest) && !args.importForce) {
|
|
10270
10564
|
console.error(`${dest} already exists; pass --force to overwrite`);
|
|
10271
10565
|
process.exitCode = 1;
|
|
10272
10566
|
return;
|
|
10273
10567
|
}
|
|
10274
|
-
|
|
10568
|
+
mkdirSync7(dirname5(dest), { recursive: true });
|
|
10275
10569
|
cpSync(SKILL_SRC, dest, { recursive: true });
|
|
10276
10570
|
console.log(`Installed the glotfile skill to ${dest}. Restart Claude Code to pick it up.`);
|
|
10277
10571
|
}
|
|
@@ -10564,12 +10858,13 @@ var COMMAND_HELP = {
|
|
|
10564
10858
|
},
|
|
10565
10859
|
"suggest-glossary": {
|
|
10566
10860
|
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]",
|
|
10861
|
+
usage: "glotfile suggest-glossary [--key <glob>] [--limit <n>] [--since <date>] [--estimate] [--batch]",
|
|
10568
10862
|
options: [
|
|
10569
10863
|
["--key <glob>", "Only scan keys matching this glob"],
|
|
10570
10864
|
["--limit <n>", "Scan at most n source strings"],
|
|
10571
10865
|
["--since <date>", "Only keys added since this date"],
|
|
10572
|
-
["--estimate", "Print batches, tokens and estimated cost without scanning"]
|
|
10866
|
+
["--estimate", "Print batches, tokens and estimated cost without scanning"],
|
|
10867
|
+
["--batch", "Submit via the provider's batch API (50% cost, async; anthropic only)"]
|
|
10573
10868
|
]
|
|
10574
10869
|
},
|
|
10575
10870
|
scan: {
|
|
@@ -10704,8 +10999,8 @@ ${formatOpts([...options, ...GLOBAL_OPTS])}`);
|
|
|
10704
10999
|
);
|
|
10705
11000
|
}
|
|
10706
11001
|
function printVersion() {
|
|
10707
|
-
const pkgPath =
|
|
10708
|
-
console.log(JSON.parse(
|
|
11002
|
+
const pkgPath = join22(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
|
11003
|
+
console.log(JSON.parse(readFileSync27(pkgPath, "utf8")).version);
|
|
10709
11004
|
}
|
|
10710
11005
|
async function main(argv) {
|
|
10711
11006
|
const args = parseArgs(argv);
|