glotfile 0.8.2 → 0.8.4
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/server.js
CHANGED
|
@@ -639,7 +639,7 @@ function setSourceValue(state, key, value) {
|
|
|
639
639
|
function setTargetValue(state, key, locale, value, clock = systemClock) {
|
|
640
640
|
const entry = requireKey(state, key);
|
|
641
641
|
if (entry.plural) throw new GlotfileError(`Key is a plural; use the plural setters: ${key}`);
|
|
642
|
-
entry.values[locale] = { value: value.trim(), state: "reviewed", updatedAt: clock() };
|
|
642
|
+
entry.values[canonLocale(locale)] = { value: value.trim(), state: "reviewed", updatedAt: clock() };
|
|
643
643
|
}
|
|
644
644
|
function formSignature(forms) {
|
|
645
645
|
return Object.entries(forms).sort(([a], [b]) => a.localeCompare(b)).map(([cat, val]) => `${cat}:${normalizeSource(val ?? "")}`).join("|");
|
|
@@ -662,8 +662,9 @@ function setSourcePluralForms(state, key, forms) {
|
|
|
662
662
|
}
|
|
663
663
|
function setPluralForms(state, key, locale, forms, clock = systemClock) {
|
|
664
664
|
const entry = requirePlural(state, key);
|
|
665
|
-
|
|
666
|
-
|
|
665
|
+
const loc = canonLocale(locale);
|
|
666
|
+
if (loc === state.config.sourceLocale) throw new GlotfileError("Use setSourcePluralForms for the source locale");
|
|
667
|
+
entry.values[loc] = { forms: normalizeForms(forms), state: "reviewed", updatedAt: clock() };
|
|
667
668
|
}
|
|
668
669
|
function convertToPlural(state, key, arg) {
|
|
669
670
|
const entry = requireKey(state, key);
|
|
@@ -691,12 +692,15 @@ function convertToScalar(state, key) {
|
|
|
691
692
|
}
|
|
692
693
|
function clearValue(state, key, locale) {
|
|
693
694
|
const entry = requireKey(state, key);
|
|
694
|
-
|
|
695
|
-
|
|
695
|
+
const loc = canonLocale(locale);
|
|
696
|
+
if (loc === state.config.sourceLocale) throw new GlotfileError("Cannot clear the source value");
|
|
697
|
+
delete entry.values[loc];
|
|
696
698
|
}
|
|
697
699
|
function setKeyState(state, key, locale, next) {
|
|
700
|
+
if (!STATES.includes(next)) throw new GlotfileError(`Unknown translation state: ${next}`);
|
|
698
701
|
const entry = requireKey(state, key);
|
|
699
|
-
const
|
|
702
|
+
const loc = canonLocale(locale);
|
|
703
|
+
const lv = entry.values[loc];
|
|
700
704
|
if (!lv) throw new GlotfileError(`No value for ${key} @ ${locale}`);
|
|
701
705
|
lv.state = next;
|
|
702
706
|
}
|
|
@@ -769,14 +773,16 @@ function removeCustomWord(state, word) {
|
|
|
769
773
|
function applyMachineTranslation(state, key, locale, value, clock = systemClock, force = false) {
|
|
770
774
|
const entry = requireKey(state, key);
|
|
771
775
|
if (entry.plural) throw new GlotfileError(`Key is a plural; use applyMachineTranslationForms: ${key}`);
|
|
772
|
-
|
|
773
|
-
entry.values[
|
|
776
|
+
const loc = canonLocale(locale);
|
|
777
|
+
if (!force && entry.values[loc]?.state === "reviewed") return false;
|
|
778
|
+
entry.values[loc] = { value: value.trim(), state: "machine", source: "ai", updatedAt: clock() };
|
|
774
779
|
return true;
|
|
775
780
|
}
|
|
776
781
|
function applyMachineTranslationForms(state, key, locale, forms, clock = systemClock, force = false) {
|
|
777
782
|
const entry = requirePlural(state, key);
|
|
778
|
-
|
|
779
|
-
entry.values[
|
|
783
|
+
const loc = canonLocale(locale);
|
|
784
|
+
if (!force && entry.values[loc]?.state === "reviewed") return false;
|
|
785
|
+
entry.values[loc] = { forms: normalizeForms(forms), state: "machine", source: "ai", updatedAt: clock() };
|
|
780
786
|
return true;
|
|
781
787
|
}
|
|
782
788
|
|
|
@@ -905,7 +911,21 @@ var PATTERNS = {
|
|
|
905
911
|
// t('key') — word boundary before t, not preceded by dot (excludes i18n.t which is above)
|
|
906
912
|
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*'([^']+)'/g,
|
|
907
913
|
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*"([^"]+)"/g,
|
|
908
|
-
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$\n]+)`/g
|
|
914
|
+
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$\n]+)`/g,
|
|
915
|
+
// vue-i18n pluralization: $tc('key') and the destructured bare tc('key').
|
|
916
|
+
/\$tc\s*\(\s*'([^']+)'/g,
|
|
917
|
+
/\$tc\s*\(\s*"([^"]+)"/g,
|
|
918
|
+
/(?<!\.)(?<![a-zA-Z0-9_$])\btc\s*\(\s*'([^']+)'/g,
|
|
919
|
+
/(?<!\.)(?<![a-zA-Z0-9_$])\btc\s*\(\s*"([^"]+)"/g,
|
|
920
|
+
// React-i18next <Trans i18nKey="key" /> (attribute order tolerated).
|
|
921
|
+
/<Trans\b[^>]*\bi18nKey\s*=\s*'([^']+)'/g,
|
|
922
|
+
/<Trans\b[^>]*\bi18nKey\s*=\s*"([^"]+)"/g,
|
|
923
|
+
// A renamed translate() wrapper (covers the common `const { t: translate }`
|
|
924
|
+
// alias by name; arbitrary aliases aren't resolved). Method `.translate()`
|
|
925
|
+
// is excluded. Over-matching here only keeps keys "used" — the safe direction
|
|
926
|
+
// for prune, which deletes only keys with no match at all.
|
|
927
|
+
/(?<!\.)(?<![a-zA-Z0-9_$])\btranslate\s*\(\s*'([^']+)'/g,
|
|
928
|
+
/(?<!\.)(?<![a-zA-Z0-9_$])\btranslate\s*\(\s*"([^"]+)"/g
|
|
909
929
|
],
|
|
910
930
|
gettext: [
|
|
911
931
|
/\b(?:gettext|ngettext)\s*\(\s*'([^']+)'/g,
|
|
@@ -939,7 +959,7 @@ var PREFIX_PATTERNS = {
|
|
|
939
959
|
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$]*)\$\{/g
|
|
940
960
|
]
|
|
941
961
|
};
|
|
942
|
-
var CACHE_VERSION =
|
|
962
|
+
var CACHE_VERSION = 7;
|
|
943
963
|
var EXT_SCANNER = {
|
|
944
964
|
".php": "laravel",
|
|
945
965
|
".vue": "js-i18n",
|
|
@@ -2300,28 +2320,33 @@ var i18nextJson = {
|
|
|
2300
2320
|
export(state, output) {
|
|
2301
2321
|
const files = [];
|
|
2302
2322
|
const warnings = [];
|
|
2303
|
-
const
|
|
2323
|
+
const { indent, finalNewline } = resolveFormat(state, output);
|
|
2324
|
+
const fmt = { indent, sortKeys: true, finalNewline };
|
|
2325
|
+
const emptyAs = resolveEmptyAs(output, "omit");
|
|
2304
2326
|
const collided = /* @__PURE__ */ new Set();
|
|
2305
2327
|
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE3));
|
|
2306
2328
|
for (const locale of state.config.locales) {
|
|
2307
2329
|
const obj = {};
|
|
2308
2330
|
for (const [key, entry] of Object.entries(state.keys)) {
|
|
2309
|
-
const lv = entry.values[locale];
|
|
2310
|
-
if (!lv) continue;
|
|
2311
2331
|
const segments = key.split(".");
|
|
2312
2332
|
const leaf = segments[segments.length - 1];
|
|
2313
2333
|
const parent = segments.slice(0, -1);
|
|
2314
2334
|
if (entry.plural) {
|
|
2315
|
-
|
|
2335
|
+
const forms = resolveForms(entry, locale, state.config.sourceLocale, emptyAs);
|
|
2336
|
+
if (!forms) continue;
|
|
2316
2337
|
for (const cat of PLURAL_CATEGORIES) {
|
|
2317
|
-
const body =
|
|
2338
|
+
const body = forms[cat];
|
|
2318
2339
|
if (body === void 0) continue;
|
|
2319
2340
|
if (setNested(obj, [...parent, `${leaf}_${cat}`], toI18next(body))) collided.add(key);
|
|
2320
2341
|
}
|
|
2321
2342
|
continue;
|
|
2322
2343
|
}
|
|
2323
|
-
|
|
2324
|
-
if (
|
|
2344
|
+
const raw = resolveScalar(entry, locale, state.config.sourceLocale, emptyAs);
|
|
2345
|
+
if (raw === null) continue;
|
|
2346
|
+
if (raw && isIcuPluralOrSelect(raw)) {
|
|
2347
|
+
warnings.push({ code: "lossy-plural", key, locale, message: "i18next-json does not yet convert ICU plural/select; written unconverted" });
|
|
2348
|
+
}
|
|
2349
|
+
if (setNested(obj, segments, toI18next(raw))) collided.add(key);
|
|
2325
2350
|
}
|
|
2326
2351
|
files.push({ path: resolvePath(output.path, resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE3)), contents: serializeJson(obj, fmt) });
|
|
2327
2352
|
}
|
|
@@ -2842,7 +2867,7 @@ function checkOutputs(state, root) {
|
|
|
2842
2867
|
}
|
|
2843
2868
|
|
|
2844
2869
|
// src/server/api.ts
|
|
2845
|
-
import { readFileSync as readFileSync23, existsSync as existsSync13, readdirSync as readdirSync15, statSync as
|
|
2870
|
+
import { readFileSync as readFileSync23, existsSync as existsSync13, readdirSync as readdirSync15, statSync as statSync10, rmSync as rmSync6 } from "fs";
|
|
2846
2871
|
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
2847
2872
|
|
|
2848
2873
|
// src/server/ai/anthropic.ts
|
|
@@ -3317,6 +3342,7 @@ var AnthropicProvider = class {
|
|
|
3317
3342
|
}, { signal });
|
|
3318
3343
|
this.recordUsage(res.usage);
|
|
3319
3344
|
const text = res.content.find((b) => b.type === "text")?.text ?? "";
|
|
3345
|
+
if (res.stop_reason === "max_tokens") throw new MalformedReplyError(text);
|
|
3320
3346
|
return parseReplyItems(text);
|
|
3321
3347
|
}
|
|
3322
3348
|
};
|
|
@@ -3402,6 +3428,7 @@ var OpenAIProvider = class {
|
|
|
3402
3428
|
]
|
|
3403
3429
|
}, { signal });
|
|
3404
3430
|
const text = res.choices?.[0]?.message?.content ?? "";
|
|
3431
|
+
if (res.choices?.[0]?.finish_reason === "length") throw new MalformedReplyError(text);
|
|
3405
3432
|
return parseReplyItems(text);
|
|
3406
3433
|
}
|
|
3407
3434
|
};
|
|
@@ -3511,8 +3538,11 @@ var BedrockProvider = class {
|
|
|
3511
3538
|
const res = await this.client.send(this.makeCommand(this.buildInput(batch)), { abortSignal: signal });
|
|
3512
3539
|
const blocks = res.output?.message?.content ?? [];
|
|
3513
3540
|
const tool = blocks.find((b) => b.toolUse)?.toolUse;
|
|
3514
|
-
if (tool?.input?.items) return tool.input.items;
|
|
3515
3541
|
const text = blocks.find((b) => b.text)?.text ?? "";
|
|
3542
|
+
if (res.stopReason === "max_tokens") {
|
|
3543
|
+
throw new MalformedReplyError(text || JSON.stringify(tool?.input ?? {}));
|
|
3544
|
+
}
|
|
3545
|
+
if (tool?.input?.items) return tool.input.items;
|
|
3516
3546
|
return parseReplyItems(text);
|
|
3517
3547
|
}
|
|
3518
3548
|
};
|
|
@@ -3778,8 +3808,30 @@ function attachScreenshotsForProvider(reqs, state, projectRoot, supportsVision)
|
|
|
3778
3808
|
return { skipped: keys.size };
|
|
3779
3809
|
}
|
|
3780
3810
|
var DEFAULT_LOCALE_CONCURRENCY = 3;
|
|
3781
|
-
|
|
3811
|
+
function isTransientError(err) {
|
|
3812
|
+
const e = err;
|
|
3813
|
+
if (e && typeof e.status === "number") return e.status === 429 || e.status >= 500;
|
|
3814
|
+
const code = e?.code;
|
|
3815
|
+
return code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ECONNREFUSED" || code === "EPIPE" || code === "EAI_AGAIN";
|
|
3816
|
+
}
|
|
3817
|
+
function sleep(ms, signal) {
|
|
3818
|
+
if (ms <= 0 || signal?.aborted) return Promise.resolve();
|
|
3819
|
+
return new Promise((resolve11) => {
|
|
3820
|
+
const t = setTimeout(() => {
|
|
3821
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3822
|
+
resolve11();
|
|
3823
|
+
}, ms);
|
|
3824
|
+
const onAbort = () => {
|
|
3825
|
+
clearTimeout(t);
|
|
3826
|
+
resolve11();
|
|
3827
|
+
};
|
|
3828
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
async function runLocaleParallel(reqs, provider, hooks = {}, concurrency = DEFAULT_LOCALE_CONCURRENCY, signal, batchSize = Infinity, retry = {}) {
|
|
3782
3832
|
if (!reqs.length) return [];
|
|
3833
|
+
const maxRetries = retry.retries ?? 3;
|
|
3834
|
+
const delayMs = retry.delayMs ?? ((attempt) => 250 * 2 ** attempt);
|
|
3783
3835
|
const byLocale = /* @__PURE__ */ new Map();
|
|
3784
3836
|
for (const req of reqs) {
|
|
3785
3837
|
let group = byLocale.get(req.targetLocale);
|
|
@@ -3814,10 +3866,20 @@ async function runLocaleParallel(reqs, provider, hooks = {}, concurrency = DEFAU
|
|
|
3814
3866
|
started.add(locale);
|
|
3815
3867
|
hooks.onLocaleStart?.(locale);
|
|
3816
3868
|
}
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3869
|
+
let batchResults;
|
|
3870
|
+
for (let attempt = 0; ; attempt++) {
|
|
3871
|
+
try {
|
|
3872
|
+
batchResults = await provider.translate(batch, (_localeDone, _localeTotal, results) => {
|
|
3873
|
+
done += results.length;
|
|
3874
|
+
hooks.onBatchComplete?.(done, total, results, locale);
|
|
3875
|
+
}, signal, (raw, size) => hooks.onMalformedReply?.(raw, size, locale));
|
|
3876
|
+
break;
|
|
3877
|
+
} catch (err) {
|
|
3878
|
+
if (attempt >= maxRetries || signal?.aborted || !isTransientError(err)) throw err;
|
|
3879
|
+
hooks.onRetry?.(locale, attempt + 1, err);
|
|
3880
|
+
await sleep(delayMs(attempt), signal);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3821
3883
|
allResults.push(...batchResults);
|
|
3822
3884
|
const left = remaining.get(locale) - 1;
|
|
3823
3885
|
remaining.set(locale, left);
|
|
@@ -3882,21 +3944,56 @@ function clearPendingBatch(projectRoot) {
|
|
|
3882
3944
|
}
|
|
3883
3945
|
|
|
3884
3946
|
// src/server/log.ts
|
|
3885
|
-
import { appendFileSync,
|
|
3947
|
+
import { appendFileSync, existsSync as existsSync9, openSync, fstatSync, readSync, closeSync, statSync as statSync2, readFileSync as readFileSync9 } from "fs";
|
|
3886
3948
|
import { resolve as resolve6 } from "path";
|
|
3887
3949
|
function logPath(projectRoot) {
|
|
3888
3950
|
return resolve6(projectRoot, ".glotfile", "log.jsonl");
|
|
3889
3951
|
}
|
|
3952
|
+
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
3953
|
+
var TRIM_LOG_TO_BYTES = 4 * 1024 * 1024;
|
|
3890
3954
|
function appendLog(projectRoot, entry) {
|
|
3891
3955
|
ensureGlotfileDir(projectRoot);
|
|
3892
|
-
appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
|
|
3893
|
-
}
|
|
3894
|
-
function readLog(projectRoot, limit = 100) {
|
|
3895
3956
|
const path = logPath(projectRoot);
|
|
3896
|
-
|
|
3957
|
+
appendFileSync(path, JSON.stringify(entry) + "\n", "utf8");
|
|
3958
|
+
trimLog(path);
|
|
3959
|
+
}
|
|
3960
|
+
function trimLog(path, maxBytes = MAX_LOG_BYTES, targetBytes = TRIM_LOG_TO_BYTES) {
|
|
3961
|
+
if (!existsSync9(path) || statSync2(path).size <= maxBytes) return;
|
|
3897
3962
|
const lines = readFileSync9(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3963
|
+
const kept = [];
|
|
3964
|
+
let bytes = 0;
|
|
3965
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3966
|
+
const lineBytes = Buffer.byteLength(lines[i], "utf8") + 1;
|
|
3967
|
+
if (kept.length > 0 && bytes + lineBytes > targetBytes) break;
|
|
3968
|
+
kept.unshift(lines[i]);
|
|
3969
|
+
bytes += lineBytes;
|
|
3970
|
+
}
|
|
3971
|
+
writeFileAtomic(path, kept.length ? kept.join("\n") + "\n" : "");
|
|
3972
|
+
}
|
|
3973
|
+
function readLastLines(path, n, chunkSize = 64 * 1024) {
|
|
3974
|
+
if (n <= 0 || !existsSync9(path)) return [];
|
|
3975
|
+
const fd = openSync(path, "r");
|
|
3976
|
+
try {
|
|
3977
|
+
let pos = fstatSync(fd).size;
|
|
3978
|
+
if (pos === 0) return [];
|
|
3979
|
+
const chunks = [];
|
|
3980
|
+
while (pos > 0) {
|
|
3981
|
+
const size = Math.min(chunkSize, pos);
|
|
3982
|
+
pos -= size;
|
|
3983
|
+
const buf = Buffer.alloc(size);
|
|
3984
|
+
readSync(fd, buf, 0, size, pos);
|
|
3985
|
+
chunks.unshift(buf);
|
|
3986
|
+
const segments = Buffer.concat(chunks).toString("utf8").split("\n");
|
|
3987
|
+
const complete = (pos > 0 ? segments.slice(1) : segments).filter((l) => l.trim() !== "");
|
|
3988
|
+
if (complete.length >= n || pos === 0) return complete.slice(-n);
|
|
3989
|
+
}
|
|
3990
|
+
return [];
|
|
3991
|
+
} finally {
|
|
3992
|
+
closeSync(fd);
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
function readLog(projectRoot, limit = 100) {
|
|
3996
|
+
return readLastLines(logPath(projectRoot), limit).map((l) => JSON.parse(l)).reverse();
|
|
3900
3997
|
}
|
|
3901
3998
|
|
|
3902
3999
|
// src/server/ai/batch-run.ts
|
|
@@ -4223,13 +4320,13 @@ function estimateTranslation(state, ai, opts) {
|
|
|
4223
4320
|
import { relative as relative3 } from "path";
|
|
4224
4321
|
|
|
4225
4322
|
// src/server/import/detect.ts
|
|
4226
|
-
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as
|
|
4323
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
4227
4324
|
import { join as join6 } from "path";
|
|
4228
4325
|
var LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4229
4326
|
var VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
|
|
4230
4327
|
function safeIsDir(p) {
|
|
4231
4328
|
try {
|
|
4232
|
-
return
|
|
4329
|
+
return statSync3(p).isDirectory();
|
|
4233
4330
|
} catch {
|
|
4234
4331
|
return false;
|
|
4235
4332
|
}
|
|
@@ -4265,7 +4362,7 @@ function detectVue(root, forced = false) {
|
|
|
4265
4362
|
if (enough) {
|
|
4266
4363
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
4267
4364
|
try {
|
|
4268
|
-
return
|
|
4365
|
+
return statSync3(join6(localeRoot, `${loc}.json`)).size;
|
|
4269
4366
|
} catch {
|
|
4270
4367
|
return 0;
|
|
4271
4368
|
}
|
|
@@ -4302,7 +4399,7 @@ function detectApple(root) {
|
|
|
4302
4399
|
locales,
|
|
4303
4400
|
sourceLocale: pickSource(locales, (loc) => {
|
|
4304
4401
|
try {
|
|
4305
|
-
return
|
|
4402
|
+
return statSync3(join6(dir, `${loc}.lproj`, "Localizable.strings")).size;
|
|
4306
4403
|
} catch {
|
|
4307
4404
|
return 0;
|
|
4308
4405
|
}
|
|
@@ -4364,7 +4461,7 @@ function detectI18next(root) {
|
|
|
4364
4461
|
if (locales.length === 0) continue;
|
|
4365
4462
|
const sourceLocale = pickSource(locales, (loc) => {
|
|
4366
4463
|
try {
|
|
4367
|
-
return readdirSync3(join6(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum +
|
|
4464
|
+
return readdirSync3(join6(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync3(join6(localeRoot, loc, f)).size, 0);
|
|
4368
4465
|
} catch {
|
|
4369
4466
|
return 0;
|
|
4370
4467
|
}
|
|
@@ -4463,10 +4560,9 @@ import { join as join7 } from "path";
|
|
|
4463
4560
|
function flattenObject(value, prefix, warnings) {
|
|
4464
4561
|
const out = {};
|
|
4465
4562
|
const walk = (node, path) => {
|
|
4466
|
-
if (typeof node === "string") {
|
|
4467
|
-
out
|
|
4468
|
-
|
|
4469
|
-
out[path] = String(node);
|
|
4563
|
+
if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
|
|
4564
|
+
if (path in out) warnings.push(`duplicate flattened key "${path}" \u2014 keeping the first value`);
|
|
4565
|
+
else out[path] = typeof node === "string" ? node : String(node);
|
|
4470
4566
|
} else if (Array.isArray(node)) {
|
|
4471
4567
|
node.forEach((el, i) => walk(el, path ? `${path}.${i}` : String(i)));
|
|
4472
4568
|
} else if (node && typeof node === "object") {
|
|
@@ -4511,7 +4607,7 @@ var vueI18nJson2 = {
|
|
|
4511
4607
|
};
|
|
4512
4608
|
|
|
4513
4609
|
// src/server/import/parsers/laravel-php.ts
|
|
4514
|
-
import { readdirSync as readdirSync5, statSync as
|
|
4610
|
+
import { readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
4515
4611
|
import { join as join8, relative as relative2 } from "path";
|
|
4516
4612
|
import { execFileSync } from "child_process";
|
|
4517
4613
|
|
|
@@ -4522,14 +4618,14 @@ function laravelToCanonical(value) {
|
|
|
4522
4618
|
|
|
4523
4619
|
// src/server/import/parsers/laravel-php.ts
|
|
4524
4620
|
function listDirs2(dir) {
|
|
4525
|
-
return readdirSync5(dir).filter((e) =>
|
|
4621
|
+
return readdirSync5(dir).filter((e) => statSync4(join8(dir, e)).isDirectory());
|
|
4526
4622
|
}
|
|
4527
4623
|
function listPhpFiles(dir) {
|
|
4528
4624
|
const out = [];
|
|
4529
4625
|
const walk = (d) => {
|
|
4530
4626
|
for (const e of readdirSync5(d)) {
|
|
4531
4627
|
const full = join8(d, e);
|
|
4532
|
-
if (
|
|
4628
|
+
if (statSync4(full).isDirectory()) walk(full);
|
|
4533
4629
|
else if (e.endsWith(".php")) out.push(full);
|
|
4534
4630
|
}
|
|
4535
4631
|
};
|
|
@@ -4650,7 +4746,7 @@ var flutterArb2 = {
|
|
|
4650
4746
|
};
|
|
4651
4747
|
|
|
4652
4748
|
// src/server/import/parsers/apple-strings.ts
|
|
4653
|
-
import { readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as
|
|
4749
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
|
|
4654
4750
|
import { join as join10 } from "path";
|
|
4655
4751
|
var LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4656
4752
|
var TABLE = "Localizable.strings";
|
|
@@ -4719,25 +4815,34 @@ function parseStrings(text, file, warnings) {
|
|
|
4719
4815
|
while (i < n && !/[\s=;]/.test(text[i])) raw += text[i++];
|
|
4720
4816
|
return raw.length ? raw : null;
|
|
4721
4817
|
};
|
|
4818
|
+
const recover = () => {
|
|
4819
|
+
while (i < n && text[i] !== ";") i++;
|
|
4820
|
+
if (i >= n) return false;
|
|
4821
|
+
i++;
|
|
4822
|
+
return true;
|
|
4823
|
+
};
|
|
4722
4824
|
while (true) {
|
|
4723
4825
|
skipTrivia();
|
|
4724
4826
|
if (i >= n) break;
|
|
4725
4827
|
const key = readToken();
|
|
4726
4828
|
if (key === null) {
|
|
4727
4829
|
warnings.push(`apple-strings: malformed entry in ${file} near offset ${i}`);
|
|
4728
|
-
break;
|
|
4830
|
+
if (!recover()) break;
|
|
4831
|
+
continue;
|
|
4729
4832
|
}
|
|
4730
4833
|
skipTrivia();
|
|
4731
4834
|
if (text[i] !== "=") {
|
|
4732
4835
|
warnings.push(`apple-strings: expected '=' after key "${key}" in ${file}`);
|
|
4733
|
-
break;
|
|
4836
|
+
if (!recover()) break;
|
|
4837
|
+
continue;
|
|
4734
4838
|
}
|
|
4735
4839
|
i++;
|
|
4736
4840
|
skipTrivia();
|
|
4737
4841
|
const value = readToken();
|
|
4738
4842
|
if (value === null) {
|
|
4739
4843
|
warnings.push(`apple-strings: missing value for key "${key}" in ${file}`);
|
|
4740
|
-
break;
|
|
4844
|
+
if (!recover()) break;
|
|
4845
|
+
continue;
|
|
4741
4846
|
}
|
|
4742
4847
|
skipTrivia();
|
|
4743
4848
|
if (text[i] === ";") i++;
|
|
@@ -4758,7 +4863,7 @@ var appleStrings2 = {
|
|
|
4758
4863
|
const file = join10(localeRoot, dir, TABLE);
|
|
4759
4864
|
let text;
|
|
4760
4865
|
try {
|
|
4761
|
-
if (!
|
|
4866
|
+
if (!statSync5(file).isFile()) continue;
|
|
4762
4867
|
text = readFileSync14(file, "utf8");
|
|
4763
4868
|
} catch {
|
|
4764
4869
|
continue;
|
|
@@ -5036,7 +5141,7 @@ var gettextPo2 = {
|
|
|
5036
5141
|
};
|
|
5037
5142
|
|
|
5038
5143
|
// src/server/import/parsers/i18next-json.ts
|
|
5039
|
-
import { readdirSync as readdirSync10, readFileSync as readFileSync17, statSync as
|
|
5144
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync17, statSync as statSync6 } from "fs";
|
|
5040
5145
|
import { join as join13 } from "path";
|
|
5041
5146
|
var LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5042
5147
|
var PLURAL_SUFFIX_RE = /^(.+)_(zero|one|two|few|many|other)$/;
|
|
@@ -5044,7 +5149,7 @@ var PLURAL_ARG = "count";
|
|
|
5044
5149
|
var DEFAULT_NAMESPACE = "translation";
|
|
5045
5150
|
function safeIsDir2(p) {
|
|
5046
5151
|
try {
|
|
5047
|
-
return
|
|
5152
|
+
return statSync6(p).isDirectory();
|
|
5048
5153
|
} catch {
|
|
5049
5154
|
return false;
|
|
5050
5155
|
}
|
|
@@ -5144,6 +5249,15 @@ function decodeDouble(body) {
|
|
|
5144
5249
|
}
|
|
5145
5250
|
const n = body[++i];
|
|
5146
5251
|
if (n === void 0) break;
|
|
5252
|
+
const hexLen = n === "x" ? 2 : n === "u" ? 4 : n === "U" ? 8 : 0;
|
|
5253
|
+
if (hexLen) {
|
|
5254
|
+
const hex = body.slice(i + 1, i + 1 + hexLen);
|
|
5255
|
+
if (hex.length === hexLen && /^[0-9a-fA-F]+$/.test(hex)) {
|
|
5256
|
+
out += String.fromCodePoint(parseInt(hex, 16));
|
|
5257
|
+
i += hexLen;
|
|
5258
|
+
continue;
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5147
5261
|
out += n === "n" ? "\n" : n === "r" ? "\r" : n === "t" ? " " : n;
|
|
5148
5262
|
}
|
|
5149
5263
|
return out;
|
|
@@ -5358,7 +5472,7 @@ var railsYaml2 = {
|
|
|
5358
5472
|
};
|
|
5359
5473
|
|
|
5360
5474
|
// src/server/import/parsers/apple-stringsdict.ts
|
|
5361
|
-
import { readdirSync as readdirSync12, readFileSync as readFileSync19, statSync as
|
|
5475
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
|
|
5362
5476
|
import { join as join15 } from "path";
|
|
5363
5477
|
var LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5364
5478
|
var TABLE2 = "Localizable.stringsdict";
|
|
@@ -5498,7 +5612,7 @@ var appleStringsdict2 = {
|
|
|
5498
5612
|
const file = join15(localeRoot, dir, TABLE2);
|
|
5499
5613
|
let text;
|
|
5500
5614
|
try {
|
|
5501
|
-
if (!
|
|
5615
|
+
if (!statSync7(file).isFile()) continue;
|
|
5502
5616
|
text = readFileSync19(file, "utf8");
|
|
5503
5617
|
} catch {
|
|
5504
5618
|
continue;
|
|
@@ -5820,7 +5934,7 @@ function refreshLocationUsage(projectRoot, format) {
|
|
|
5820
5934
|
}
|
|
5821
5935
|
|
|
5822
5936
|
// src/server/export-run.ts
|
|
5823
|
-
import { existsSync as existsSync12, readFileSync as readFileSync20, readdirSync as readdirSync13, rmdirSync, statSync as
|
|
5937
|
+
import { existsSync as existsSync12, readFileSync as readFileSync20, readdirSync as readdirSync13, rmdirSync, statSync as statSync8, unlinkSync } from "fs";
|
|
5824
5938
|
import { dirname as dirname2, resolve as resolve7, sep } from "path";
|
|
5825
5939
|
function effectiveLocales(config) {
|
|
5826
5940
|
const limit = config.exportLocales;
|
|
@@ -5863,7 +5977,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
5863
5977
|
if (!segment.includes("{locale}") && !segment.includes("{namespace}")) {
|
|
5864
5978
|
const next = resolve7(dir, segment);
|
|
5865
5979
|
if (isLast) {
|
|
5866
|
-
if (stale(locale) && existsSync12(next) &&
|
|
5980
|
+
if (stale(locale) && existsSync12(next) && statSync8(next).isFile()) {
|
|
5867
5981
|
unlinkSync(next);
|
|
5868
5982
|
deleted++;
|
|
5869
5983
|
removeEmptyDirs(dir, root);
|
|
@@ -6069,7 +6183,7 @@ function createEventHub() {
|
|
|
6069
6183
|
}
|
|
6070
6184
|
|
|
6071
6185
|
// src/server/watch.ts
|
|
6072
|
-
import { statSync as
|
|
6186
|
+
import { statSync as statSync9, readdirSync as readdirSync14 } from "fs";
|
|
6073
6187
|
import { join as join17 } from "path";
|
|
6074
6188
|
import { createHash as createHash2 } from "crypto";
|
|
6075
6189
|
function hashState(state) {
|
|
@@ -6079,14 +6193,14 @@ function signature(statePath) {
|
|
|
6079
6193
|
const fmt = detectFormat(statePath);
|
|
6080
6194
|
if (fmt === "none") return "none";
|
|
6081
6195
|
if (fmt === "single") {
|
|
6082
|
-
const s =
|
|
6196
|
+
const s = statSync9(statePath);
|
|
6083
6197
|
return `single:${s.size}:${s.mtimeMs}`;
|
|
6084
6198
|
}
|
|
6085
6199
|
const dir = splitDirFor(statePath);
|
|
6086
6200
|
const parts = [];
|
|
6087
6201
|
for (const rel of ["config.json", "keys.json"]) {
|
|
6088
6202
|
try {
|
|
6089
|
-
const s =
|
|
6203
|
+
const s = statSync9(join17(dir, rel));
|
|
6090
6204
|
parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
|
|
6091
6205
|
} catch {
|
|
6092
6206
|
}
|
|
@@ -6094,7 +6208,7 @@ function signature(statePath) {
|
|
|
6094
6208
|
try {
|
|
6095
6209
|
for (const name of readdirSync14(join17(dir, "locales")).sort()) {
|
|
6096
6210
|
if (!name.endsWith(".json")) continue;
|
|
6097
|
-
const s =
|
|
6211
|
+
const s = statSync9(join17(dir, "locales", name));
|
|
6098
6212
|
parts.push(`${name}:${s.size}:${s.mtimeMs}`);
|
|
6099
6213
|
}
|
|
6100
6214
|
} catch {
|
|
@@ -6349,7 +6463,7 @@ function createApi(deps) {
|
|
|
6349
6463
|
filePath = abs;
|
|
6350
6464
|
} else {
|
|
6351
6465
|
try {
|
|
6352
|
-
if (
|
|
6466
|
+
if (statSync10(abs).isDirectory()) walk(abs, depth + 1);
|
|
6353
6467
|
} catch {
|
|
6354
6468
|
}
|
|
6355
6469
|
continue;
|
|
@@ -7381,8 +7495,16 @@ async function readFileResponse(absPath) {
|
|
|
7381
7495
|
return null;
|
|
7382
7496
|
}
|
|
7383
7497
|
}
|
|
7498
|
+
function isLocalHost(hostname) {
|
|
7499
|
+
const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
7500
|
+
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost");
|
|
7501
|
+
}
|
|
7384
7502
|
function buildApp(opts) {
|
|
7385
7503
|
const app = new Hono2();
|
|
7504
|
+
app.use("*", async (c, next) => {
|
|
7505
|
+
if (!isLocalHost(new URL(c.req.url).hostname)) return c.text("Forbidden: non-local Host header", 403);
|
|
7506
|
+
return next();
|
|
7507
|
+
});
|
|
7386
7508
|
const apiDeps = {
|
|
7387
7509
|
statePath: opts.statePath,
|
|
7388
7510
|
autoExport: true,
|