glotfile 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/cli.js +418 -116
- package/dist/server/server.js +324 -94
- package/dist/ui/assets/{index-Dwn9g3g-.js → index-CrceYqHe.js} +19 -7
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
package/dist/server/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";
|
|
@@ -3251,7 +3251,7 @@ function checkOutputs(state, root) {
|
|
|
3251
3251
|
}
|
|
3252
3252
|
|
|
3253
3253
|
// src/server/api.ts
|
|
3254
|
-
import { readFileSync as
|
|
3254
|
+
import { readFileSync as readFileSync26, existsSync as existsSync14, readdirSync as readdirSync16, statSync as statSync10, rmSync as rmSync7 } from "fs";
|
|
3255
3255
|
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
3256
3256
|
|
|
3257
3257
|
// src/server/ai/anthropic.ts
|
|
@@ -4770,6 +4770,127 @@ async function applyContextBatchResults(load, persist, provider, pending, projec
|
|
|
4770
4770
|
return { written, errors, retried: retryChunks.length };
|
|
4771
4771
|
}
|
|
4772
4772
|
|
|
4773
|
+
// src/server/ai/pending-glossary-batch.ts
|
|
4774
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync5, rmSync as rmSync6 } from "fs";
|
|
4775
|
+
import { join as join7 } from "path";
|
|
4776
|
+
function pendingGlossaryBatchPath(projectRoot) {
|
|
4777
|
+
return join7(projectRoot, ".glotfile", "glossary-suggest-batch.json");
|
|
4778
|
+
}
|
|
4779
|
+
function loadPendingGlossaryBatch(projectRoot) {
|
|
4780
|
+
const path = pendingGlossaryBatchPath(projectRoot);
|
|
4781
|
+
if (!existsSync11(path)) return void 0;
|
|
4782
|
+
try {
|
|
4783
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
4784
|
+
if (parsed?.version !== 1) return void 0;
|
|
4785
|
+
return parsed;
|
|
4786
|
+
} catch {
|
|
4787
|
+
return void 0;
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
function savePendingGlossaryBatch(projectRoot, pending) {
|
|
4791
|
+
const dir = join7(projectRoot, ".glotfile");
|
|
4792
|
+
mkdirSync6(dir, { recursive: true });
|
|
4793
|
+
const gitignore = join7(dir, ".gitignore");
|
|
4794
|
+
if (!existsSync11(gitignore)) writeFileSync5(gitignore, "*\n");
|
|
4795
|
+
writeFileSync5(pendingGlossaryBatchPath(projectRoot), JSON.stringify(pending, null, 2) + "\n");
|
|
4796
|
+
}
|
|
4797
|
+
function clearPendingGlossaryBatch(projectRoot) {
|
|
4798
|
+
rmSync6(pendingGlossaryBatchPath(projectRoot), { force: true });
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
// src/server/ai/glossary-batch-run.ts
|
|
4802
|
+
function completionRequestFor2(chunk2, knownTerms) {
|
|
4803
|
+
return {
|
|
4804
|
+
system: buildGlossarySuggestSystemPrompt(),
|
|
4805
|
+
content: [{ type: "text", text: buildGlossarySuggestBatchPrompt(chunk2, knownTerms) }],
|
|
4806
|
+
schema: GLOSSARY_SUGGEST_SCHEMA
|
|
4807
|
+
};
|
|
4808
|
+
}
|
|
4809
|
+
async function submitGlossarySuggestBatch(provider, sources, knownTerms, batchSize, model, projectRoot) {
|
|
4810
|
+
if (loadPendingGlossaryBatch(projectRoot)) {
|
|
4811
|
+
throw new Error("A glossary suggestion batch is already pending. Apply or cancel it first.");
|
|
4812
|
+
}
|
|
4813
|
+
const chunks = [];
|
|
4814
|
+
const size = Math.max(1, batchSize);
|
|
4815
|
+
for (let i = 0; i < sources.length; i += size) chunks.push(sources.slice(i, i + size));
|
|
4816
|
+
const jobs = chunks.map((chunk2, i) => ({ customId: `gloss_${i}`, chunk: chunk2 }));
|
|
4817
|
+
const batchId = await provider.submitCompletionBatch(
|
|
4818
|
+
jobs.map((j) => ({ customId: j.customId, request: completionRequestFor2(j.chunk, knownTerms) }))
|
|
4819
|
+
);
|
|
4820
|
+
const pending = {
|
|
4821
|
+
version: 1,
|
|
4822
|
+
// Only Anthropic implements completion batches today.
|
|
4823
|
+
provider: "anthropic",
|
|
4824
|
+
model,
|
|
4825
|
+
batchId,
|
|
4826
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4827
|
+
total: sources.length,
|
|
4828
|
+
knownTerms,
|
|
4829
|
+
jobs: jobs.map((j) => ({
|
|
4830
|
+
customId: j.customId,
|
|
4831
|
+
requests: j.chunk
|
|
4832
|
+
}))
|
|
4833
|
+
};
|
|
4834
|
+
savePendingGlossaryBatch(projectRoot, pending);
|
|
4835
|
+
return pending;
|
|
4836
|
+
}
|
|
4837
|
+
async function applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, ai) {
|
|
4838
|
+
provider.takeUsage?.();
|
|
4839
|
+
const outcomes = await provider.completionBatchResults(pending.batchId);
|
|
4840
|
+
const batchUsage = provider.takeUsage?.();
|
|
4841
|
+
const allTerms = [];
|
|
4842
|
+
const errors = [];
|
|
4843
|
+
const jobFailures = [];
|
|
4844
|
+
const retryChunks = [];
|
|
4845
|
+
for (const job of pending.jobs) {
|
|
4846
|
+
const outcome = outcomes.get(job.customId);
|
|
4847
|
+
if (outcome?.type === "json") {
|
|
4848
|
+
const batch = outcome.value;
|
|
4849
|
+
allTerms.push(...batch.terms ?? []);
|
|
4850
|
+
continue;
|
|
4851
|
+
}
|
|
4852
|
+
if (!outcome) jobFailures.push({ customId: job.customId, locale: "", type: "missing" });
|
|
4853
|
+
else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: "", type: "malformed", raw: outcome.raw });
|
|
4854
|
+
else jobFailures.push({ customId: job.customId, locale: "", type: "failed", error: outcome.error });
|
|
4855
|
+
retryChunks.push(job.requests);
|
|
4856
|
+
}
|
|
4857
|
+
for (const chunk2 of retryChunks) {
|
|
4858
|
+
try {
|
|
4859
|
+
const raw = await provider.complete(completionRequestFor2(chunk2, pending.knownTerms));
|
|
4860
|
+
const batch = raw;
|
|
4861
|
+
allTerms.push(...batch.terms ?? []);
|
|
4862
|
+
} catch (e) {
|
|
4863
|
+
errors.push({ error: e.message });
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
const retryUsage = provider.takeUsage?.();
|
|
4867
|
+
const pricing = resolvePricing({ ...ai, model: pending.model });
|
|
4868
|
+
let estimatedCostUsd;
|
|
4869
|
+
if (pricing && (batchUsage || retryUsage)) {
|
|
4870
|
+
estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
|
|
4871
|
+
}
|
|
4872
|
+
let usage;
|
|
4873
|
+
if (batchUsage || retryUsage) {
|
|
4874
|
+
usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
4875
|
+
if (retryUsage) addUsage(usage, retryUsage);
|
|
4876
|
+
}
|
|
4877
|
+
const fresh = load();
|
|
4878
|
+
const added = mergeGlossarySuggestions(fresh, dedupeTerms(allTerms));
|
|
4879
|
+
persist(fresh);
|
|
4880
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
4881
|
+
const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
|
|
4882
|
+
appendLog(projectRoot, {
|
|
4883
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4884
|
+
kind: "glossary",
|
|
4885
|
+
summary: `Applied glossary suggestion batch ${pending.batchId}: ${added.length} new term(s), ${errors.length} error(s), ${retryChunks.length} job(s) retried${costSuffix}`,
|
|
4886
|
+
model: pending.model,
|
|
4887
|
+
jobFailures: jobFailures.length ? jobFailures : void 0,
|
|
4888
|
+
usage,
|
|
4889
|
+
estimatedCostUsd
|
|
4890
|
+
});
|
|
4891
|
+
return { added: added.length, errors, retried: retryChunks.length };
|
|
4892
|
+
}
|
|
4893
|
+
|
|
4773
4894
|
// src/server/ai/estimate.ts
|
|
4774
4895
|
var CJK_RE = /[ -鿿가-豈-]/g;
|
|
4775
4896
|
function estimateTokens(text) {
|
|
@@ -4844,6 +4965,28 @@ function estimateContext(targets, ai) {
|
|
|
4844
4965
|
estimatedCost: pricing ? (inputTokens * pricing.inputPerMTok + outputTokens * pricing.outputPerMTok) / 1e6 : null
|
|
4845
4966
|
};
|
|
4846
4967
|
}
|
|
4968
|
+
var TERM_REPLY_TOKENS = 24;
|
|
4969
|
+
var TERM_YIELD = 0.15;
|
|
4970
|
+
function estimateGlossarySuggest(sources, knownTerms, ai) {
|
|
4971
|
+
const batchSize = Math.max(1, ai.contextBatchSize ?? ai.batchSize ?? 10);
|
|
4972
|
+
const batches = chunk(sources, batchSize);
|
|
4973
|
+
const system = buildGlossarySuggestSystemPrompt();
|
|
4974
|
+
let inputTokens = 0;
|
|
4975
|
+
let outputTokens = 0;
|
|
4976
|
+
for (const batch of batches) {
|
|
4977
|
+
inputTokens += estimateTokens(system) + estimateTokens(buildGlossarySuggestBatchPrompt(batch, knownTerms));
|
|
4978
|
+
outputTokens += Math.ceil(batch.length * TERM_YIELD) * TERM_REPLY_TOKENS;
|
|
4979
|
+
}
|
|
4980
|
+
const pricing = resolvePricing(ai);
|
|
4981
|
+
return {
|
|
4982
|
+
sources: sources.length,
|
|
4983
|
+
batches: batches.length,
|
|
4984
|
+
inputTokens,
|
|
4985
|
+
outputTokens,
|
|
4986
|
+
pricing,
|
|
4987
|
+
estimatedCost: pricing ? (inputTokens * pricing.inputPerMTok + outputTokens * pricing.outputPerMTok) / 1e6 : null
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4847
4990
|
|
|
4848
4991
|
// src/server/ai/price-fetch.ts
|
|
4849
4992
|
var MODELS_DEV_URL = "https://models.dev/api.json";
|
|
@@ -4897,8 +5040,8 @@ async function refreshPrices(opts = {}) {
|
|
|
4897
5040
|
import { relative as relative3 } from "path";
|
|
4898
5041
|
|
|
4899
5042
|
// src/server/import/detect.ts
|
|
4900
|
-
import { existsSync as
|
|
4901
|
-
import { join as
|
|
5043
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
|
|
5044
|
+
import { join as join8 } from "path";
|
|
4902
5045
|
var LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4903
5046
|
var VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
|
|
4904
5047
|
function safeIsDir(p) {
|
|
@@ -4909,7 +5052,7 @@ function safeIsDir(p) {
|
|
|
4909
5052
|
}
|
|
4910
5053
|
}
|
|
4911
5054
|
function listDirs(dir) {
|
|
4912
|
-
return readdirSync3(dir).filter((e) => safeIsDir(
|
|
5055
|
+
return readdirSync3(dir).filter((e) => safeIsDir(join8(dir, e)));
|
|
4913
5056
|
}
|
|
4914
5057
|
function fileCount(dir) {
|
|
4915
5058
|
try {
|
|
@@ -4923,23 +5066,23 @@ function pickSource(locales, sizeOf) {
|
|
|
4923
5066
|
return [...locales].sort((a, b) => sizeOf(b) - sizeOf(a) || a.localeCompare(b))[0] ?? "en";
|
|
4924
5067
|
}
|
|
4925
5068
|
function detectLaravel(root) {
|
|
4926
|
-
const localeRoot = [
|
|
5069
|
+
const localeRoot = [join8(root, "resources", "lang"), join8(root, "lang")].find(safeIsDir);
|
|
4927
5070
|
if (!localeRoot) return null;
|
|
4928
5071
|
const locales = listDirs(localeRoot).filter((d) => LOCALE_RE.test(d));
|
|
4929
5072
|
if (locales.length === 0) return null;
|
|
4930
|
-
const sourceLocale = pickSource(locales, (loc) => fileCount(
|
|
5073
|
+
const sourceLocale = pickSource(locales, (loc) => fileCount(join8(localeRoot, loc)));
|
|
4931
5074
|
return { format: "laravel-php", localeRoot, locales, sourceLocale };
|
|
4932
5075
|
}
|
|
4933
5076
|
function detectVue(root, forced = false) {
|
|
4934
5077
|
for (const rel of VUE_DIR_CANDIDATES) {
|
|
4935
|
-
const localeRoot =
|
|
5078
|
+
const localeRoot = join8(root, rel);
|
|
4936
5079
|
if (!safeIsDir(localeRoot)) continue;
|
|
4937
5080
|
const locales = readdirSync3(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
4938
5081
|
const enough = locales.length >= 2 || locales.length === 1 && (forced || locales[0] === "en" || locales[0].startsWith("en-") || locales[0].startsWith("en_"));
|
|
4939
5082
|
if (enough) {
|
|
4940
5083
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
4941
5084
|
try {
|
|
4942
|
-
return statSync3(
|
|
5085
|
+
return statSync3(join8(localeRoot, `${loc}.json`)).size;
|
|
4943
5086
|
} catch {
|
|
4944
5087
|
return 0;
|
|
4945
5088
|
}
|
|
@@ -4953,9 +5096,9 @@ var NEXT_INTL_CONFIG_CANDIDATES = ["src/i18n/request.ts", "i18n/request.ts", "sr
|
|
|
4953
5096
|
var NEXT_INTL_ROUTING_CANDIDATES = ["src/i18n/routing.ts", "i18n/routing.ts", "src/i18n/routing.js", "i18n/routing.js"];
|
|
4954
5097
|
var NEXT_INTL_DIR_CANDIDATES = ["messages", "src/messages", "locales", "src/locales", "src/i18n/messages"];
|
|
4955
5098
|
function hasNextIntlSignal(root) {
|
|
4956
|
-
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) =>
|
|
5099
|
+
if (NEXT_INTL_CONFIG_CANDIDATES.some((rel) => existsSync12(join8(root, rel)))) return true;
|
|
4957
5100
|
try {
|
|
4958
|
-
const pkg = JSON.parse(
|
|
5101
|
+
const pkg = JSON.parse(readFileSync13(join8(root, "package.json"), "utf8"));
|
|
4959
5102
|
if (pkg.dependencies?.["next-intl"] || pkg.devDependencies?.["next-intl"]) return true;
|
|
4960
5103
|
} catch {
|
|
4961
5104
|
}
|
|
@@ -4964,7 +5107,7 @@ function hasNextIntlSignal(root) {
|
|
|
4964
5107
|
function nextIntlDefaultLocale(root) {
|
|
4965
5108
|
for (const rel of NEXT_INTL_ROUTING_CANDIDATES) {
|
|
4966
5109
|
try {
|
|
4967
|
-
const m =
|
|
5110
|
+
const m = readFileSync13(join8(root, rel), "utf8").match(/defaultLocale\s*:\s*['"]([^'"]+)['"]/);
|
|
4968
5111
|
if (m) return m[1];
|
|
4969
5112
|
} catch {
|
|
4970
5113
|
}
|
|
@@ -4974,14 +5117,14 @@ function nextIntlDefaultLocale(root) {
|
|
|
4974
5117
|
function detectNextIntl(root, forced = false) {
|
|
4975
5118
|
if (!forced && !hasNextIntlSignal(root)) return null;
|
|
4976
5119
|
for (const rel of NEXT_INTL_DIR_CANDIDATES) {
|
|
4977
|
-
const localeRoot =
|
|
5120
|
+
const localeRoot = join8(root, rel);
|
|
4978
5121
|
if (!safeIsDir(localeRoot)) continue;
|
|
4979
5122
|
const locales = readdirSync3(localeRoot).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).filter((l) => LOCALE_RE.test(l));
|
|
4980
5123
|
if (locales.length === 0) continue;
|
|
4981
5124
|
const def = nextIntlDefaultLocale(root);
|
|
4982
5125
|
const sourceLocale = def && locales.includes(def) ? def : pickSource(locales, (loc) => {
|
|
4983
5126
|
try {
|
|
4984
|
-
return statSync3(
|
|
5127
|
+
return statSync3(join8(localeRoot, `${loc}.json`)).size;
|
|
4985
5128
|
} catch {
|
|
4986
5129
|
return 0;
|
|
4987
5130
|
}
|
|
@@ -4992,7 +5135,7 @@ function detectNextIntl(root, forced = false) {
|
|
|
4992
5135
|
}
|
|
4993
5136
|
function detectArb(root) {
|
|
4994
5137
|
for (const rel of ["lib/l10n", "l10n", "lib/src/l10n"]) {
|
|
4995
|
-
const localeRoot =
|
|
5138
|
+
const localeRoot = join8(root, rel);
|
|
4996
5139
|
if (!safeIsDir(localeRoot)) continue;
|
|
4997
5140
|
const locales = readdirSync3(localeRoot).map((f) => f.match(/^(?:app_)?(.+)\.arb$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l));
|
|
4998
5141
|
if (locales.length >= 1) {
|
|
@@ -5002,10 +5145,10 @@ function detectArb(root) {
|
|
|
5002
5145
|
return null;
|
|
5003
5146
|
}
|
|
5004
5147
|
function lprojLocales(dir) {
|
|
5005
|
-
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5148
|
+
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync12(join8(dir, `${l}.lproj`, "Localizable.strings")));
|
|
5006
5149
|
}
|
|
5007
5150
|
function detectApple(root) {
|
|
5008
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5151
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5009
5152
|
let best = null;
|
|
5010
5153
|
for (const dir of candidates) {
|
|
5011
5154
|
const locales = lprojLocales(dir);
|
|
@@ -5017,7 +5160,7 @@ function detectApple(root) {
|
|
|
5017
5160
|
locales,
|
|
5018
5161
|
sourceLocale: pickSource(locales, (loc) => {
|
|
5019
5162
|
try {
|
|
5020
|
-
return statSync3(
|
|
5163
|
+
return statSync3(join8(dir, `${loc}.lproj`, "Localizable.strings")).size;
|
|
5021
5164
|
} catch {
|
|
5022
5165
|
return 0;
|
|
5023
5166
|
}
|
|
@@ -5030,7 +5173,7 @@ function detectApple(root) {
|
|
|
5030
5173
|
var ANGULAR_DIR_CANDIDATES = [".", "src/locale", "src/locales", "src/i18n", "locale", "locales", "i18n", "translations"];
|
|
5031
5174
|
function detectAngularXliff(root) {
|
|
5032
5175
|
for (const rel of ANGULAR_DIR_CANDIDATES) {
|
|
5033
|
-
const localeRoot = rel === "." ? root :
|
|
5176
|
+
const localeRoot = rel === "." ? root : join8(root, rel);
|
|
5034
5177
|
if (!safeIsDir(localeRoot)) continue;
|
|
5035
5178
|
const files = readdirSync3(localeRoot).filter((f) => /^messages(\..+)?\.xlf$/.test(f)).sort();
|
|
5036
5179
|
if (files.length === 0) continue;
|
|
@@ -5038,7 +5181,7 @@ function detectAngularXliff(root) {
|
|
|
5038
5181
|
const attrFile = files.includes("messages.xlf") ? "messages.xlf" : files[0];
|
|
5039
5182
|
let sourceLocale;
|
|
5040
5183
|
try {
|
|
5041
|
-
sourceLocale =
|
|
5184
|
+
sourceLocale = readFileSync13(join8(localeRoot, attrFile), "utf8").match(/source-language="([^"]+)"/)?.[1];
|
|
5042
5185
|
} catch {
|
|
5043
5186
|
}
|
|
5044
5187
|
if (!sourceLocale && locales.length === 0) continue;
|
|
@@ -5049,14 +5192,14 @@ function detectAngularXliff(root) {
|
|
|
5049
5192
|
return null;
|
|
5050
5193
|
}
|
|
5051
5194
|
function detectRails(root) {
|
|
5052
|
-
const localeRoot =
|
|
5195
|
+
const localeRoot = join8(root, "config", "locales");
|
|
5053
5196
|
if (!safeIsDir(localeRoot)) return null;
|
|
5054
5197
|
const locales = [];
|
|
5055
5198
|
for (const file of readdirSync3(localeRoot).sort()) {
|
|
5056
5199
|
if (!/\.ya?ml$/.test(file)) continue;
|
|
5057
5200
|
let text;
|
|
5058
5201
|
try {
|
|
5059
|
-
text =
|
|
5202
|
+
text = readFileSync13(join8(localeRoot, file), "utf8");
|
|
5060
5203
|
} catch {
|
|
5061
5204
|
continue;
|
|
5062
5205
|
}
|
|
@@ -5071,15 +5214,15 @@ function detectRails(root) {
|
|
|
5071
5214
|
var I18NEXT_DIR_CANDIDATES = ["public/locales", "static/locales", "locales", "src/locales", "src/i18n/locales"];
|
|
5072
5215
|
function detectI18next(root) {
|
|
5073
5216
|
for (const rel of I18NEXT_DIR_CANDIDATES) {
|
|
5074
|
-
const localeRoot =
|
|
5217
|
+
const localeRoot = join8(root, rel);
|
|
5075
5218
|
if (!safeIsDir(localeRoot)) continue;
|
|
5076
5219
|
const locales = listDirs(localeRoot).filter(
|
|
5077
|
-
(d) => LOCALE_RE.test(d) && readdirSync3(
|
|
5220
|
+
(d) => LOCALE_RE.test(d) && readdirSync3(join8(localeRoot, d)).some((f) => f.endsWith(".json"))
|
|
5078
5221
|
);
|
|
5079
5222
|
if (locales.length === 0) continue;
|
|
5080
5223
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
5081
5224
|
try {
|
|
5082
|
-
return readdirSync3(
|
|
5225
|
+
return readdirSync3(join8(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync3(join8(localeRoot, loc, f)).size, 0);
|
|
5083
5226
|
} catch {
|
|
5084
5227
|
return 0;
|
|
5085
5228
|
}
|
|
@@ -5096,8 +5239,8 @@ function gettextLocales(dir) {
|
|
|
5096
5239
|
if (!locales.includes(flat)) locales.push(flat);
|
|
5097
5240
|
continue;
|
|
5098
5241
|
}
|
|
5099
|
-
if (!LOCALE_RE.test(entry) || !safeIsDir(
|
|
5100
|
-
const sub =
|
|
5242
|
+
if (!LOCALE_RE.test(entry) || !safeIsDir(join8(dir, entry))) continue;
|
|
5243
|
+
const sub = join8(dir, entry);
|
|
5101
5244
|
const hasPo = (d) => {
|
|
5102
5245
|
try {
|
|
5103
5246
|
return readdirSync3(d).some((f) => f.endsWith(".po"));
|
|
@@ -5105,7 +5248,7 @@ function gettextLocales(dir) {
|
|
|
5105
5248
|
return false;
|
|
5106
5249
|
}
|
|
5107
5250
|
};
|
|
5108
|
-
if (hasPo(
|
|
5251
|
+
if (hasPo(join8(sub, "LC_MESSAGES")) || hasPo(sub)) {
|
|
5109
5252
|
if (!locales.includes(entry)) locales.push(entry);
|
|
5110
5253
|
}
|
|
5111
5254
|
}
|
|
@@ -5114,7 +5257,7 @@ function gettextLocales(dir) {
|
|
|
5114
5257
|
var GETTEXT_DIR_CANDIDATES = ["locale", "locales", "po", "translations"];
|
|
5115
5258
|
function detectGettext(root) {
|
|
5116
5259
|
for (const rel of GETTEXT_DIR_CANDIDATES) {
|
|
5117
|
-
const localeRoot =
|
|
5260
|
+
const localeRoot = join8(root, rel);
|
|
5118
5261
|
if (!safeIsDir(localeRoot)) continue;
|
|
5119
5262
|
const locales = gettextLocales(localeRoot);
|
|
5120
5263
|
if (locales.length === 0) continue;
|
|
@@ -5123,10 +5266,10 @@ function detectGettext(root) {
|
|
|
5123
5266
|
return null;
|
|
5124
5267
|
}
|
|
5125
5268
|
function detectAppleStringsdict(root) {
|
|
5126
|
-
const candidates = [root, ...listDirs(root).map((d) =>
|
|
5269
|
+
const candidates = [root, ...listDirs(root).map((d) => join8(root, d))];
|
|
5127
5270
|
let best = null;
|
|
5128
5271
|
for (const dir of candidates) {
|
|
5129
|
-
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) &&
|
|
5272
|
+
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
5273
|
if (locales.length === 0) continue;
|
|
5131
5274
|
if (!best || locales.length > best.locales.length) {
|
|
5132
5275
|
best = { format: "apple-stringsdict", localeRoot: dir, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
@@ -5159,7 +5302,7 @@ var BY_FORMAT = {
|
|
|
5159
5302
|
"apple-stringsdict": detectAppleStringsdict
|
|
5160
5303
|
};
|
|
5161
5304
|
function detect(root, formatOverride) {
|
|
5162
|
-
if (!
|
|
5305
|
+
if (!existsSync12(root)) return null;
|
|
5163
5306
|
if (formatOverride) {
|
|
5164
5307
|
const fn = BY_FORMAT[formatOverride];
|
|
5165
5308
|
if (!fn) throw new Error(`Unknown format: ${formatOverride}`);
|
|
@@ -5173,8 +5316,8 @@ function detect(root, formatOverride) {
|
|
|
5173
5316
|
}
|
|
5174
5317
|
|
|
5175
5318
|
// src/server/import/parsers/vue-i18n-json.ts
|
|
5176
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
5177
|
-
import { join as
|
|
5319
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
|
|
5320
|
+
import { join as join9 } from "path";
|
|
5178
5321
|
|
|
5179
5322
|
// src/server/import/flatten.ts
|
|
5180
5323
|
function flattenObject(value, prefix, warnings) {
|
|
@@ -5215,7 +5358,7 @@ var vueI18nJson2 = {
|
|
|
5215
5358
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5216
5359
|
let data;
|
|
5217
5360
|
try {
|
|
5218
|
-
data = JSON.parse(
|
|
5361
|
+
data = JSON.parse(readFileSync14(join9(localeRoot, file), "utf8"));
|
|
5219
5362
|
} catch (e) {
|
|
5220
5363
|
warnings.push(`vue-i18n-json: failed to parse ${file}: ${e.message}`);
|
|
5221
5364
|
continue;
|
|
@@ -5230,8 +5373,8 @@ var vueI18nJson2 = {
|
|
|
5230
5373
|
};
|
|
5231
5374
|
|
|
5232
5375
|
// src/server/import/parsers/next-intl-json.ts
|
|
5233
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
5234
|
-
import { join as
|
|
5376
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
|
|
5377
|
+
import { join as join10 } from "path";
|
|
5235
5378
|
var LOCALE_RE3 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5236
5379
|
var nextIntlJson2 = {
|
|
5237
5380
|
name: "next-intl-json",
|
|
@@ -5246,7 +5389,7 @@ var nextIntlJson2 = {
|
|
|
5246
5389
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5247
5390
|
let data;
|
|
5248
5391
|
try {
|
|
5249
|
-
data = JSON.parse(
|
|
5392
|
+
data = JSON.parse(readFileSync15(join10(localeRoot, file), "utf8"));
|
|
5250
5393
|
} catch (e) {
|
|
5251
5394
|
warnings.push(`next-intl-json: failed to parse ${file}: ${e.message}`);
|
|
5252
5395
|
continue;
|
|
@@ -5262,7 +5405,7 @@ var nextIntlJson2 = {
|
|
|
5262
5405
|
|
|
5263
5406
|
// src/server/import/parsers/laravel-php.ts
|
|
5264
5407
|
import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
5265
|
-
import { join as
|
|
5408
|
+
import { join as join11, relative as relative2 } from "path";
|
|
5266
5409
|
import { execFileSync } from "child_process";
|
|
5267
5410
|
|
|
5268
5411
|
// src/server/import/placeholders.ts
|
|
@@ -5278,13 +5421,13 @@ function railsToCanonical(value) {
|
|
|
5278
5421
|
|
|
5279
5422
|
// src/server/import/parsers/laravel-php.ts
|
|
5280
5423
|
function listDirs2(dir) {
|
|
5281
|
-
return readdirSync6(dir).filter((e) => statSync4(
|
|
5424
|
+
return readdirSync6(dir).filter((e) => statSync4(join11(dir, e)).isDirectory());
|
|
5282
5425
|
}
|
|
5283
5426
|
function listPhpFiles(dir) {
|
|
5284
5427
|
const out = [];
|
|
5285
5428
|
const walk = (d) => {
|
|
5286
5429
|
for (const e of readdirSync6(d)) {
|
|
5287
|
-
const full =
|
|
5430
|
+
const full = join11(d, e);
|
|
5288
5431
|
if (statSync4(full).isDirectory()) walk(full);
|
|
5289
5432
|
else if (e.endsWith(".php")) out.push(full);
|
|
5290
5433
|
}
|
|
@@ -5321,7 +5464,7 @@ var laravelPhp2 = {
|
|
|
5321
5464
|
for (const locale of listDirs2(localeRoot).sort()) {
|
|
5322
5465
|
if (locale === "vendor") continue;
|
|
5323
5466
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5324
|
-
const localeDir =
|
|
5467
|
+
const localeDir = join11(localeRoot, locale);
|
|
5325
5468
|
locales.push(locale);
|
|
5326
5469
|
for (const file of listPhpFiles(localeDir)) {
|
|
5327
5470
|
const group = relative2(localeDir, file).replace(/\\/g, "/").replace(/\.php$/, "");
|
|
@@ -5344,8 +5487,8 @@ var laravelPhp2 = {
|
|
|
5344
5487
|
};
|
|
5345
5488
|
|
|
5346
5489
|
// src/server/import/parsers/flutter-arb.ts
|
|
5347
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
5348
|
-
import { join as
|
|
5490
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
|
|
5491
|
+
import { join as join12 } from "path";
|
|
5349
5492
|
var LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5350
5493
|
function localeFromArbName(file) {
|
|
5351
5494
|
const m = file.match(/^(.+)\.arb$/);
|
|
@@ -5381,7 +5524,7 @@ var flutterArb2 = {
|
|
|
5381
5524
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5382
5525
|
let data;
|
|
5383
5526
|
try {
|
|
5384
|
-
data = JSON.parse(
|
|
5527
|
+
data = JSON.parse(readFileSync16(join12(localeRoot, file), "utf8"));
|
|
5385
5528
|
} catch (e) {
|
|
5386
5529
|
warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
|
|
5387
5530
|
continue;
|
|
@@ -5406,8 +5549,8 @@ var flutterArb2 = {
|
|
|
5406
5549
|
};
|
|
5407
5550
|
|
|
5408
5551
|
// src/server/import/parsers/apple-strings.ts
|
|
5409
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
5410
|
-
import { join as
|
|
5552
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync5 } from "fs";
|
|
5553
|
+
import { join as join13 } from "path";
|
|
5411
5554
|
var LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5412
5555
|
var TABLE = "Localizable.strings";
|
|
5413
5556
|
function localeFromLproj(dir) {
|
|
@@ -5523,16 +5666,16 @@ var appleStrings2 = {
|
|
|
5523
5666
|
const locale = localeFromLproj(dir);
|
|
5524
5667
|
if (!locale) continue;
|
|
5525
5668
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5526
|
-
const file =
|
|
5669
|
+
const file = join13(localeRoot, dir, TABLE);
|
|
5527
5670
|
let text;
|
|
5528
5671
|
try {
|
|
5529
5672
|
if (!statSync5(file).isFile()) continue;
|
|
5530
|
-
text =
|
|
5673
|
+
text = readFileSync17(file, "utf8");
|
|
5531
5674
|
} catch {
|
|
5532
5675
|
continue;
|
|
5533
5676
|
}
|
|
5534
5677
|
locales.push(locale);
|
|
5535
|
-
const others = readdirSync8(
|
|
5678
|
+
const others = readdirSync8(join13(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
|
|
5536
5679
|
if (others.length) {
|
|
5537
5680
|
warnings.push(`apple-strings: ${dir} has other .strings tables (${others.join(", ")}); only ${TABLE} is imported`);
|
|
5538
5681
|
}
|
|
@@ -5545,8 +5688,8 @@ var appleStrings2 = {
|
|
|
5545
5688
|
};
|
|
5546
5689
|
|
|
5547
5690
|
// src/server/import/parsers/angular-xliff.ts
|
|
5548
|
-
import { readdirSync as readdirSync9, readFileSync as
|
|
5549
|
-
import { join as
|
|
5691
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync18 } from "fs";
|
|
5692
|
+
import { join as join14 } from "path";
|
|
5550
5693
|
var LOCALE_RE6 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5551
5694
|
var FILE_RE = /^messages(?:\.(.+))?\.xlf$/;
|
|
5552
5695
|
function decodeEntities(s) {
|
|
@@ -5613,7 +5756,7 @@ var angularXliff2 = {
|
|
|
5613
5756
|
if (fnameLocale !== void 0 && !LOCALE_RE6.test(fnameLocale)) continue;
|
|
5614
5757
|
let xml;
|
|
5615
5758
|
try {
|
|
5616
|
-
xml =
|
|
5759
|
+
xml = readFileSync18(join14(localeRoot, file), "utf8");
|
|
5617
5760
|
} catch (e) {
|
|
5618
5761
|
warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
|
|
5619
5762
|
continue;
|
|
@@ -5658,8 +5801,8 @@ var angularXliff2 = {
|
|
|
5658
5801
|
};
|
|
5659
5802
|
|
|
5660
5803
|
// src/server/import/parsers/gettext-po.ts
|
|
5661
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
5662
|
-
import { join as
|
|
5804
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync19 } from "fs";
|
|
5805
|
+
import { join as join15 } from "path";
|
|
5663
5806
|
var LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5664
5807
|
var DIRECTIVE_RE = /^(msgctxt|msgid_plural|msgid|msgstr)(?:\[(\d+)\])?[ \t]+"(.*)"\s*$/;
|
|
5665
5808
|
var CONT_RE = /^[ \t]*"(.*)"\s*$/;
|
|
@@ -5751,17 +5894,17 @@ function discoverPoFiles(root) {
|
|
|
5751
5894
|
for (const e of entries) {
|
|
5752
5895
|
if (e.isFile() && e.name.endsWith(".po")) {
|
|
5753
5896
|
const base = e.name.slice(0, -3);
|
|
5754
|
-
found.push({ path:
|
|
5897
|
+
found.push({ path: join15(root, e.name), rel: e.name, locale: LOCALE_RE7.test(base) ? base : null });
|
|
5755
5898
|
} else if (e.isDirectory() && LOCALE_RE7.test(e.name)) {
|
|
5756
|
-
for (const sub of [
|
|
5899
|
+
for (const sub of [join15(e.name, "LC_MESSAGES"), e.name]) {
|
|
5757
5900
|
let names;
|
|
5758
5901
|
try {
|
|
5759
|
-
names = readdirSync10(
|
|
5902
|
+
names = readdirSync10(join15(root, sub)).sort();
|
|
5760
5903
|
} catch {
|
|
5761
5904
|
continue;
|
|
5762
5905
|
}
|
|
5763
5906
|
for (const f of names) {
|
|
5764
|
-
if (f.endsWith(".po")) found.push({ path:
|
|
5907
|
+
if (f.endsWith(".po")) found.push({ path: join15(root, sub, f), rel: join15(sub, f), locale: e.name });
|
|
5765
5908
|
}
|
|
5766
5909
|
}
|
|
5767
5910
|
}
|
|
@@ -5777,7 +5920,7 @@ var gettextPo2 = {
|
|
|
5777
5920
|
for (const file of discoverPoFiles(localeRoot)) {
|
|
5778
5921
|
let entries;
|
|
5779
5922
|
try {
|
|
5780
|
-
entries = parseEntries(
|
|
5923
|
+
entries = parseEntries(readFileSync19(file.path, "utf8"));
|
|
5781
5924
|
} catch (e) {
|
|
5782
5925
|
warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
|
|
5783
5926
|
continue;
|
|
@@ -5822,8 +5965,8 @@ var gettextPo2 = {
|
|
|
5822
5965
|
};
|
|
5823
5966
|
|
|
5824
5967
|
// src/server/import/parsers/i18next-json.ts
|
|
5825
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
5826
|
-
import { join as
|
|
5968
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
|
|
5969
|
+
import { join as join16 } from "path";
|
|
5827
5970
|
var LOCALE_RE8 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5828
5971
|
var PLURAL_SUFFIX_RE = /^(.+)_(zero|one|two|few|many|other)$/;
|
|
5829
5972
|
var PLURAL_ARG = "count";
|
|
@@ -5842,7 +5985,7 @@ function fromI18next(value) {
|
|
|
5842
5985
|
function ingestFile(path, label, prefix, locale, keys, warnings) {
|
|
5843
5986
|
let data;
|
|
5844
5987
|
try {
|
|
5845
|
-
data = JSON.parse(
|
|
5988
|
+
data = JSON.parse(readFileSync20(path, "utf8"));
|
|
5846
5989
|
} catch (e) {
|
|
5847
5990
|
warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
|
|
5848
5991
|
return false;
|
|
@@ -5884,7 +6027,7 @@ var i18nextJson2 = {
|
|
|
5884
6027
|
const keys = {};
|
|
5885
6028
|
const locales = [];
|
|
5886
6029
|
for (const entry of readdirSync11(localeRoot).sort()) {
|
|
5887
|
-
const full =
|
|
6030
|
+
const full = join16(localeRoot, entry);
|
|
5888
6031
|
if (safeIsDir2(full)) {
|
|
5889
6032
|
if (!LOCALE_RE8.test(entry)) continue;
|
|
5890
6033
|
if (opts?.locales && !opts.locales.includes(entry)) continue;
|
|
@@ -5893,7 +6036,7 @@ var i18nextJson2 = {
|
|
|
5893
6036
|
if (!file.endsWith(".json")) continue;
|
|
5894
6037
|
const ns = file.slice(0, -".json".length);
|
|
5895
6038
|
const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
|
|
5896
|
-
if (ingestFile(
|
|
6039
|
+
if (ingestFile(join16(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
|
|
5897
6040
|
}
|
|
5898
6041
|
if (any && !locales.includes(entry)) locales.push(entry);
|
|
5899
6042
|
} else if (entry.endsWith(".json")) {
|
|
@@ -5910,8 +6053,8 @@ var i18nextJson2 = {
|
|
|
5910
6053
|
};
|
|
5911
6054
|
|
|
5912
6055
|
// src/server/import/parsers/rails-yaml.ts
|
|
5913
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
5914
|
-
import { join as
|
|
6056
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync21 } from "fs";
|
|
6057
|
+
import { join as join17 } from "path";
|
|
5915
6058
|
var LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/i;
|
|
5916
6059
|
var CATEGORY_SET = new Set(PLURAL_CATEGORIES);
|
|
5917
6060
|
function makeNode() {
|
|
@@ -6129,7 +6272,7 @@ var railsYaml2 = {
|
|
|
6129
6272
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
6130
6273
|
let text;
|
|
6131
6274
|
try {
|
|
6132
|
-
text =
|
|
6275
|
+
text = readFileSync21(join17(localeRoot, file), "utf8");
|
|
6133
6276
|
} catch (e) {
|
|
6134
6277
|
warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
|
|
6135
6278
|
continue;
|
|
@@ -6150,8 +6293,8 @@ var railsYaml2 = {
|
|
|
6150
6293
|
};
|
|
6151
6294
|
|
|
6152
6295
|
// src/server/import/parsers/apple-stringsdict.ts
|
|
6153
|
-
import { readdirSync as readdirSync13, readFileSync as
|
|
6154
|
-
import { join as
|
|
6296
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync22, statSync as statSync7 } from "fs";
|
|
6297
|
+
import { join as join18 } from "path";
|
|
6155
6298
|
var LOCALE_RE10 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
6156
6299
|
var TABLE2 = "Localizable.stringsdict";
|
|
6157
6300
|
function localeFromLproj2(dir) {
|
|
@@ -6305,16 +6448,16 @@ var appleStringsdict2 = {
|
|
|
6305
6448
|
const locale = localeFromLproj2(dir);
|
|
6306
6449
|
if (!locale) continue;
|
|
6307
6450
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
6308
|
-
const file =
|
|
6451
|
+
const file = join18(localeRoot, dir, TABLE2);
|
|
6309
6452
|
let text;
|
|
6310
6453
|
try {
|
|
6311
6454
|
if (!statSync7(file).isFile()) continue;
|
|
6312
|
-
text =
|
|
6455
|
+
text = readFileSync22(file, "utf8");
|
|
6313
6456
|
} catch {
|
|
6314
6457
|
continue;
|
|
6315
6458
|
}
|
|
6316
6459
|
locales.push(locale);
|
|
6317
|
-
const others = readdirSync13(
|
|
6460
|
+
const others = readdirSync13(join18(localeRoot, dir)).filter(
|
|
6318
6461
|
(f) => f.endsWith(".stringsdict") && f !== TABLE2
|
|
6319
6462
|
);
|
|
6320
6463
|
if (others.length) {
|
|
@@ -6635,7 +6778,7 @@ function refreshLocationUsage(projectRoot, format) {
|
|
|
6635
6778
|
}
|
|
6636
6779
|
|
|
6637
6780
|
// src/server/export-run.ts
|
|
6638
|
-
import { existsSync as
|
|
6781
|
+
import { existsSync as existsSync13, readFileSync as readFileSync23, readdirSync as readdirSync14, rmdirSync, statSync as statSync8, unlinkSync } from "fs";
|
|
6639
6782
|
import { dirname as dirname2, resolve as resolve7, sep } from "path";
|
|
6640
6783
|
function effectiveLocales(config) {
|
|
6641
6784
|
const limit = config.exportLocales;
|
|
@@ -6678,7 +6821,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
6678
6821
|
if (!segment.includes("{locale}") && !segment.includes("{namespace}")) {
|
|
6679
6822
|
const next = resolve7(dir, segment);
|
|
6680
6823
|
if (isLast) {
|
|
6681
|
-
if (stale(locale) &&
|
|
6824
|
+
if (stale(locale) && existsSync13(next) && statSync8(next).isFile()) {
|
|
6682
6825
|
unlinkSync(next);
|
|
6683
6826
|
deleted++;
|
|
6684
6827
|
removeEmptyDirs(dir, root);
|
|
@@ -6734,7 +6877,7 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
6734
6877
|
writtenPaths.add(abs);
|
|
6735
6878
|
let current = null;
|
|
6736
6879
|
try {
|
|
6737
|
-
current =
|
|
6880
|
+
current = readFileSync23(abs, "utf8");
|
|
6738
6881
|
} catch {
|
|
6739
6882
|
}
|
|
6740
6883
|
if (current === f.contents) {
|
|
@@ -6751,17 +6894,17 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
6751
6894
|
}
|
|
6752
6895
|
|
|
6753
6896
|
// src/server/ui-prefs.ts
|
|
6754
|
-
import { readFileSync as
|
|
6897
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
6755
6898
|
import { homedir as homedir2 } from "os";
|
|
6756
|
-
import { join as
|
|
6899
|
+
import { join as join19 } from "path";
|
|
6757
6900
|
var THEMES = ["system", "light", "dark"];
|
|
6758
6901
|
var isThemeMode = (v) => THEMES.includes(v);
|
|
6759
6902
|
var isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
6760
|
-
var defaultUiPrefsPath = () =>
|
|
6903
|
+
var defaultUiPrefsPath = () => join19(homedir2(), ".glotfile", "ui.json");
|
|
6761
6904
|
var DEFAULTS = { theme: "system" };
|
|
6762
6905
|
function readJson(path) {
|
|
6763
6906
|
try {
|
|
6764
|
-
const parsed = JSON.parse(
|
|
6907
|
+
const parsed = JSON.parse(readFileSync24(path, "utf8"));
|
|
6765
6908
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6766
6909
|
} catch {
|
|
6767
6910
|
return {};
|
|
@@ -6780,7 +6923,7 @@ function saveUiPrefs(path, prefs) {
|
|
|
6780
6923
|
}
|
|
6781
6924
|
|
|
6782
6925
|
// src/server/local-settings.ts
|
|
6783
|
-
import { readFileSync as
|
|
6926
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
6784
6927
|
import { resolve as resolve8 } from "path";
|
|
6785
6928
|
var EDITOR_IDS = ["vscode", "zed", "phpstorm"];
|
|
6786
6929
|
var isEditorId = (v) => EDITOR_IDS.includes(v);
|
|
@@ -6795,7 +6938,7 @@ var DEFAULT_EDITOR = "vscode";
|
|
|
6795
6938
|
var settingsPath = (projectRoot) => resolve8(projectRoot, ".glotfile", "settings.json");
|
|
6796
6939
|
function readJson2(path) {
|
|
6797
6940
|
try {
|
|
6798
|
-
const parsed = JSON.parse(
|
|
6941
|
+
const parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
6799
6942
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6800
6943
|
} catch {
|
|
6801
6944
|
return {};
|
|
@@ -6885,7 +7028,7 @@ function createEventHub() {
|
|
|
6885
7028
|
|
|
6886
7029
|
// src/server/watch.ts
|
|
6887
7030
|
import { statSync as statSync9, readdirSync as readdirSync15 } from "fs";
|
|
6888
|
-
import { join as
|
|
7031
|
+
import { join as join20 } from "path";
|
|
6889
7032
|
import { createHash as createHash2 } from "crypto";
|
|
6890
7033
|
function hashState(state) {
|
|
6891
7034
|
return createHash2("sha1").update(serializeJson(state, state.config.format)).digest("hex");
|
|
@@ -6901,15 +7044,15 @@ function signature(statePath) {
|
|
|
6901
7044
|
const parts = [];
|
|
6902
7045
|
for (const rel of ["config.json", "keys.json"]) {
|
|
6903
7046
|
try {
|
|
6904
|
-
const s = statSync9(
|
|
7047
|
+
const s = statSync9(join20(dir, rel));
|
|
6905
7048
|
parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
|
|
6906
7049
|
} catch {
|
|
6907
7050
|
}
|
|
6908
7051
|
}
|
|
6909
7052
|
try {
|
|
6910
|
-
for (const name of readdirSync15(
|
|
7053
|
+
for (const name of readdirSync15(join20(dir, "locales")).sort()) {
|
|
6911
7054
|
if (!name.endsWith(".json")) continue;
|
|
6912
|
-
const s = statSync9(
|
|
7055
|
+
const s = statSync9(join20(dir, "locales", name));
|
|
6913
7056
|
parts.push(`${name}:${s.size}:${s.mtimeMs}`);
|
|
6914
7057
|
}
|
|
6915
7058
|
} catch {
|
|
@@ -6982,9 +7125,9 @@ var sanitize = (s) => s.replace(/[^\w.\-]+/g, "_");
|
|
|
6982
7125
|
var screenshotDirName = (statePath) => basename(statePath).replace(/\.[^.]+$/, "") + "-screenshots";
|
|
6983
7126
|
function projectName(root) {
|
|
6984
7127
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
6985
|
-
if (
|
|
7128
|
+
if (existsSync14(nameFile)) {
|
|
6986
7129
|
try {
|
|
6987
|
-
const name =
|
|
7130
|
+
const name = readFileSync26(nameFile, "utf8").trim();
|
|
6988
7131
|
if (name) return name;
|
|
6989
7132
|
} catch {
|
|
6990
7133
|
}
|
|
@@ -7198,7 +7341,7 @@ function createApi(deps) {
|
|
|
7198
7341
|
if (name.startsWith(".") || name === "node_modules") continue;
|
|
7199
7342
|
const abs = resolve9(dir, name);
|
|
7200
7343
|
let filePath = null;
|
|
7201
|
-
if ((name === "glotfile" || name.endsWith(".glotfile")) &&
|
|
7344
|
+
if ((name === "glotfile" || name.endsWith(".glotfile")) && existsSync14(resolve9(abs, "config.json"))) {
|
|
7202
7345
|
filePath = resolve9(dir, `${name}.json`);
|
|
7203
7346
|
} else if (name === "glotfile.json" || name.endsWith(".glotfile.json")) {
|
|
7204
7347
|
filePath = abs;
|
|
@@ -7232,7 +7375,7 @@ function createApi(deps) {
|
|
|
7232
7375
|
const resolved = resolve9(projectRoot, path);
|
|
7233
7376
|
const inside = resolved === projectRoot || resolved.startsWith(projectRoot + sep2);
|
|
7234
7377
|
if (!inside) return c.json({ error: "file is outside the project" }, 400);
|
|
7235
|
-
if (!
|
|
7378
|
+
if (!existsSync14(resolved)) return c.json({ error: "file not found" }, 400);
|
|
7236
7379
|
loadState(resolved);
|
|
7237
7380
|
deps.statePath = resolved;
|
|
7238
7381
|
watcher.retarget(resolved);
|
|
@@ -7294,9 +7437,9 @@ function createApi(deps) {
|
|
|
7294
7437
|
const abs = resolve9(root, screenshot);
|
|
7295
7438
|
const rel = relative4(root, abs);
|
|
7296
7439
|
const seg0 = rel.split(sep2)[0] ?? "";
|
|
7297
|
-
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") &&
|
|
7440
|
+
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") && existsSync14(abs)) {
|
|
7298
7441
|
try {
|
|
7299
|
-
|
|
7442
|
+
rmSync7(abs);
|
|
7300
7443
|
} catch {
|
|
7301
7444
|
}
|
|
7302
7445
|
}
|
|
@@ -7625,6 +7768,93 @@ function createApi(deps) {
|
|
|
7625
7768
|
await stream.writeSSE({ event: "done", data: JSON.stringify({ added: added.length, terms: added }) });
|
|
7626
7769
|
});
|
|
7627
7770
|
});
|
|
7771
|
+
app.post("/glossary/suggest/estimate", async (c) => {
|
|
7772
|
+
const body = await c.req.json().catch(() => ({}));
|
|
7773
|
+
const s = load();
|
|
7774
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
7775
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7776
|
+
return c.json(estimateGlossarySuggest(sources, knownTermList(s), aiCfg));
|
|
7777
|
+
});
|
|
7778
|
+
app.get("/glossary/suggest/batch/status", async (c) => {
|
|
7779
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7780
|
+
let supported = false;
|
|
7781
|
+
let provider;
|
|
7782
|
+
try {
|
|
7783
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7784
|
+
supported = supportsBatchComplete(provider);
|
|
7785
|
+
} catch {
|
|
7786
|
+
}
|
|
7787
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
7788
|
+
if (!pending) return c.json({ supported, pending: null });
|
|
7789
|
+
const base = { batchId: pending.batchId, createdAt: pending.createdAt, model: pending.model, total: pending.total };
|
|
7790
|
+
if (!provider || !supportsBatchComplete(provider)) {
|
|
7791
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null } });
|
|
7792
|
+
}
|
|
7793
|
+
try {
|
|
7794
|
+
const status = await provider.translationBatchStatus(pending.batchId);
|
|
7795
|
+
return c.json({ supported, pending: { ...base, status: status.status, counts: status.counts } });
|
|
7796
|
+
} catch (e) {
|
|
7797
|
+
return c.json({ supported, pending: { ...base, status: "unknown", counts: null, error: e.message } });
|
|
7798
|
+
}
|
|
7799
|
+
});
|
|
7800
|
+
app.post("/glossary/suggest/batch", (c) => withTranslateLock(async () => {
|
|
7801
|
+
const body = await c.req.json().catch(() => ({}));
|
|
7802
|
+
const s = load();
|
|
7803
|
+
const sources = selectGlossarySources(s, { keyGlob: body.keyGlob, limit: body.limit, since: body.since });
|
|
7804
|
+
if (!sources.length) return c.json({ error: "No source strings to scan." }, 400);
|
|
7805
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7806
|
+
let provider;
|
|
7807
|
+
try {
|
|
7808
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7809
|
+
} catch (e) {
|
|
7810
|
+
return c.json({ error: e.message }, 400);
|
|
7811
|
+
}
|
|
7812
|
+
if (!supportsBatchComplete(provider)) {
|
|
7813
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
7814
|
+
}
|
|
7815
|
+
const batchSize = aiCfg.contextBatchSize ?? aiCfg.batchSize ?? 10;
|
|
7816
|
+
let pending;
|
|
7817
|
+
try {
|
|
7818
|
+
pending = await submitGlossarySuggestBatch(provider, sources, knownTermList(s), batchSize, aiCfg.model, projectRoot);
|
|
7819
|
+
} catch (e) {
|
|
7820
|
+
return c.json({ error: e.message }, 409);
|
|
7821
|
+
}
|
|
7822
|
+
appendLog(projectRoot, {
|
|
7823
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7824
|
+
kind: "glossary",
|
|
7825
|
+
summary: `Submitted glossary suggestion batch ${pending.batchId} (${pending.total} sources)`,
|
|
7826
|
+
model: aiCfg.model
|
|
7827
|
+
});
|
|
7828
|
+
return c.json({ batchId: pending.batchId, total: pending.total });
|
|
7829
|
+
}));
|
|
7830
|
+
app.post("/glossary/suggest/batch/apply", (c) => withTranslateLock(async () => {
|
|
7831
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
7832
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
7833
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7834
|
+
let provider;
|
|
7835
|
+
try {
|
|
7836
|
+
provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7837
|
+
} catch (e) {
|
|
7838
|
+
return c.json({ error: e.message }, 400);
|
|
7839
|
+
}
|
|
7840
|
+
if (!supportsBatchComplete(provider)) {
|
|
7841
|
+
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
7842
|
+
}
|
|
7843
|
+
const outcome = await applyGlossarySuggestBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
|
|
7844
|
+
return c.json(outcome);
|
|
7845
|
+
}));
|
|
7846
|
+
app.post("/glossary/suggest/batch/cancel", async (c) => {
|
|
7847
|
+
const pending = loadPendingGlossaryBatch(projectRoot);
|
|
7848
|
+
if (!pending) return c.json({ error: "No pending glossary suggestion batch." }, 404);
|
|
7849
|
+
const aiCfg = loadLocalSettings(projectRoot).ai;
|
|
7850
|
+
try {
|
|
7851
|
+
const provider = deps.makeProvider ? deps.makeProvider() : makeProvider(aiCfg);
|
|
7852
|
+
if (supportsBatchComplete(provider)) await provider.cancelTranslationBatch(pending.batchId);
|
|
7853
|
+
} catch {
|
|
7854
|
+
}
|
|
7855
|
+
clearPendingGlossaryBatch(projectRoot);
|
|
7856
|
+
return c.json({ canceled: pending.batchId });
|
|
7857
|
+
});
|
|
7628
7858
|
app.post("/keys/:key/screenshot", async (c) => {
|
|
7629
7859
|
const key = c.req.param("key");
|
|
7630
7860
|
const body = await c.req.parseBody();
|
|
@@ -8322,7 +8552,7 @@ function createApi(deps) {
|
|
|
8322
8552
|
|
|
8323
8553
|
// src/server/server.ts
|
|
8324
8554
|
var here = dirname4(fileURLToPath(import.meta.url));
|
|
8325
|
-
var DEFAULT_UI_DIR =
|
|
8555
|
+
var DEFAULT_UI_DIR = join21(here, "..", "ui");
|
|
8326
8556
|
var MIME = {
|
|
8327
8557
|
".html": "text/html; charset=utf-8",
|
|
8328
8558
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -8389,7 +8619,7 @@ function buildApp(opts) {
|
|
|
8389
8619
|
const file = await readFileResponse(target);
|
|
8390
8620
|
if (file) return file;
|
|
8391
8621
|
}
|
|
8392
|
-
const index = await readFileResponse(
|
|
8622
|
+
const index = await readFileResponse(join21(root, "index.html"));
|
|
8393
8623
|
if (index) return index;
|
|
8394
8624
|
return c.notFound();
|
|
8395
8625
|
});
|