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/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Hono as Hono2 } from "hono";
|
|
3
3
|
import { serve } from "@hono/node-server";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
-
import { dirname as dirname4, join as
|
|
5
|
+
import { dirname as dirname4, join as join21, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
|
|
6
6
|
import { readFile, stat } from "fs/promises";
|
|
7
7
|
import { createServer } from "net";
|
|
8
8
|
import open from "open";
|
|
@@ -265,9 +265,6 @@ function validate(raw) {
|
|
|
265
265
|
if (entry.contextSource !== void 0 && entry.contextSource !== "ai") {
|
|
266
266
|
fail(`key "${key}" contextSource must be "ai" if present`);
|
|
267
267
|
}
|
|
268
|
-
if (entry.contextAt !== void 0 && typeof entry.contextAt !== "string") {
|
|
269
|
-
fail(`key "${key}" contextAt must be a string if present`);
|
|
270
|
-
}
|
|
271
268
|
}
|
|
272
269
|
if (raw.glossary !== void 0 && !Array.isArray(raw.glossary)) fail("glossary must be an array");
|
|
273
270
|
if (raw.glossarySuggestions !== void 0 && !Array.isArray(raw.glossarySuggestions)) fail("glossarySuggestions must be an array");
|
|
@@ -713,7 +710,6 @@ function setMetadata(state, key, partial) {
|
|
|
713
710
|
delete safe.values;
|
|
714
711
|
if ("context" in safe) {
|
|
715
712
|
delete entry.contextSource;
|
|
716
|
-
delete entry.contextAt;
|
|
717
713
|
}
|
|
718
714
|
Object.assign(entry, safe);
|
|
719
715
|
if ("context" in safe && !entry.context) delete entry.context;
|
|
@@ -1730,7 +1726,7 @@ var CONTEXT_BATCH_SCHEMA = {
|
|
|
1730
1726
|
required: ["items"],
|
|
1731
1727
|
additionalProperties: false
|
|
1732
1728
|
};
|
|
1733
|
-
function applyContext(state, reqs, results,
|
|
1729
|
+
function applyContext(state, reqs, results, force = false) {
|
|
1734
1730
|
const byId = new Map(reqs.map((r) => [r.id, r]));
|
|
1735
1731
|
let written = 0;
|
|
1736
1732
|
const errors = [];
|
|
@@ -1754,7 +1750,6 @@ function applyContext(state, reqs, results, clock = systemClock, force = false)
|
|
|
1754
1750
|
if (!entry || entry.context && !force) continue;
|
|
1755
1751
|
entry.context = context;
|
|
1756
1752
|
entry.contextSource = "ai";
|
|
1757
|
-
entry.contextAt = clock();
|
|
1758
1753
|
written++;
|
|
1759
1754
|
}
|
|
1760
1755
|
return { written, errors };
|
|
@@ -3251,7 +3246,7 @@ function checkOutputs(state, root) {
|
|
|
3251
3246
|
}
|
|
3252
3247
|
|
|
3253
3248
|
// src/server/api.ts
|
|
3254
|
-
import { readFileSync as
|
|
3249
|
+
import { readFileSync as readFileSync26, existsSync as existsSync14, readdirSync as readdirSync16, statSync as statSync10, rmSync as rmSync7 } from "fs";
|
|
3255
3250
|
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
3256
3251
|
|
|
3257
3252
|
// src/server/ai/anthropic.ts
|
|
@@ -3987,7 +3982,7 @@ var BedrockProvider = class {
|
|
|
3987
3982
|
if (res.stopReason === "max_tokens") {
|
|
3988
3983
|
throw new MalformedReplyError(text || JSON.stringify(tool?.input ?? {}));
|
|
3989
3984
|
}
|
|
3990
|
-
if (tool?.input?.items) return tool.input.items;
|
|
3985
|
+
if (Array.isArray(tool?.input?.items)) return tool.input.items;
|
|
3991
3986
|
return parseReplyItems(text);
|
|
3992
3987
|
}
|
|
3993
3988
|
};
|
|
@@ -4751,7 +4746,7 @@ async function applyContextBatchResults(load, persist, provider, pending, projec
|
|
|
4751
4746
|
if (retryUsage) addUsage(usage, retryUsage);
|
|
4752
4747
|
}
|
|
4753
4748
|
const fresh = load();
|
|
4754
|
-
const { written, errors: applyErrors } = applyContext(fresh, applied, items,
|
|
4749
|
+
const { written, errors: applyErrors } = applyContext(fresh, applied, items, pending.force);
|
|
4755
4750
|
errors.push(...applyErrors);
|
|
4756
4751
|
persist(fresh);
|
|
4757
4752
|
clearPendingContextBatch(projectRoot);
|
|
@@ -4770,6 +4765,127 @@ async function applyContextBatchResults(load, persist, provider, pending, projec
|
|
|
4770
4765
|
return { written, errors, retried: retryChunks.length };
|
|
4771
4766
|
}
|
|
4772
4767
|
|
|
4768
|
+
// src/server/ai/pending-glossary-batch.ts
|
|
4769
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync5, rmSync as rmSync6 } from "fs";
|
|
4770
|
+
import { join as join7 } from "path";
|
|
4771
|
+
function pendingGlossaryBatchPath(projectRoot) {
|
|
4772
|
+
return join7(projectRoot, ".glotfile", "glossary-suggest-batch.json");
|
|
4773
|
+
}
|
|
4774
|
+
function loadPendingGlossaryBatch(projectRoot) {
|
|
4775
|
+
const path = pendingGlossaryBatchPath(projectRoot);
|
|
4776
|
+
if (!existsSync11(path)) return void 0;
|
|
4777
|
+
try {
|
|
4778
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
4779
|
+
if (parsed?.version !== 1) return void 0;
|
|
4780
|
+
return parsed;
|
|
4781
|
+
} catch {
|
|
4782
|
+
return void 0;
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
function savePendingGlossaryBatch(projectRoot, pending) {
|
|
4786
|
+
const dir = join7(projectRoot, ".glotfile");
|
|
4787
|
+
mkdirSync6(dir, { recursive: true });
|
|
4788
|
+
const gitignore = join7(dir, ".gitignore");
|
|
4789
|
+
if (!existsSync11(gitignore)) writeFileSync5(gitignore, "*\n");
|
|
4790
|
+
writeFileSync5(pendingGlossaryBatchPath(projectRoot), JSON.stringify(pending, null, 2) + "\n");
|
|
4791
|
+
}
|
|
4792
|
+
function clearPendingGlossaryBatch(projectRoot) {
|
|
4793
|
+
rmSync6(pendingGlossaryBatchPath(projectRoot), { force: true });
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4796
|
+
// src/server/ai/glossary-batch-run.ts
|
|
4797
|
+
function completionRequestFor2(chunk2, knownTerms) {
|
|
4798
|
+
return {
|
|
4799
|
+
system: buildGlossarySuggestSystemPrompt(),
|
|
4800
|
+
content: [{ type: "text", text: buildGlossarySuggestBatchPrompt(chunk2, knownTerms) }],
|
|
4801
|
+
schema: GLOSSARY_SUGGEST_SCHEMA
|
|
4802
|
+
};
|
|
4803
|
+
}
|
|
4804
|
+
async function submitGlossarySuggestBatch(provider, sources, knownTerms, batchSize, model, projectRoot) {
|
|
4805
|
+
if (loadPendingGlossaryBatch(projectRoot)) {
|
|
4806
|
+
throw new Error("A glossary suggestion batch is already pending. Apply or cancel it first.");
|
|
4807
|
+
}
|
|
4808
|
+
const chunks = [];
|
|
4809
|
+
const size = Math.max(1, batchSize);
|
|
4810
|
+
for (let i = 0; i < sources.length; i += size) chunks.push(sources.slice(i, i + size));
|
|
4811
|
+
const jobs = chunks.map((chunk2, i) => ({ customId: `gloss_${i}`, chunk: chunk2 }));
|
|
4812
|
+
const batchId = await provider.submitCompletionBatch(
|
|
4813
|
+
jobs.map((j) => ({ customId: j.customId, request: completionRequestFor2(j.chunk, knownTerms) }))
|
|
4814
|
+
);
|
|
4815
|
+
const pending = {
|
|
4816
|
+
version: 1,
|
|
4817
|
+
// Only Anthropic implements completion batches today.
|
|
4818
|
+
provider: "anthropic",
|
|
4819
|
+
model,
|
|
4820
|
+
batchId,
|
|
4821
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4822
|
+
total: sources.length,
|
|
4823
|
+
knownTerms,
|
|
4824
|
+
jobs: jobs.map((j) => ({
|
|
4825
|
+
customId: j.customId,
|
|
4826
|
+
requests: j.chunk
|
|
4827
|
+
}))
|
|
4828
|
+
};
|
|
4829
|
+
savePendingGlossaryBatch(projectRoot, pending);
|
|
4830
|
+
return pending;
|
|
4831
|
+
}
|
|
4832
|
+
async function applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, ai) {
|
|
4833
|
+
provider.takeUsage?.();
|
|
4834
|
+
const outcomes = await provider.completionBatchResults(pending.batchId);
|
|
4835
|
+
const batchUsage = provider.takeUsage?.();
|
|
4836
|
+
const allTerms = [];
|
|
4837
|
+
const errors = [];
|
|
4838
|
+
const jobFailures = [];
|
|
4839
|
+
const retryChunks = [];
|
|
4840
|
+
for (const job of pending.jobs) {
|
|
4841
|
+
const outcome = outcomes.get(job.customId);
|
|
4842
|
+
if (outcome?.type === "json") {
|
|
4843
|
+
const batch = outcome.value;
|
|
4844
|
+
allTerms.push(...batch.terms ?? []);
|
|
4845
|
+
continue;
|
|
4846
|
+
}
|
|
4847
|
+
if (!outcome) jobFailures.push({ customId: job.customId, locale: "", type: "missing" });
|
|
4848
|
+
else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: "", type: "malformed", raw: outcome.raw });
|
|
4849
|
+
else jobFailures.push({ customId: job.customId, locale: "", type: "failed", error: outcome.error });
|
|
4850
|
+
retryChunks.push(job.requests);
|
|
4851
|
+
}
|
|
4852
|
+
for (const chunk2 of retryChunks) {
|
|
4853
|
+
try {
|
|
4854
|
+
const raw = await provider.complete(completionRequestFor2(chunk2, pending.knownTerms));
|
|
4855
|
+
const batch = raw;
|
|
4856
|
+
allTerms.push(...batch.terms ?? []);
|
|
4857
|
+
} catch (e) {
|
|
4858
|
+
errors.push({ error: e.message });
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
const retryUsage = provider.takeUsage?.();
|
|
4862
|
+
const pricing = resolvePricing({ ...ai, model: pending.model });
|
|
4863
|
+
let estimatedCostUsd;
|
|
4864
|
+
if (pricing && (batchUsage || retryUsage)) {
|
|
4865
|
+
estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
|
|
4866
|
+
}
|
|
4867
|
+
let usage;
|
|
4868
|
+
if (batchUsage || retryUsage) {
|
|
4869
|
+
usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
4870
|
+
if (retryUsage) addUsage(usage, retryUsage);
|
|
4871
|
+
}
|
|
4872
|
+
const fresh = load();
|
|
4873
|
+
const added = mergeGlossarySuggestions(fresh, dedupeTerms(allTerms));
|
|
4874
|
+
persist(fresh);
|
|
4875
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
4876
|
+
const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
|
|
4877
|
+
appendLog(projectRoot, {
|
|
4878
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4879
|
+
kind: "glossary",
|
|
4880
|
+
summary: `Applied glossary suggestion batch ${pending.batchId}: ${added.length} new term(s), ${errors.length} error(s), ${retryChunks.length} job(s) retried${costSuffix}`,
|
|
4881
|
+
model: pending.model,
|
|
4882
|
+
jobFailures: jobFailures.length ? jobFailures : void 0,
|
|
4883
|
+
usage,
|
|
4884
|
+
estimatedCostUsd
|
|
4885
|
+
});
|
|
4886
|
+
return { added: added.length, errors, retried: retryChunks.length };
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4773
4889
|
// src/server/ai/estimate.ts
|
|
4774
4890
|
var CJK_RE = /[ -鿿가-豈-]/g;
|
|
4775
4891
|
function estimateTokens(text) {
|
|
@@ -4844,6 +4960,28 @@ function estimateContext(targets, ai) {
|
|
|
4844
4960
|
estimatedCost: pricing ? (inputTokens * pricing.inputPerMTok + outputTokens * pricing.outputPerMTok) / 1e6 : null
|
|
4845
4961
|
};
|
|
4846
4962
|
}
|
|
4963
|
+
var TERM_REPLY_TOKENS = 24;
|
|
4964
|
+
var TERM_YIELD = 0.15;
|
|
4965
|
+
function estimateGlossarySuggest(sources, knownTerms, ai) {
|
|
4966
|
+
const batchSize = Math.max(1, ai.contextBatchSize ?? ai.batchSize ?? 10);
|
|
4967
|
+
const batches = chunk(sources, batchSize);
|
|
4968
|
+
const system = buildGlossarySuggestSystemPrompt();
|
|
4969
|
+
let inputTokens = 0;
|
|
4970
|
+
let outputTokens = 0;
|
|
4971
|
+
for (const batch of batches) {
|
|
4972
|
+
inputTokens += estimateTokens(system) + estimateTokens(buildGlossarySuggestBatchPrompt(batch, knownTerms));
|
|
4973
|
+
outputTokens += Math.ceil(batch.length * TERM_YIELD) * TERM_REPLY_TOKENS;
|
|
4974
|
+
}
|
|
4975
|
+
const pricing = resolvePricing(ai);
|
|
4976
|
+
return {
|
|
4977
|
+
sources: sources.length,
|
|
4978
|
+
batches: batches.length,
|
|
4979
|
+
inputTokens,
|
|
4980
|
+
outputTokens,
|
|
4981
|
+
pricing,
|
|
4982
|
+
estimatedCost: pricing ? (inputTokens * pricing.inputPerMTok + outputTokens * pricing.outputPerMTok) / 1e6 : null
|
|
4983
|
+
};
|
|
4984
|
+
}
|
|
4847
4985
|
|
|
4848
4986
|
// src/server/ai/price-fetch.ts
|
|
4849
4987
|
var MODELS_DEV_URL = "https://models.dev/api.json";
|
|
@@ -4897,8 +5035,8 @@ async function refreshPrices(opts = {}) {
|
|
|
4897
5035
|
import { relative as relative3 } from "path";
|
|
4898
5036
|
|
|
4899
5037
|
// src/server/import/detect.ts
|
|
4900
|
-
import { existsSync as
|
|
4901
|
-
import { join as
|
|
5038
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
|
|
5039
|
+
import { join as join8 } from "path";
|
|
4902
5040
|
var LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4903
5041
|
var VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
|
|
4904
5042
|
function safeIsDir(p) {
|
|
@@ -4909,7 +5047,7 @@ function safeIsDir(p) {
|
|
|
4909
5047
|
}
|
|
4910
5048
|
}
|
|
4911
5049
|
function listDirs(dir) {
|
|
4912
|
-
return readdirSync3(dir).filter((e) => safeIsDir(
|
|
5050
|
+
return readdirSync3(dir).filter((e) => safeIsDir(join8(dir, e)));
|
|
4913
5051
|
}
|
|
4914
5052
|
function fileCount(dir) {
|
|
4915
5053
|
try {
|
|
@@ -4923,23 +5061,23 @@ function pickSource(locales, sizeOf) {
|
|
|
4923
5061
|
return [...locales].sort((a, b) => sizeOf(b) - sizeOf(a) || a.localeCompare(b))[0] ?? "en";
|
|
4924
5062
|
}
|
|
4925
5063
|
function detectLaravel(root) {
|
|
4926
|
-
const localeRoot = [
|
|
5064
|
+
const localeRoot = [join8(root, "resources", "lang"), join8(root, "lang")].find(safeIsDir);
|
|
4927
5065
|
if (!localeRoot) return null;
|
|
4928
5066
|
const locales = listDirs(localeRoot).filter((d) => LOCALE_RE.test(d));
|
|
4929
5067
|
if (locales.length === 0) return null;
|
|
4930
|
-
const sourceLocale = pickSource(locales, (loc) => fileCount(
|
|
5068
|
+
const sourceLocale = pickSource(locales, (loc) => fileCount(join8(localeRoot, loc)));
|
|
4931
5069
|
return { format: "laravel-php", localeRoot, locales, sourceLocale };
|
|
4932
5070
|
}
|
|
4933
5071
|
function detectVue(root, forced = false) {
|
|
4934
5072
|
for (const rel of VUE_DIR_CANDIDATES) {
|
|
4935
|
-
const localeRoot =
|
|
5073
|
+
const localeRoot = join8(root, rel);
|
|
4936
5074
|
if (!safeIsDir(localeRoot)) continue;
|
|
4937
5075
|
const locales = readdirSync3(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
4938
5076
|
const enough = locales.length >= 2 || locales.length === 1 && (forced || locales[0] === "en" || locales[0].startsWith("en-") || locales[0].startsWith("en_"));
|
|
4939
5077
|
if (enough) {
|
|
4940
5078
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
4941
5079
|
try {
|
|
4942
|
-
return statSync3(
|
|
5080
|
+
return statSync3(join8(localeRoot, `${loc}.json`)).size;
|
|
4943
5081
|
} catch {
|
|
4944
5082
|
return 0;
|
|
4945
5083
|
}
|
|
@@ -4953,9 +5091,9 @@ var NEXT_INTL_CONFIG_CANDIDATES = ["src/i18n/request.ts", "i18n/request.ts", "sr
|
|
|
4953
5091
|
var NEXT_INTL_ROUTING_CANDIDATES = ["src/i18n/routing.ts", "i18n/routing.ts", "src/i18n/routing.js", "i18n/routing.js"];
|
|
4954
5092
|
var NEXT_INTL_DIR_CANDIDATES = ["messages", "src/messages", "locales", "src/locales", "src/i18n/messages"];
|
|
4955
5093
|
function hasNextIntlSignal(root) {
|
|
4956
|
-
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) =>
|
|
5094
|
+
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) => existsSync12(join8(root, rel)))) return true;
|
|
4957
5095
|
try {
|
|
4958
|
-
const pkg = JSON.parse(
|
|
5096
|
+
const pkg = JSON.parse(readFileSync13(join8(root, "package.json"), "utf8"));
|
|
4959
5097
|
if (pkg.dependencies?.["next-intl"] || pkg.devDependencies?.["next-intl"]) return true;
|
|
4960
5098
|
} catch {
|
|
4961
5099
|
}
|
|
@@ -4964,7 +5102,7 @@ function hasNextIntlSignal(root) {
|
|
|
4964
5102
|
function nextIntlDefaultLocale(root) {
|
|
4965
5103
|
for (const rel of NEXT_INTL_ROUTING_CANDIDATES) {
|
|
4966
5104
|
try {
|
|
4967
|
-
const m =
|
|
5105
|
+
const m = readFileSync13(join8(root, rel), "utf8").match(/defaultLocale\s*:\s*['"]([^'"]+)['"]/);
|
|
4968
5106
|
if (m) return m[1];
|
|
4969
5107
|
} catch {
|
|
4970
5108
|
}
|
|
@@ -4974,14 +5112,14 @@ function nextIntlDefaultLocale(root) {
|
|
|
4974
5112
|
function detectNextIntl(root, forced = false) {
|
|
4975
5113
|
if (!forced && !hasNextIntlSignal(root)) return null;
|
|
4976
5114
|
for (const rel of NEXT_INTL_DIR_CANDIDATES) {
|
|
4977
|
-
const localeRoot =
|
|
5115
|
+
const localeRoot = join8(root, rel);
|
|
4978
5116
|
if (!safeIsDir(localeRoot)) continue;
|
|
4979
5117
|
const locales = readdirSync3(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
4980
5118
|
if (locales.length === 0) continue;
|
|
4981
5119
|
const def = nextIntlDefaultLocale(root);
|
|
4982
5120
|
const sourceLocale = def && locales.includes(def) ? def : pickSource(locales, (loc) => {
|
|
4983
5121
|
try {
|
|
4984
|
-
return statSync3(
|
|
5122
|
+
return statSync3(join8(localeRoot, `${loc}.json`)).size;
|
|
4985
5123
|
} catch {
|
|
4986
5124
|
return 0;
|
|
4987
5125
|
}
|
|
@@ -4992,7 +5130,7 @@ function detectNextIntl(root, forced = false) {
|
|
|
4992
5130
|
}
|
|
4993
5131
|
function detectArb(root) {
|
|
4994
5132
|
for (const rel of ["lib/l10n", "l10n", "lib/src/l10n"]) {
|
|
4995
|
-
const localeRoot =
|
|
5133
|
+
const localeRoot = join8(root, rel);
|
|
4996
5134
|
if (!safeIsDir(localeRoot)) continue;
|
|
4997
5135
|
const locales = readdirSync3(localeRoot).map((f) => f.match(/^(?:app_)?(.+)\.arb$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l));
|
|
4998
5136
|
if (locales.length >= 1) {
|
|
@@ -5002,10 +5140,10 @@ function detectArb(root) {
|
|
|
5002
5140
|
return null;
|
|
5003
5141
|
}
|
|
5004
5142
|
function lprojLocales(dir) {
|
|
5005
|
-
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5143
|
+
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync12(join8(dir, `${l}.lproj`, "Localizable.strings")));
|
|
5006
5144
|
}
|
|
5007
5145
|
function detectApple(root) {
|
|
5008
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5146
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5009
5147
|
let best = null;
|
|
5010
5148
|
for (const dir of candidates) {
|
|
5011
5149
|
const locales = lprojLocales(dir);
|
|
@@ -5017,7 +5155,7 @@ function detectApple(root) {
|
|
|
5017
5155
|
locales,
|
|
5018
5156
|
sourceLocale: pickSource(locales, (loc) => {
|
|
5019
5157
|
try {
|
|
5020
|
-
return statSync3(
|
|
5158
|
+
return statSync3(join8(dir, `${loc}.lproj`, "Localizable.strings")).size;
|
|
5021
5159
|
} catch {
|
|
5022
5160
|
return 0;
|
|
5023
5161
|
}
|
|
@@ -5030,7 +5168,7 @@ function detectApple(root) {
|
|
|
5030
5168
|
var ANGULAR_DIR_CANDIDATES = [".", "src/locale", "src/locales", "src/i18n", "locale", "locales", "i18n", "translations"];
|
|
5031
5169
|
function detectAngularXliff(root) {
|
|
5032
5170
|
for (const rel of ANGULAR_DIR_CANDIDATES) {
|
|
5033
|
-
const localeRoot = rel === "." ? root :
|
|
5171
|
+
const localeRoot = rel === "." ? root : join8(root, rel);
|
|
5034
5172
|
if (!safeIsDir(localeRoot)) continue;
|
|
5035
5173
|
const files = readdirSync3(localeRoot).filter((f) => /^messages(\..+)?\.xlf$/.test(f)).sort();
|
|
5036
5174
|
if (files.length === 0) continue;
|
|
@@ -5038,7 +5176,7 @@ function detectAngularXliff(root) {
|
|
|
5038
5176
|
const attrFile = files.includes("messages.xlf") ? "messages.xlf" : files[0];
|
|
5039
5177
|
let sourceLocale;
|
|
5040
5178
|
try {
|
|
5041
|
-
sourceLocale =
|
|
5179
|
+
sourceLocale = readFileSync13(join8(localeRoot, attrFile), "utf8").match(/source-language="([^"]+)"/)?.[1];
|
|
5042
5180
|
} catch {
|
|
5043
5181
|
}
|
|
5044
5182
|
if (!sourceLocale && locales.length === 0) continue;
|
|
@@ -5049,14 +5187,14 @@ function detectAngularXliff(root) {
|
|
|
5049
5187
|
return null;
|
|
5050
5188
|
}
|
|
5051
5189
|
function detectRails(root) {
|
|
5052
|
-
const localeRoot =
|
|
5190
|
+
const localeRoot = join8(root, "config", "locales");
|
|
5053
5191
|
if (!safeIsDir(localeRoot)) return null;
|
|
5054
5192
|
const locales = [];
|
|
5055
5193
|
for (const file of readdirSync3(localeRoot).sort()) {
|
|
5056
5194
|
if (!/\.ya?ml$/.test(file)) continue;
|
|
5057
5195
|
let text;
|
|
5058
5196
|
try {
|
|
5059
|
-
text =
|
|
5197
|
+
text = readFileSync13(join8(localeRoot, file), "utf8");
|
|
5060
5198
|
} catch {
|
|
5061
5199
|
continue;
|
|
5062
5200
|
}
|
|
@@ -5071,15 +5209,15 @@ function detectRails(root) {
|
|
|
5071
5209
|
var I18NEXT_DIR_CANDIDATES = ["public/locales", "static/locales", "locales", "src/locales", "src/i18n/locales"];
|
|
5072
5210
|
function detectI18next(root) {
|
|
5073
5211
|
for (const rel of I18NEXT_DIR_CANDIDATES) {
|
|
5074
|
-
const localeRoot =
|
|
5212
|
+
const localeRoot = join8(root, rel);
|
|
5075
5213
|
if (!safeIsDir(localeRoot)) continue;
|
|
5076
5214
|
const locales = listDirs(localeRoot).filter(
|
|
5077
|
-
(d) => LOCALE_RE.test(d) && readdirSync3(
|
|
5215
|
+
(d) => LOCALE_RE.test(d) && readdirSync3(join8(localeRoot, d)).some((f) => f.endsWith(".json"))
|
|
5078
5216
|
);
|
|
5079
5217
|
if (locales.length === 0) continue;
|
|
5080
5218
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
5081
5219
|
try {
|
|
5082
|
-
return readdirSync3(
|
|
5220
|
+
return readdirSync3(join8(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync3(join8(localeRoot, loc, f)).size, 0);
|
|
5083
5221
|
} catch {
|
|
5084
5222
|
return 0;
|
|
5085
5223
|
}
|
|
@@ -5096,8 +5234,8 @@ function gettextLocales(dir) {
|
|
|
5096
5234
|
if (!locales.includes(flat)) locales.push(flat);
|
|
5097
5235
|
continue;
|
|
5098
5236
|
}
|
|
5099
|
-
if (!LOCALE_RE.test(entry) || !safeIsDir(
|
|
5100
|
-
const sub =
|
|
5237
|
+
if (!LOCALE_RE.test(entry) || !safeIsDir(join8(dir, entry))) continue;
|
|
5238
|
+
const sub = join8(dir, entry);
|
|
5101
5239
|
const hasPo = (d) => {
|
|
5102
5240
|
try {
|
|
5103
5241
|
return readdirSync3(d).some((f) => f.endsWith(".po"));
|
|
@@ -5105,7 +5243,7 @@ function gettextLocales(dir) {
|
|
|
5105
5243
|
return false;
|
|
5106
5244
|
}
|
|
5107
5245
|
};
|
|
5108
|
-
if (hasPo(
|
|
5246
|
+
if (hasPo(join8(sub, "LC_MESSAGES")) || hasPo(sub)) {
|
|
5109
5247
|
if (!locales.includes(entry)) locales.push(entry);
|
|
5110
5248
|
}
|
|
5111
5249
|
}
|
|
@@ -5114,7 +5252,7 @@ function gettextLocales(dir) {
|
|
|
5114
5252
|
var GETTEXT_DIR_CANDIDATES = ["locale", "locales", "po", "translations"];
|
|
5115
5253
|
function detectGettext(root) {
|
|
5116
5254
|
for (const rel of GETTEXT_DIR_CANDIDATES) {
|
|
5117
|
-
const localeRoot =
|
|
5255
|
+
const localeRoot = join8(root, rel);
|
|
5118
5256
|
if (!safeIsDir(localeRoot)) continue;
|
|
5119
5257
|
const locales = gettextLocales(localeRoot);
|
|
5120
5258
|
if (locales.length === 0) continue;
|
|
@@ -5123,10 +5261,10 @@ function detectGettext(root) {
|
|
|
5123
5261
|
return null;
|
|
5124
5262
|
}
|
|
5125
5263
|
function detectAppleStringsdict(root) {
|
|
5126
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5264
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5127
5265
|
let best = null;
|
|
5128
5266
|
for (const dir of candidates) {
|
|
5129
|
-
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5267
|
+
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync12(join8(dir, `${l}.lproj`, "Localizable.stringsdict")));
|
|
5130
5268
|
if (locales.length === 0) continue;
|
|
5131
5269
|
if (!best || locales.length > best.locales.length) {
|
|
5132
5270
|
best = { format: "apple-stringsdict", localeRoot: dir, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
@@ -5159,7 +5297,7 @@ var BY_FORMAT = {
|
|
|
5159
5297
|
"apple-stringsdict": detectAppleStringsdict
|
|
5160
5298
|
};
|
|
5161
5299
|
function detect(root, formatOverride) {
|
|
5162
|
-
if (!
|
|
5300
|
+
if (!existsSync12(root)) return null;
|
|
5163
5301
|
if (formatOverride) {
|
|
5164
5302
|
const fn = BY_FORMAT[formatOverride];
|
|
5165
5303
|
if (!fn) throw new Error(`Unknown format: ${formatOverride}`);
|
|
@@ -5173,8 +5311,8 @@ function detect(root, formatOverride) {
|
|
|
5173
5311
|
}
|
|
5174
5312
|
|
|
5175
5313
|
// src/server/import/parsers/vue-i18n-json.ts
|
|
5176
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
5177
|
-
import { join as
|
|
5314
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
|
|
5315
|
+
import { join as join9 } from "path";
|
|
5178
5316
|
|
|
5179
5317
|
// src/server/import/flatten.ts
|
|
5180
5318
|
function flattenObject(value, prefix, warnings) {
|
|
@@ -5215,7 +5353,7 @@ var vueI18nJson2 = {
|
|
|
5215
5353
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5216
5354
|
let data;
|
|
5217
5355
|
try {
|
|
5218
|
-
data = JSON.parse(
|
|
5356
|
+
data = JSON.parse(readFileSync14(join9(localeRoot, file), "utf8"));
|
|
5219
5357
|
} catch (e) {
|
|
5220
5358
|
warnings.push(`vue-i18n-json: failed to parse ${file}: ${e.message}`);
|
|
5221
5359
|
continue;
|
|
@@ -5230,8 +5368,8 @@ var vueI18nJson2 = {
|
|
|
5230
5368
|
};
|
|
5231
5369
|
|
|
5232
5370
|
// src/server/import/parsers/next-intl-json.ts
|
|
5233
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
5234
|
-
import { join as
|
|
5371
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
|
|
5372
|
+
import { join as join10 } from "path";
|
|
5235
5373
|
var LOCALE_RE3 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5236
5374
|
var nextIntlJson2 = {
|
|
5237
5375
|
name: "next-intl-json",
|
|
@@ -5246,7 +5384,7 @@ var nextIntlJson2 = {
|
|
|
5246
5384
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5247
5385
|
let data;
|
|
5248
5386
|
try {
|
|
5249
|
-
data = JSON.parse(
|
|
5387
|
+
data = JSON.parse(readFileSync15(join10(localeRoot, file), "utf8"));
|
|
5250
5388
|
} catch (e) {
|
|
5251
5389
|
warnings.push(`next-intl-json: failed to parse ${file}: ${e.message}`);
|
|
5252
5390
|
continue;
|
|
@@ -5262,7 +5400,7 @@ var nextIntlJson2 = {
|
|
|
5262
5400
|
|
|
5263
5401
|
// src/server/import/parsers/laravel-php.ts
|
|
5264
5402
|
import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
5265
|
-
import { join as
|
|
5403
|
+
import { join as join11, relative as relative2 } from "path";
|
|
5266
5404
|
import { execFileSync } from "child_process";
|
|
5267
5405
|
|
|
5268
5406
|
// src/server/import/placeholders.ts
|
|
@@ -5278,13 +5416,13 @@ function railsToCanonical(value) {
|
|
|
5278
5416
|
|
|
5279
5417
|
// src/server/import/parsers/laravel-php.ts
|
|
5280
5418
|
function listDirs2(dir) {
|
|
5281
|
-
return readdirSync6(dir).filter((e) => statSync4(
|
|
5419
|
+
return readdirSync6(dir).filter((e) => statSync4(join11(dir, e)).isDirectory());
|
|
5282
5420
|
}
|
|
5283
5421
|
function listPhpFiles(dir) {
|
|
5284
5422
|
const out = [];
|
|
5285
5423
|
const walk = (d) => {
|
|
5286
5424
|
for (const e of readdirSync6(d)) {
|
|
5287
|
-
const full =
|
|
5425
|
+
const full = join11(d, e);
|
|
5288
5426
|
if (statSync4(full).isDirectory()) walk(full);
|
|
5289
5427
|
else if (e.endsWith(".php")) out.push(full);
|
|
5290
5428
|
}
|
|
@@ -5321,7 +5459,7 @@ var laravelPhp2 = {
|
|
|
5321
5459
|
for (const locale of listDirs2(localeRoot).sort()) {
|
|
5322
5460
|
if (locale === "vendor") continue;
|
|
5323
5461
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5324
|
-
const localeDir =
|
|
5462
|
+
const localeDir = join11(localeRoot, locale);
|
|
5325
5463
|
locales.push(locale);
|
|
5326
5464
|
for (const file of listPhpFiles(localeDir)) {
|
|
5327
5465
|
const group = relative2(localeDir, file).replace(/\\/g, "/").replace(/\.php$/, "");
|
|
@@ -5344,8 +5482,8 @@ var laravelPhp2 = {
|
|
|
5344
5482
|
};
|
|
5345
5483
|
|
|
5346
5484
|
// src/server/import/parsers/flutter-arb.ts
|
|
5347
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
5348
|
-
import { join as
|
|
5485
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
|
|
5486
|
+
import { join as join12 } from "path";
|
|
5349
5487
|
var LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5350
5488
|
function localeFromArbName(file) {
|
|
5351
5489
|
const m = file.match(/^(.+)\.arb$/);
|
|
@@ -5381,7 +5519,7 @@ var flutterArb2 = {
|
|
|
5381
5519
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5382
5520
|
let data;
|
|
5383
5521
|
try {
|
|
5384
|
-
data = JSON.parse(
|
|
5522
|
+
data = JSON.parse(readFileSync16(join12(localeRoot, file), "utf8"));
|
|
5385
5523
|
} catch (e) {
|
|
5386
5524
|
warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
|
|
5387
5525
|
continue;
|
|
@@ -5406,8 +5544,8 @@ var flutterArb2 = {
|
|
|
5406
5544
|
};
|
|
5407
5545
|
|
|
5408
5546
|
// src/server/import/parsers/apple-strings.ts
|
|
5409
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
5410
|
-
import { join as
|
|
5547
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync5 } from "fs";
|
|
5548
|
+
import { join as join13 } from "path";
|
|
5411
5549
|
var LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5412
5550
|
var TABLE = "Localizable.strings";
|
|
5413
5551
|
function localeFromLproj(dir) {
|
|
@@ -5523,16 +5661,16 @@ var appleStrings2 = {
|
|
|
5523
5661
|
const locale = localeFromLproj(dir);
|
|
5524
5662
|
if (!locale) continue;
|
|
5525
5663
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5526
|
-
const file =
|
|
5664
|
+
const file = join13(localeRoot, dir, TABLE);
|
|
5527
5665
|
let text;
|
|
5528
5666
|
try {
|
|
5529
5667
|
if (!statSync5(file).isFile()) continue;
|
|
5530
|
-
text =
|
|
5668
|
+
text = readFileSync17(file, "utf8");
|
|
5531
5669
|
} catch {
|
|
5532
5670
|
continue;
|
|
5533
5671
|
}
|
|
5534
5672
|
locales.push(locale);
|
|
5535
|
-
const others = readdirSync8(
|
|
5673
|
+
const others = readdirSync8(join13(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
|
|
5536
5674
|
if (others.length) {
|
|
5537
5675
|
warnings.push(`apple-strings: ${dir} has other .strings tables (${others.join(", ")}); only ${TABLE} is imported`);
|
|
5538
5676
|
}
|
|
@@ -5545,8 +5683,8 @@ var appleStrings2 = {
|
|
|
5545
5683
|
};
|
|
5546
5684
|
|
|
5547
5685
|
// src/server/import/parsers/angular-xliff.ts
|
|
5548
|
-
import { readdirSync as readdirSync9, readFileSync as
|
|
5549
|
-
import { join as
|
|
5686
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync18 } from "fs";
|
|
5687
|
+
import { join as join14 } from "path";
|
|
5550
5688
|
var LOCALE_RE6 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5551
5689
|
var FILE_RE = /^messages(?:\.(.+))?\.xlf$/;
|
|
5552
5690
|
function decodeEntities(s) {
|
|
@@ -5613,7 +5751,7 @@ var angularXliff2 = {
|
|
|
5613
5751
|
if (fnameLocale !== void 0 && !LOCALE_RE6.test(fnameLocale)) continue;
|
|
5614
5752
|
let xml;
|
|
5615
5753
|
try {
|
|
5616
|
-
xml =
|
|
5754
|
+
xml = readFileSync18(join14(localeRoot, file), "utf8");
|
|
5617
5755
|
} catch (e) {
|
|
5618
5756
|
warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
|
|
5619
5757
|
continue;
|
|
@@ -5658,8 +5796,8 @@ var angularXliff2 = {
|
|
|
5658
5796
|
};
|
|
5659
5797
|
|
|
5660
5798
|
// src/server/import/parsers/gettext-po.ts
|
|
5661
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
5662
|
-
import { join as
|
|
5799
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync19 } from "fs";
|
|
5800
|
+
import { join as join15 } from "path";
|
|
5663
5801
|
var LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5664
5802
|
var DIRECTIVE_RE = /^(msgctxt|msgid_plural|msgid|msgstr)(?:\[(\d+)\])?[ \t]+"(.*)"\s*$/;
|
|
5665
5803
|
var CONT_RE = /^[ \t]*"(.*)"\s*$/;
|
|
@@ -5751,17 +5889,17 @@ function discoverPoFiles(root) {
|
|
|
5751
5889
|
for (const e of entries) {
|
|
5752
5890
|
if (e.isFile() && e.name.endsWith(".po")) {
|
|
5753
5891
|
const base = e.name.slice(0, -3);
|
|
5754
|
-
found.push({ path:
|
|
5892
|
+
found.push({ path: join15(root, e.name), rel: e.name, locale: LOCALE_RE7.test(base) ? base : null });
|
|
5755
5893
|
} else if (e.isDirectory() && LOCALE_RE7.test(e.name)) {
|
|
5756
|
-
for (const sub of [
|
|
5894
|
+
for (const sub of [join15(e.name, "LC_MESSAGES"), e.name]) {
|
|
5757
5895
|
let names;
|
|
5758
5896
|
try {
|
|
5759
|
-
names = readdirSync10(
|
|
5897
|
+
names = readdirSync10(join15(root, sub)).sort();
|
|
5760
5898
|
} catch {
|
|
5761
5899
|
continue;
|
|
5762
5900
|
}
|
|
5763
5901
|
for (const f of names) {
|
|
5764
|
-
if (f.endsWith(".po")) found.push({ path:
|
|
5902
|
+
if (f.endsWith(".po")) found.push({ path: join15(root, sub, f), rel: join15(sub, f), locale: e.name });
|
|
5765
5903
|
}
|
|
5766
5904
|
}
|
|
5767
5905
|
}
|
|
@@ -5777,7 +5915,7 @@ var gettextPo2 = {
|
|
|
5777
5915
|
for (const file of discoverPoFiles(localeRoot)) {
|
|
5778
5916
|
let entries;
|
|
5779
5917
|
try {
|
|
5780
|
-
entries = parseEntries(
|
|
5918
|
+
entries = parseEntries(readFileSync19(file.path, "utf8"));
|
|
5781
5919
|
} catch (e) {
|
|
5782
5920
|
warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
|
|
5783
5921
|
continue;
|
|
@@ -5822,8 +5960,8 @@ var gettextPo2 = {
|
|
|
5822
5960
|
};
|
|
5823
5961
|
|
|
5824
5962
|
// src/server/import/parsers/i18next-json.ts
|
|
5825
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
5826
|
-
import { join as
|
|
5963
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
|
|
5964
|
+
import { join as join16 } from "path";
|
|
5827
5965
|
var LOCALE_RE8 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5828
5966
|
var PLURAL_SUFFIX_RE = /^(.+)_(zero|one|two|few|many|other)$/;
|
|
5829
5967
|
var PLURAL_ARG = "count";
|
|
@@ -5842,7 +5980,7 @@ function fromI18next(value) {
|
|
|
5842
5980
|
function ingestFile(path, label, prefix, locale, keys, warnings) {
|
|
5843
5981
|
let data;
|
|
5844
5982
|
try {
|
|
5845
|
-
data = JSON.parse(
|
|
5983
|
+
data = JSON.parse(readFileSync20(path, "utf8"));
|
|
5846
5984
|
} catch (e) {
|
|
5847
5985
|
warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
|
|
5848
5986
|
return false;
|
|
@@ -5884,7 +6022,7 @@ var i18nextJson2 = {
|
|
|
5884
6022
|
const keys = {};
|
|
5885
6023
|
const locales = [];
|
|
5886
6024
|
for (const entry of readdirSync11(localeRoot).sort()) {
|
|
5887
|
-
const full =
|
|
6025
|
+
const full = join16(localeRoot, entry);
|
|
5888
6026
|
if (safeIsDir2(full)) {
|
|
5889
6027
|
if (!LOCALE_RE8.test(entry)) continue;
|
|
5890
6028
|
if (opts?.locales && !opts.locales.includes(entry)) continue;
|
|
@@ -5893,7 +6031,7 @@ var i18nextJson2 = {
|
|
|
5893
6031
|
if (!file.endsWith(".json")) continue;
|
|
5894
6032
|
const ns = file.slice(0, -".json".length);
|
|
5895
6033
|
const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
|
|
5896
|
-
if (ingestFile(
|
|
6034
|
+
if (ingestFile(join16(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
|
|
5897
6035
|
}
|
|
5898
6036
|
if (any && !locales.includes(entry)) locales.push(entry);
|
|
5899
6037
|
} else if (entry.endsWith(".json")) {
|
|
@@ -5910,8 +6048,8 @@ var i18nextJson2 = {
|
|
|
5910
6048
|
};
|
|
5911
6049
|
|
|
5912
6050
|
// src/server/import/parsers/rails-yaml.ts
|
|
5913
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
5914
|
-
import { join as
|
|
6051
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync21 } from "fs";
|
|
6052
|
+
import { join as join17 } from "path";
|
|
5915
6053
|
var LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/i;
|
|
5916
6054
|
var CATEGORY_SET = new Set(PLURAL_CATEGORIES);
|
|
5917
6055
|
function makeNode() {
|
|
@@ -6129,7 +6267,7 @@ var railsYaml2 = {
|
|
|
6129
6267
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
6130
6268
|
let text;
|
|
6131
6269
|
try {
|
|
6132
|
-
text =
|
|
6270
|
+
text = readFileSync21(join17(localeRoot, file), "utf8");
|
|
6133
6271
|
} catch (e) {
|
|
6134
6272
|
warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
|
|
6135
6273
|
continue;
|
|
@@ -6150,8 +6288,8 @@ var railsYaml2 = {
|
|
|
6150
6288
|
};
|
|
6151
6289
|
|
|
6152
6290
|
// src/server/import/parsers/apple-stringsdict.ts
|
|
6153
|
-
import { readdirSync as readdirSync13, readFileSync as
|
|
6154
|
-
import { join as
|
|
6291
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync22, statSync as statSync7 } from "fs";
|
|
6292
|
+
import { join as join18 } from "path";
|
|
6155
6293
|
var LOCALE_RE10 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
6156
6294
|
var TABLE2 = "Localizable.stringsdict";
|
|
6157
6295
|
function localeFromLproj2(dir) {
|
|
@@ -6305,16 +6443,16 @@ var appleStringsdict2 = {
|
|
|
6305
6443
|
const locale = localeFromLproj2(dir);
|
|
6306
6444
|
if (!locale) continue;
|
|
6307
6445
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
6308
|
-
const file =
|
|
6446
|
+
const file = join18(localeRoot, dir, TABLE2);
|
|
6309
6447
|
let text;
|
|
6310
6448
|
try {
|
|
6311
6449
|
if (!statSync7(file).isFile()) continue;
|
|
6312
|
-
text =
|
|
6450
|
+
text = readFileSync22(file, "utf8");
|
|
6313
6451
|
} catch {
|
|
6314
6452
|
continue;
|
|
6315
6453
|
}
|
|
6316
6454
|
locales.push(locale);
|
|
6317
|
-
const others = readdirSync13(
|
|
6455
|
+
const others = readdirSync13(join18(localeRoot, dir)).filter(
|
|
6318
6456
|
(f) => f.endsWith(".stringsdict") && f !== TABLE2
|
|
6319
6457
|
);
|
|
6320
6458
|
if (others.length) {
|
|
@@ -6635,7 +6773,7 @@ function refreshLocationUsage(projectRoot, format) {
|
|
|
6635
6773
|
}
|
|
6636
6774
|
|
|
6637
6775
|
// src/server/export-run.ts
|
|
6638
|
-
import { existsSync as
|
|
6776
|
+
import { existsSync as existsSync13, readFileSync as readFileSync23, readdirSync as readdirSync14, rmdirSync, statSync as statSync8, unlinkSync } from "fs";
|
|
6639
6777
|
import { dirname as dirname2, resolve as resolve7, sep } from "path";
|
|
6640
6778
|
function effectiveLocales(config) {
|
|
6641
6779
|
const limit = config.exportLocales;
|
|
@@ -6678,7 +6816,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
6678
6816
|
if (!segment.includes("{locale}") && !segment.includes("{namespace}")) {
|
|
6679
6817
|
const next = resolve7(dir, segment);
|
|
6680
6818
|
if (isLast) {
|
|
6681
|
-
if (stale(locale) &&
|
|
6819
|
+
if (stale(locale) && existsSync13(next) && statSync8(next).isFile()) {
|
|
6682
6820
|
unlinkSync(next);
|
|
6683
6821
|
deleted++;
|
|
6684
6822
|
removeEmptyDirs(dir, root);
|
|
@@ -6734,7 +6872,7 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
6734
6872
|
writtenPaths.add(abs);
|
|
6735
6873
|
let current = null;
|
|
6736
6874
|
try {
|
|
6737
|
-
current =
|
|
6875
|
+
current = readFileSync23(abs, "utf8");
|
|
6738
6876
|
} catch {
|
|
6739
6877
|
}
|
|
6740
6878
|
if (current === f.contents) {
|
|
@@ -6751,17 +6889,17 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
6751
6889
|
}
|
|
6752
6890
|
|
|
6753
6891
|
// src/server/ui-prefs.ts
|
|
6754
|
-
import { readFileSync as
|
|
6892
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
6755
6893
|
import { homedir as homedir2 } from "os";
|
|
6756
|
-
import { join as
|
|
6894
|
+
import { join as join19 } from "path";
|
|
6757
6895
|
var THEMES = ["system", "light", "dark"];
|
|
6758
6896
|
var isThemeMode = (v) => THEMES.includes(v);
|
|
6759
6897
|
var isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
6760
|
-
var defaultUiPrefsPath = () =>
|
|
6898
|
+
var defaultUiPrefsPath = () => join19(homedir2(), ".glotfile", "ui.json");
|
|
6761
6899
|
var DEFAULTS = { theme: "system" };
|
|
6762
6900
|
function readJson(path) {
|
|
6763
6901
|
try {
|
|
6764
|
-
const parsed = JSON.parse(
|
|
6902
|
+
const parsed = JSON.parse(readFileSync24(path, "utf8"));
|
|
6765
6903
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6766
6904
|
} catch {
|
|
6767
6905
|
return {};
|
|
@@ -6780,7 +6918,7 @@ function saveUiPrefs(path, prefs) {
|
|
|
6780
6918
|
}
|
|
6781
6919
|
|
|
6782
6920
|
// src/server/local-settings.ts
|
|
6783
|
-
import { readFileSync as
|
|
6921
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
6784
6922
|
import { resolve as resolve8 } from "path";
|
|
6785
6923
|
var EDITOR_IDS = ["vscode", "zed", "phpstorm"];
|
|
6786
6924
|
var isEditorId = (v) => EDITOR_IDS.includes(v);
|
|
@@ -6795,7 +6933,7 @@ var DEFAULT_EDITOR = "vscode";
|
|
|
6795
6933
|
var settingsPath = (projectRoot) => resolve8(projectRoot, ".glotfile", "settings.json");
|
|
6796
6934
|
function readJson2(path) {
|
|
6797
6935
|
try {
|
|
6798
|
-
const parsed = JSON.parse(
|
|
6936
|
+
const parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
6799
6937
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6800
6938
|
} catch {
|
|
6801
6939
|
return {};
|
|
@@ -6885,7 +7023,7 @@ function createEventHub() {
|
|
|
6885
7023
|
|
|
6886
7024
|
// src/server/watch.ts
|
|
6887
7025
|
import { statSync as statSync9, readdirSync as readdirSync15 } from "fs";
|
|
6888
|
-
import { join as
|
|
7026
|
+
import { join as join20 } from "path";
|
|
6889
7027
|
import { createHash as createHash2 } from "crypto";
|
|
6890
7028
|
function hashState(state) {
|
|
6891
7029
|
return createHash2("sha1").update(serializeJson(state, state.config.format)).digest("hex");
|
|
@@ -6901,15 +7039,15 @@ function signature(statePath) {
|
|
|
6901
7039
|
const parts = [];
|
|
6902
7040
|
for (const rel of ["config.json", "keys.json"]) {
|
|
6903
7041
|
try {
|
|
6904
|
-
const s = statSync9(
|
|
7042
|
+
const s = statSync9(join20(dir, rel));
|
|
6905
7043
|
parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
|
|
6906
7044
|
} catch {
|
|
6907
7045
|
}
|
|
6908
7046
|
}
|
|
6909
7047
|
try {
|
|
6910
|
-
for (const name of readdirSync15(
|
|
7048
|
+
for (const name of readdirSync15(join20(dir, "locales")).sort()) {
|
|
6911
7049
|
if (!name.endsWith(".json")) continue;
|
|
6912
|
-
const s = statSync9(
|
|
7050
|
+
const s = statSync9(join20(dir, "locales", name));
|
|
6913
7051
|
parts.push(`${name}:${s.size}:${s.mtimeMs}`);
|
|
6914
7052
|
}
|
|
6915
7053
|
} catch {
|
|
@@ -6982,9 +7120,9 @@ var sanitize = (s) => s.replace(/[^\w.\-]+/g, "_");
|
|
|
6982
7120
|
var screenshotDirName = (statePath) => basename(statePath).replace(/\.[^.]+$/, "") + "-screenshots";
|
|
6983
7121
|
function projectName(root) {
|
|
6984
7122
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
6985
|
-
if (
|
|
7123
|
+
if (existsSync14(nameFile)) {
|
|
6986
7124
|
try {
|
|
6987
|
-
const name =
|
|
7125
|
+
const name = readFileSync26(nameFile, "utf8").trim();
|
|
6988
7126
|
if (name) return name;
|
|
6989
7127
|
} catch {
|
|
6990
7128
|
}
|
|
@@ -7198,7 +7336,7 @@ function createApi(deps) {
|
|
|
7198
7336
|
if (name.startsWith(".") || name === "node_modules") continue;
|
|
7199
7337
|
const abs = resolve9(dir, name);
|
|
7200
7338
|
let filePath = null;
|
|
7201
|
-
if ((name === "glotfile" || name.endsWith(".glotfile")) &&
|
|
7339
|
+
if ((name === "glotfile" || name.endsWith(".glotfile")) && existsSync14(resolve9(abs, "config.json"))) {
|
|
7202
7340
|
filePath = resolve9(dir, `${name}.json`);
|
|
7203
7341
|
} else if (name === "glotfile.json" || name.endsWith(".glotfile.json")) {
|
|
7204
7342
|
filePath = abs;
|
|
@@ -7232,7 +7370,7 @@ function createApi(deps) {
|
|
|
7232
7370
|
const resolved = resolve9(projectRoot, path);
|
|
7233
7371
|
const inside = resolved === projectRoot || resolved.startsWith(projectRoot + sep2);
|
|
7234
7372
|
if (!inside) return c.json({ error: "file is outside the project" }, 400);
|
|
7235
|
-
if (!
|
|
7373
|
+
if (!existsSync14(resolved)) return c.json({ error: "file not found" }, 400);
|
|
7236
7374
|
loadState(resolved);
|
|
7237
7375
|
deps.statePath = resolved;
|
|
7238
7376
|
watcher.retarget(resolved);
|
|
@@ -7294,9 +7432,9 @@ function createApi(deps) {
|
|
|
7294
7432
|
const abs = resolve9(root, screenshot);
|
|
7295
7433
|
const rel = relative4(root, abs);
|
|
7296
7434
|
const seg0 = rel.split(sep2)[0] ?? "";
|
|
7297
|
-
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") &&
|
|
7435
|
+
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") && existsSync14(abs)) {
|
|
7298
7436
|
try {
|
|
7299
|
-
|
|
7437
|
+
rmSync7(abs);
|
|
7300
7438
|
} catch {
|
|
7301
7439
|
}
|
|
7302
7440
|
}
|
|
@@ -7377,7 +7515,6 @@ function createApi(deps) {
|
|
|
7377
7515
|
if (clearContext === true) {
|
|
7378
7516
|
delete entry.context;
|
|
7379
7517
|
delete entry.contextSource;
|
|
7380
|
-
delete entry.contextAt;
|
|
7381
7518
|
}
|
|
7382
7519
|
updated++;
|
|
7383
7520
|
}
|
|
@@ -7625,6 +7762,93 @@ function createApi(deps) {
|
|
|
7625
7762
|
await stream.writeSSE({ event: "done", data: JSON.stringify({ added: added.length, terms: added }) });
|
|
7626
7763
|
});
|
|
7627
7764
|
});
|
|
7765
|
+
app.post("/glossary/suggest/estimate", async (c) => {
|
|
7766
|
+
const body = await c.req.json().catch(() => ({}));
|
|
7767
|
+
const s = load();
|
|
7768
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
7769
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7770
|
+
return c.json(estimateGlossarySuggest(sources, knownTermList(s), aiCfg));
|
|
7771
|
+
});
|
|
7772
|
+
app.get("/glossary/suggest/batch/status", async (c) => {
|
|
7773
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7774
|
+
let supported = false;
|
|
7775
|
+
let provider;
|
|
7776
|
+
try {
|
|
7777
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7778
|
+
supported = supportsBatchComplete(provider);
|
|
7779
|
+
} catch {
|
|
7780
|
+
}
|
|
7781
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
7782
|
+
if (!pending) return c.json({ supported, pending: null });
|
|
7783
|
+
const base = { batchId: pending.batchId, createdAt: pending.createdAt, model: pending.model, total: pending.total };
|
|
7784
|
+
if (!provider || !supportsBatchComplete(provider)) {
|
|
7785
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null } });
|
|
7786
|
+
}
|
|
7787
|
+
try {
|
|
7788
|
+
const status = await provider.translationBatchStatus(pending.batchId);
|
|
7789
|
+
return c.json({ supported, pending: { ...base, status: status.status, counts: status.counts } });
|
|
7790
|
+
} catch (e) {
|
|
7791
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null, error: e.message } });
|
|
7792
|
+
}
|
|
7793
|
+
});
|
|
7794
|
+
app.post("/glossary/suggest/batch", (c) => withTranslateLock(async () => {
|
|
7795
|
+
const body = await c.req.json().catch(() => ({}));
|
|
7796
|
+
const s = load();
|
|
7797
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
7798
|
+
if (!sources.length) return c.json({ error: "No source strings to scan." }, 400);
|
|
7799
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7800
|
+
let provider;
|
|
7801
|
+
try {
|
|
7802
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7803
|
+
} catch (e) {
|
|
7804
|
+
return c.json({ error: e.message }, 400);
|
|
7805
|
+
}
|
|
7806
|
+
if (!supportsBatchComplete(provider)) {
|
|
7807
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
7808
|
+
}
|
|
7809
|
+
const batchSize = aiCfg.contextBatchSize ?? aiCfg.batchSize ?? 10;
|
|
7810
|
+
let pending;
|
|
7811
|
+
try {
|
|
7812
|
+
pending = await submitGlossarySuggestBatch(provider, sources, knownTermList(s), batchSize, aiCfg.model, projectRoot);
|
|
7813
|
+
} catch (e) {
|
|
7814
|
+
return c.json({ error: e.message }, 409);
|
|
7815
|
+
}
|
|
7816
|
+
appendLog(projectRoot, {
|
|
7817
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7818
|
+
kind: "glossary",
|
|
7819
|
+
summary: `Submitted glossary suggestion batch ${pending.batchId} (${pending.total} sources)`,
|
|
7820
|
+
model: aiCfg.model
|
|
7821
|
+
});
|
|
7822
|
+
return c.json({ batchId: pending.batchId, total: pending.total });
|
|
7823
|
+
}));
|
|
7824
|
+
app.post("/glossary/suggest/batch/apply", (c) => withTranslateLock(async () => {
|
|
7825
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
7826
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
7827
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7828
|
+
let provider;
|
|
7829
|
+
try {
|
|
7830
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7831
|
+
} catch (e) {
|
|
7832
|
+
return c.json({ error: e.message }, 400);
|
|
7833
|
+
}
|
|
7834
|
+
if (!supportsBatchComplete(provider)) {
|
|
7835
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
7836
|
+
}
|
|
7837
|
+
const outcome = await applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
|
|
7838
|
+
return c.json(outcome);
|
|
7839
|
+
}));
|
|
7840
|
+
app.post("/glossary/suggest/batch/cancel", async (c) => {
|
|
7841
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
7842
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
7843
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7844
|
+
try {
|
|
7845
|
+
const provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7846
|
+
if (supportsBatchComplete(provider)) await provider.cancelTranslationBatch(pending.batchId);
|
|
7847
|
+
} catch {
|
|
7848
|
+
}
|
|
7849
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
7850
|
+
return c.json({ canceled: pending.batchId });
|
|
7851
|
+
});
|
|
7628
7852
|
app.post("/keys/:key/screenshot", async (c) => {
|
|
7629
7853
|
const key = c.req.param("key");
|
|
7630
7854
|
const body = await c.req.parseBody();
|
|
@@ -8178,7 +8402,7 @@ function createApi(deps) {
|
|
|
8178
8402
|
if (signal?.aborted) break;
|
|
8179
8403
|
const batch = raw;
|
|
8180
8404
|
const fresh = load();
|
|
8181
|
-
const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [],
|
|
8405
|
+
const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], body.force === true);
|
|
8182
8406
|
const usage = provider.takeUsage?.();
|
|
8183
8407
|
appendLog(projectRoot, {
|
|
8184
8408
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8322,7 +8546,7 @@ function createApi(deps) {
|
|
|
8322
8546
|
|
|
8323
8547
|
// src/server/server.ts
|
|
8324
8548
|
var here = dirname4(fileURLToPath(import.meta.url));
|
|
8325
|
-
var DEFAULT_UI_DIR =
|
|
8549
|
+
var DEFAULT_UI_DIR = join21(here, "..", "ui");
|
|
8326
8550
|
var MIME = {
|
|
8327
8551
|
".html": "text/html; charset=utf-8",
|
|
8328
8552
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -8389,7 +8613,7 @@ function buildApp(opts) {
|
|
|
8389
8613
|
const file = await readFileResponse(target);
|
|
8390
8614
|
if (file) return file;
|
|
8391
8615
|
}
|
|
8392
|
-
const index = await readFileResponse(
|
|
8616
|
+
const index = await readFileResponse(join21(root, "index.html"));
|
|
8393
8617
|
if (index) return index;
|
|
8394
8618
|
return c.notFound();
|
|
8395
8619
|
});
|