glotfile 0.5.4 → 0.6.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 +1032 -47
- package/dist/server/server.js +980 -45
- package/dist/ui/assets/{index-BGtqXjLQ.js → index-Vn-AfOXc.js} +13 -5
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +6 -1
- package/skill/references/cli-reference.md +6 -0
- package/skill/references/workflows.md +27 -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 join15, 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";
|
|
@@ -121,6 +121,7 @@ function validate(raw) {
|
|
|
121
121
|
if (o.indent !== void 0 && typeof o.indent !== "number") fail("config.outputs[].indent must be a number");
|
|
122
122
|
if (o.finalNewline !== void 0 && typeof o.finalNewline !== "boolean") fail("config.outputs[].finalNewline must be a boolean");
|
|
123
123
|
if (o.includeLocale !== void 0 && typeof o.includeLocale !== "boolean") fail("config.outputs[].includeLocale must be a boolean");
|
|
124
|
+
if (o.skipSourceLocale !== void 0 && typeof o.skipSourceLocale !== "boolean") fail("config.outputs[].skipSourceLocale must be a boolean");
|
|
124
125
|
if (o.localeAliases !== void 0) {
|
|
125
126
|
if (!isObject(o.localeAliases)) fail("config.outputs[].localeAliases must be an object");
|
|
126
127
|
for (const [k, v] of Object.entries(o.localeAliases)) {
|
|
@@ -2535,27 +2536,41 @@ var vueI18nJson = {
|
|
|
2535
2536
|
function xmlEscape2(s) {
|
|
2536
2537
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2537
2538
|
}
|
|
2538
|
-
function
|
|
2539
|
+
function attrEscape(s) {
|
|
2540
|
+
return xmlEscape2(s).replace(/"/g, """);
|
|
2541
|
+
}
|
|
2542
|
+
function angularXMeta(placeholders, name) {
|
|
2543
|
+
return /^[A-Z][A-Z0-9_]*$/.test(name) ? placeholders?.[name] : void 0;
|
|
2544
|
+
}
|
|
2545
|
+
function renderInterpolations(text, ids, placeholders) {
|
|
2539
2546
|
let out = "";
|
|
2540
2547
|
let last = 0;
|
|
2541
2548
|
for (const m of text.matchAll(/\{(\w+)\}/g)) {
|
|
2542
2549
|
const name = m[1];
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2550
|
+
out += xmlEscape2(text.slice(last, m.index));
|
|
2551
|
+
const meta = angularXMeta(placeholders, name);
|
|
2552
|
+
if (meta) {
|
|
2553
|
+
const ctype = meta.type ? ` ctype="${attrEscape(meta.type)}"` : "";
|
|
2554
|
+
const equiv = meta.example !== void 0 ? ` equiv-text="${attrEscape(meta.example)}"` : "";
|
|
2555
|
+
out += `<x id="${attrEscape(name)}"${ctype}${equiv}/>`;
|
|
2556
|
+
} else {
|
|
2557
|
+
let id = ids.get(name);
|
|
2558
|
+
if (id === void 0) {
|
|
2559
|
+
id = ids.size === 0 ? "INTERPOLATION" : `INTERPOLATION_${ids.size}`;
|
|
2560
|
+
ids.set(name, id);
|
|
2561
|
+
}
|
|
2562
|
+
out += `<x id="${id}" equiv-text="{{${name}}}"/>`;
|
|
2547
2563
|
}
|
|
2548
|
-
out += xmlEscape2(text.slice(last, m.index)) + `<x id="${id}" equiv-text="{{${name}}}"/>`;
|
|
2549
2564
|
last = m.index + m[0].length;
|
|
2550
2565
|
}
|
|
2551
2566
|
return out + xmlEscape2(text.slice(last));
|
|
2552
2567
|
}
|
|
2553
|
-
function renderPluralIcu(forms, ids) {
|
|
2568
|
+
function renderPluralIcu(forms, ids, placeholders) {
|
|
2554
2569
|
const cats = [
|
|
2555
2570
|
...Object.keys(forms).filter((c) => c.startsWith("=")),
|
|
2556
2571
|
...PLURAL_CATEGORIES.filter((c) => forms[c] !== void 0)
|
|
2557
2572
|
];
|
|
2558
|
-
const branches = cats.map((cat) => `${cat} {${renderInterpolations(forms[cat] ?? "", ids)}}`);
|
|
2573
|
+
const branches = cats.map((cat) => `${cat} {${renderInterpolations(forms[cat] ?? "", ids, placeholders)}}`);
|
|
2559
2574
|
return `{VAR_PLURAL, plural, ${branches.join(" ")}}`;
|
|
2560
2575
|
}
|
|
2561
2576
|
function renderEmbeddedIcu(value) {
|
|
@@ -2565,8 +2580,8 @@ function renderEmbeddedIcu(value) {
|
|
|
2565
2580
|
);
|
|
2566
2581
|
return xmlEscape2(renamed);
|
|
2567
2582
|
}
|
|
2568
|
-
function renderScalar(value, ids) {
|
|
2569
|
-
return isIcuPluralOrSelect(value) ? renderEmbeddedIcu(value) : renderInterpolations(value, ids);
|
|
2583
|
+
function renderScalar(value, ids, placeholders) {
|
|
2584
|
+
return isIcuPluralOrSelect(value) ? renderEmbeddedIcu(value) : renderInterpolations(value, ids, placeholders);
|
|
2570
2585
|
}
|
|
2571
2586
|
var DEFAULT_LOCALE_CASE8 = "bcp47-hyphen";
|
|
2572
2587
|
var angularXliff = {
|
|
@@ -2589,6 +2604,7 @@ var angularXliff = {
|
|
|
2589
2604
|
const emptyAs = resolveEmptyAs(output, "source");
|
|
2590
2605
|
const keys = Object.keys(state.keys).sort();
|
|
2591
2606
|
for (const locale of state.config.locales) {
|
|
2607
|
+
if (output.skipSourceLocale && locale === sourceLocale) continue;
|
|
2592
2608
|
const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE8);
|
|
2593
2609
|
const units = [];
|
|
2594
2610
|
for (const key of keys) {
|
|
@@ -2599,17 +2615,18 @@ var angularXliff = {
|
|
|
2599
2615
|
if (entry.plural) {
|
|
2600
2616
|
const targetForms = resolveForms(entry, locale, sourceLocale, emptyAs);
|
|
2601
2617
|
if (targetForms === null) continue;
|
|
2602
|
-
source = renderPluralIcu(entry.values[sourceLocale]?.forms ?? {}, ids);
|
|
2603
|
-
target = renderPluralIcu(targetForms, ids);
|
|
2618
|
+
source = renderPluralIcu(entry.values[sourceLocale]?.forms ?? {}, ids, entry.placeholders);
|
|
2619
|
+
target = renderPluralIcu(targetForms, ids, entry.placeholders);
|
|
2604
2620
|
} else {
|
|
2605
2621
|
const targetValue = resolveScalar(entry, locale, sourceLocale, emptyAs);
|
|
2606
2622
|
if (targetValue === null) continue;
|
|
2607
|
-
source = renderScalar(entry.values[sourceLocale]?.value ?? "", ids);
|
|
2608
|
-
target = renderScalar(targetValue, ids);
|
|
2623
|
+
source = renderScalar(entry.values[sourceLocale]?.value ?? "", ids, entry.placeholders);
|
|
2624
|
+
target = renderScalar(targetValue, ids, entry.placeholders);
|
|
2609
2625
|
}
|
|
2626
|
+
const translated = locale === sourceLocale || (entry.plural ? entry.values[locale]?.forms !== void 0 : !!entry.values[locale]?.value);
|
|
2610
2627
|
units.push(` <trans-unit id="${xmlEscape2(key)}" datatype="html">`);
|
|
2611
2628
|
units.push(` <source>${source}</source>`);
|
|
2612
|
-
units.push(` <target>${target}</target>`);
|
|
2629
|
+
units.push(` <target${translated ? "" : ' state="new"'}>${target}</target>`);
|
|
2613
2630
|
if (entry.description) {
|
|
2614
2631
|
units.push(` <note priority="1" from="description">${xmlEscape2(entry.description)}</note>`);
|
|
2615
2632
|
}
|
|
@@ -2770,7 +2787,7 @@ function checkOutputs(state, root) {
|
|
|
2770
2787
|
}
|
|
2771
2788
|
|
|
2772
2789
|
// src/server/api.ts
|
|
2773
|
-
import { readFileSync as
|
|
2790
|
+
import { readFileSync as readFileSync21, existsSync as existsSync11, readdirSync as readdirSync14, statSync as statSync8, rmSync as rmSync4 } from "fs";
|
|
2774
2791
|
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
2775
2792
|
|
|
2776
2793
|
// src/server/ai/anthropic.ts
|
|
@@ -2789,7 +2806,7 @@ function buildSystemPrompt(hasPluralItems) {
|
|
|
2789
2806
|
"- Preserve ICU plural/select structure verbatim (e.g. {count, plural, one {\u2026} other {\u2026}}); translate only the human-readable text inside each branch.",
|
|
2790
2807
|
"- Glossary: a term marked do-not-translate MUST appear unchanged in the translation. A term with a forced translation for the target locale MUST use that exact translation.",
|
|
2791
2808
|
"- Respect the max length (characters) when given; prefer a shorter natural phrasing over exceeding it.",
|
|
2792
|
-
|
|
2809
|
+
'- Quotation marks and apostrophes: punctuate exactly as a professional native translator instinctively would for the target language \u2014 its typographic conventions (e.g. \u201EGerman\u201C, \xABFrench\xBB, \u201CEnglish\u201D, \u2019 for apostrophes), applied with judgment about what is quoted prose versus a literal that must stay untouched. Never emit a raw ASCII double-quote (") inside a translated string \u2014 it corrupts the JSON reply.',
|
|
2793
2810
|
"- Match the register and capitalization conventions of the target language and of UI microcopy.",
|
|
2794
2811
|
"- Return ONLY the translated string for each item \u2014 no quotes, notes, or explanations."
|
|
2795
2812
|
];
|
|
@@ -2881,12 +2898,66 @@ var MalformedReplyError = class extends Error {
|
|
|
2881
2898
|
}
|
|
2882
2899
|
raw;
|
|
2883
2900
|
};
|
|
2901
|
+
function repairUnescapedQuotes(text) {
|
|
2902
|
+
const skipWs = (from) => {
|
|
2903
|
+
let i = from;
|
|
2904
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
2905
|
+
return i;
|
|
2906
|
+
};
|
|
2907
|
+
const stack = [];
|
|
2908
|
+
let out = "";
|
|
2909
|
+
let inString = false;
|
|
2910
|
+
let isKey = false;
|
|
2911
|
+
for (let i = 0; i < text.length; i++) {
|
|
2912
|
+
const ch = text[i];
|
|
2913
|
+
const top = stack[stack.length - 1];
|
|
2914
|
+
if (inString) {
|
|
2915
|
+
if (ch === "\\") {
|
|
2916
|
+
out += ch + (text[i + 1] ?? "");
|
|
2917
|
+
i++;
|
|
2918
|
+
} else if (ch !== '"') {
|
|
2919
|
+
out += ch;
|
|
2920
|
+
} else {
|
|
2921
|
+
const next = text[skipWs(i + 1)];
|
|
2922
|
+
const startsNextMember = () => {
|
|
2923
|
+
const after = text[skipWs(skipWs(i + 1) + 1)];
|
|
2924
|
+
return top?.type === "obj" ? after === '"' : after === "{" || after === "[" || after === '"';
|
|
2925
|
+
};
|
|
2926
|
+
const closes = isKey ? next === ":" : next === "}" || next === "]" || next === void 0 || next === "," && startsNextMember();
|
|
2927
|
+
if (closes) {
|
|
2928
|
+
inString = false;
|
|
2929
|
+
out += ch;
|
|
2930
|
+
} else {
|
|
2931
|
+
out += '\\"';
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
out += ch;
|
|
2937
|
+
if (ch === '"') {
|
|
2938
|
+
inString = true;
|
|
2939
|
+
isKey = top?.type === "obj" && top.expectingKey;
|
|
2940
|
+
} else if (ch === "{") stack.push({ type: "obj", expectingKey: true });
|
|
2941
|
+
else if (ch === "[") stack.push({ type: "arr", expectingKey: false });
|
|
2942
|
+
else if (ch === "}" || ch === "]") stack.pop();
|
|
2943
|
+
else if (ch === "," && top?.type === "obj") top.expectingKey = true;
|
|
2944
|
+
else if (ch === ":" && top) top.expectingKey = false;
|
|
2945
|
+
}
|
|
2946
|
+
try {
|
|
2947
|
+
JSON.parse(out);
|
|
2948
|
+
return out;
|
|
2949
|
+
} catch {
|
|
2950
|
+
return void 0;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2884
2953
|
function parseReplyItems(text) {
|
|
2885
2954
|
let parsed;
|
|
2886
2955
|
try {
|
|
2887
2956
|
parsed = JSON.parse(text);
|
|
2888
2957
|
} catch {
|
|
2889
|
-
|
|
2958
|
+
const repaired = repairUnescapedQuotes(text);
|
|
2959
|
+
if (repaired === void 0) throw new MalformedReplyError(text);
|
|
2960
|
+
parsed = JSON.parse(repaired);
|
|
2890
2961
|
}
|
|
2891
2962
|
if (!Array.isArray(parsed.items)) throw new MalformedReplyError(text);
|
|
2892
2963
|
return parsed.items;
|
|
@@ -3675,7 +3746,7 @@ function readLog(projectRoot, limit = 100) {
|
|
|
3675
3746
|
import { relative as relative3 } from "path";
|
|
3676
3747
|
|
|
3677
3748
|
// src/server/import/detect.ts
|
|
3678
|
-
import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
3749
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
|
|
3679
3750
|
import { join as join4 } from "path";
|
|
3680
3751
|
var LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
3681
3752
|
var VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
|
|
@@ -3764,12 +3835,134 @@ function detectApple(root) {
|
|
|
3764
3835
|
}
|
|
3765
3836
|
return best;
|
|
3766
3837
|
}
|
|
3767
|
-
var
|
|
3838
|
+
var ANGULAR_DIR_CANDIDATES = [".", "src/locale", "src/locales", "src/i18n", "locale", "locales", "i18n", "translations"];
|
|
3839
|
+
function detectAngularXliff(root) {
|
|
3840
|
+
for (const rel of ANGULAR_DIR_CANDIDATES) {
|
|
3841
|
+
const localeRoot = rel === "." ? root : join4(root, rel);
|
|
3842
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
3843
|
+
const files = readdirSync3(localeRoot).filter((f) => /^messages(\..+)?\.xlf$/.test(f)).sort();
|
|
3844
|
+
if (files.length === 0) continue;
|
|
3845
|
+
const locales = files.map((f) => f.match(/^messages\.(.+)\.xlf$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l));
|
|
3846
|
+
const attrFile = files.includes("messages.xlf") ? "messages.xlf" : files[0];
|
|
3847
|
+
let sourceLocale;
|
|
3848
|
+
try {
|
|
3849
|
+
sourceLocale = readFileSync9(join4(localeRoot, attrFile), "utf8").match(/source-language="([^"]+)"/)?.[1];
|
|
3850
|
+
} catch {
|
|
3851
|
+
}
|
|
3852
|
+
if (!sourceLocale && locales.length === 0) continue;
|
|
3853
|
+
sourceLocale ??= pickSource(locales, () => 0);
|
|
3854
|
+
if (!locales.includes(sourceLocale)) locales.unshift(sourceLocale);
|
|
3855
|
+
return { format: "angular-xliff", localeRoot, locales, sourceLocale };
|
|
3856
|
+
}
|
|
3857
|
+
return null;
|
|
3858
|
+
}
|
|
3859
|
+
function detectRails(root) {
|
|
3860
|
+
const localeRoot = join4(root, "config", "locales");
|
|
3861
|
+
if (!safeIsDir(localeRoot)) return null;
|
|
3862
|
+
const locales = [];
|
|
3863
|
+
for (const file of readdirSync3(localeRoot).sort()) {
|
|
3864
|
+
if (!/\.ya?ml$/.test(file)) continue;
|
|
3865
|
+
let text;
|
|
3866
|
+
try {
|
|
3867
|
+
text = readFileSync9(join4(localeRoot, file), "utf8");
|
|
3868
|
+
} catch {
|
|
3869
|
+
continue;
|
|
3870
|
+
}
|
|
3871
|
+
for (const m of text.matchAll(/^(["']?)([A-Za-z][\w-]*)\1:\s*(?:#.*)?$/gm)) {
|
|
3872
|
+
const token = m[2];
|
|
3873
|
+
if (LOCALE_RE.test(token) && !locales.includes(token)) locales.push(token);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
if (locales.length === 0) return null;
|
|
3877
|
+
return { format: "rails-yaml", localeRoot, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
3878
|
+
}
|
|
3879
|
+
var I18NEXT_DIR_CANDIDATES = ["public/locales", "static/locales", "locales", "src/locales", "src/i18n/locales"];
|
|
3880
|
+
function detectI18next(root) {
|
|
3881
|
+
for (const rel of I18NEXT_DIR_CANDIDATES) {
|
|
3882
|
+
const localeRoot = join4(root, rel);
|
|
3883
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
3884
|
+
const locales = listDirs(localeRoot).filter(
|
|
3885
|
+
(d) => LOCALE_RE.test(d) && readdirSync3(join4(localeRoot, d)).some((f) => f.endsWith(".json"))
|
|
3886
|
+
);
|
|
3887
|
+
if (locales.length === 0) continue;
|
|
3888
|
+
const sourceLocale = pickSource(locales, (loc) => {
|
|
3889
|
+
try {
|
|
3890
|
+
return readdirSync3(join4(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync2(join4(localeRoot, loc, f)).size, 0);
|
|
3891
|
+
} catch {
|
|
3892
|
+
return 0;
|
|
3893
|
+
}
|
|
3894
|
+
});
|
|
3895
|
+
return { format: "i18next-json", localeRoot, locales, sourceLocale };
|
|
3896
|
+
}
|
|
3897
|
+
return null;
|
|
3898
|
+
}
|
|
3899
|
+
function gettextLocales(dir) {
|
|
3900
|
+
const locales = [];
|
|
3901
|
+
for (const entry of readdirSync3(dir).sort()) {
|
|
3902
|
+
const flat = entry.match(/^(.+)\.po$/)?.[1];
|
|
3903
|
+
if (flat && LOCALE_RE.test(flat)) {
|
|
3904
|
+
if (!locales.includes(flat)) locales.push(flat);
|
|
3905
|
+
continue;
|
|
3906
|
+
}
|
|
3907
|
+
if (!LOCALE_RE.test(entry) || !safeIsDir(join4(dir, entry))) continue;
|
|
3908
|
+
const sub = join4(dir, entry);
|
|
3909
|
+
const hasPo = (d) => {
|
|
3910
|
+
try {
|
|
3911
|
+
return readdirSync3(d).some((f) => f.endsWith(".po"));
|
|
3912
|
+
} catch {
|
|
3913
|
+
return false;
|
|
3914
|
+
}
|
|
3915
|
+
};
|
|
3916
|
+
if (hasPo(join4(sub, "LC_MESSAGES")) || hasPo(sub)) {
|
|
3917
|
+
if (!locales.includes(entry)) locales.push(entry);
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
return locales;
|
|
3921
|
+
}
|
|
3922
|
+
var GETTEXT_DIR_CANDIDATES = ["locale", "locales", "po", "translations"];
|
|
3923
|
+
function detectGettext(root) {
|
|
3924
|
+
for (const rel of GETTEXT_DIR_CANDIDATES) {
|
|
3925
|
+
const localeRoot = join4(root, rel);
|
|
3926
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
3927
|
+
const locales = gettextLocales(localeRoot);
|
|
3928
|
+
if (locales.length === 0) continue;
|
|
3929
|
+
return { format: "gettext-po", localeRoot, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
3930
|
+
}
|
|
3931
|
+
return null;
|
|
3932
|
+
}
|
|
3933
|
+
function detectAppleStringsdict(root) {
|
|
3934
|
+
const candidates = [root, ...listDirs(root).map((d) => join4(root, d))];
|
|
3935
|
+
let best = null;
|
|
3936
|
+
for (const dir of candidates) {
|
|
3937
|
+
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync9(join4(dir, `${l}.lproj`, "Localizable.stringsdict")));
|
|
3938
|
+
if (locales.length === 0) continue;
|
|
3939
|
+
if (!best || locales.length > best.locales.length) {
|
|
3940
|
+
best = { format: "apple-stringsdict", localeRoot: dir, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
return best;
|
|
3944
|
+
}
|
|
3945
|
+
var DETECTORS = [
|
|
3946
|
+
detectLaravel,
|
|
3947
|
+
detectVue,
|
|
3948
|
+
detectArb,
|
|
3949
|
+
detectApple,
|
|
3950
|
+
detectAngularXliff,
|
|
3951
|
+
detectRails,
|
|
3952
|
+
detectI18next,
|
|
3953
|
+
detectGettext,
|
|
3954
|
+
detectAppleStringsdict
|
|
3955
|
+
];
|
|
3768
3956
|
var BY_FORMAT = {
|
|
3769
3957
|
"laravel-php": detectLaravel,
|
|
3770
3958
|
"vue-i18n-json": (root) => detectVue(root, true),
|
|
3771
3959
|
"flutter-arb": detectArb,
|
|
3772
|
-
"apple-strings": detectApple
|
|
3960
|
+
"apple-strings": detectApple,
|
|
3961
|
+
"angular-xliff": detectAngularXliff,
|
|
3962
|
+
"rails-yaml": detectRails,
|
|
3963
|
+
"i18next-json": detectI18next,
|
|
3964
|
+
"gettext-po": detectGettext,
|
|
3965
|
+
"apple-stringsdict": detectAppleStringsdict
|
|
3773
3966
|
};
|
|
3774
3967
|
function detect(root, formatOverride) {
|
|
3775
3968
|
if (!existsSync9(root)) return null;
|
|
@@ -3786,7 +3979,7 @@ function detect(root, formatOverride) {
|
|
|
3786
3979
|
}
|
|
3787
3980
|
|
|
3788
3981
|
// src/server/import/parsers/vue-i18n-json.ts
|
|
3789
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
3982
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
|
|
3790
3983
|
import { join as join5 } from "path";
|
|
3791
3984
|
|
|
3792
3985
|
// src/server/import/flatten.ts
|
|
@@ -3826,7 +4019,7 @@ var vueI18nJson2 = {
|
|
|
3826
4019
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
3827
4020
|
let data;
|
|
3828
4021
|
try {
|
|
3829
|
-
data = JSON.parse(
|
|
4022
|
+
data = JSON.parse(readFileSync10(join5(localeRoot, file), "utf8"));
|
|
3830
4023
|
} catch (e) {
|
|
3831
4024
|
warnings.push(`vue-i18n-json: failed to parse ${file}: ${e.message}`);
|
|
3832
4025
|
continue;
|
|
@@ -3918,7 +4111,7 @@ var laravelPhp2 = {
|
|
|
3918
4111
|
};
|
|
3919
4112
|
|
|
3920
4113
|
// src/server/import/parsers/flutter-arb.ts
|
|
3921
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
4114
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
3922
4115
|
import { join as join7 } from "path";
|
|
3923
4116
|
var LOCALE_RE3 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
3924
4117
|
function localeFromArbName(file) {
|
|
@@ -3955,7 +4148,7 @@ var flutterArb2 = {
|
|
|
3955
4148
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
3956
4149
|
let data;
|
|
3957
4150
|
try {
|
|
3958
|
-
data = JSON.parse(
|
|
4151
|
+
data = JSON.parse(readFileSync11(join7(localeRoot, file), "utf8"));
|
|
3959
4152
|
} catch (e) {
|
|
3960
4153
|
warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
|
|
3961
4154
|
continue;
|
|
@@ -3980,7 +4173,7 @@ var flutterArb2 = {
|
|
|
3980
4173
|
};
|
|
3981
4174
|
|
|
3982
4175
|
// src/server/import/parsers/apple-strings.ts
|
|
3983
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
4176
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
3984
4177
|
import { join as join8 } from "path";
|
|
3985
4178
|
var LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
3986
4179
|
var TABLE = "Localizable.strings";
|
|
@@ -4089,7 +4282,7 @@ var appleStrings2 = {
|
|
|
4089
4282
|
let text;
|
|
4090
4283
|
try {
|
|
4091
4284
|
if (!statSync4(file).isFile()) continue;
|
|
4092
|
-
text =
|
|
4285
|
+
text = readFileSync12(file, "utf8");
|
|
4093
4286
|
} catch {
|
|
4094
4287
|
continue;
|
|
4095
4288
|
}
|
|
@@ -4106,12 +4299,747 @@ var appleStrings2 = {
|
|
|
4106
4299
|
}
|
|
4107
4300
|
};
|
|
4108
4301
|
|
|
4302
|
+
// src/server/import/parsers/angular-xliff.ts
|
|
4303
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
|
|
4304
|
+
import { join as join9 } from "path";
|
|
4305
|
+
var LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4306
|
+
var FILE_RE = /^messages(?:\.(.+))?\.xlf$/;
|
|
4307
|
+
function decodeEntities(s) {
|
|
4308
|
+
return s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d))).replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
4309
|
+
}
|
|
4310
|
+
function parseAttrs(s) {
|
|
4311
|
+
const out = {};
|
|
4312
|
+
for (const m of s.matchAll(/([\w-]+)="([^"]*)"/g)) out[m[1]] = decodeEntities(m[2]);
|
|
4313
|
+
return out;
|
|
4314
|
+
}
|
|
4315
|
+
function decodeInline(raw, addMeta) {
|
|
4316
|
+
let out = "";
|
|
4317
|
+
let last = 0;
|
|
4318
|
+
for (const m of raw.matchAll(/<x\b([^>]*?)\/>/g)) {
|
|
4319
|
+
out += decodeEntities(raw.slice(last, m.index));
|
|
4320
|
+
const attrs = parseAttrs(m[1]);
|
|
4321
|
+
const id = attrs["id"] ?? "X";
|
|
4322
|
+
const equiv = attrs["equiv-text"];
|
|
4323
|
+
const simple = equiv?.match(/^\{\{\s*(\w+)\s*\}\}$/);
|
|
4324
|
+
if (simple) {
|
|
4325
|
+
out += `{${simple[1]}}`;
|
|
4326
|
+
} else {
|
|
4327
|
+
out += `{${id}}`;
|
|
4328
|
+
const meta = {};
|
|
4329
|
+
if (attrs["ctype"]) meta.type = attrs["ctype"];
|
|
4330
|
+
if (equiv !== void 0) meta.example = equiv;
|
|
4331
|
+
addMeta(id, meta);
|
|
4332
|
+
}
|
|
4333
|
+
last = m.index + m[0].length;
|
|
4334
|
+
}
|
|
4335
|
+
return out + decodeEntities(raw.slice(last));
|
|
4336
|
+
}
|
|
4337
|
+
var angularXliff2 = {
|
|
4338
|
+
name: "angular-xliff",
|
|
4339
|
+
parse(localeRoot, opts) {
|
|
4340
|
+
const warnings = [];
|
|
4341
|
+
const keys = {};
|
|
4342
|
+
const locales = [];
|
|
4343
|
+
const seen = (loc) => {
|
|
4344
|
+
if (!locales.includes(loc)) locales.push(loc);
|
|
4345
|
+
};
|
|
4346
|
+
const files = readdirSync8(localeRoot).filter((f) => FILE_RE.test(f)).sort((a, b) => (a === "messages.xlf" ? -1 : 0) - (b === "messages.xlf" ? -1 : 0) || a.localeCompare(b));
|
|
4347
|
+
for (const file of files) {
|
|
4348
|
+
const fnameLocale = file.match(FILE_RE)[1];
|
|
4349
|
+
if (fnameLocale !== void 0 && !LOCALE_RE5.test(fnameLocale)) continue;
|
|
4350
|
+
let xml;
|
|
4351
|
+
try {
|
|
4352
|
+
xml = readFileSync13(join9(localeRoot, file), "utf8");
|
|
4353
|
+
} catch (e) {
|
|
4354
|
+
warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
|
|
4355
|
+
continue;
|
|
4356
|
+
}
|
|
4357
|
+
const sourceLocale = xml.match(/source-language="([^"]+)"/)?.[1];
|
|
4358
|
+
if (!sourceLocale) {
|
|
4359
|
+
warnings.push(`angular-xliff: ${file} has no source-language attribute; skipped`);
|
|
4360
|
+
continue;
|
|
4361
|
+
}
|
|
4362
|
+
const targetLocale = xml.match(/target-language="([^"]+)"/)?.[1] ?? fnameLocale;
|
|
4363
|
+
if (opts?.locales && !opts.locales.includes(targetLocale ?? sourceLocale)) continue;
|
|
4364
|
+
for (const unit of xml.matchAll(/<trans-unit\b([^>]*)>([\s\S]*?)<\/trans-unit>/g)) {
|
|
4365
|
+
const id = parseAttrs(unit[1])["id"];
|
|
4366
|
+
if (!id) {
|
|
4367
|
+
warnings.push(`angular-xliff: ${file} has a trans-unit without an id; skipped`);
|
|
4368
|
+
continue;
|
|
4369
|
+
}
|
|
4370
|
+
const body = unit[2];
|
|
4371
|
+
const src = body.match(/<source\b[^>]*>([\s\S]*?)<\/source>/);
|
|
4372
|
+
let tgt = body.match(/<target\b([^>]*)>([\s\S]*?)<\/target>/);
|
|
4373
|
+
if (tgt && /\bstate="new"/.test(tgt[1])) tgt = null;
|
|
4374
|
+
const entry = keys[id] ??= { values: {} };
|
|
4375
|
+
const addMeta = (name, meta) => {
|
|
4376
|
+
(entry.placeholders ??= {})[name] ??= meta;
|
|
4377
|
+
};
|
|
4378
|
+
if (src && entry.values[sourceLocale] === void 0) {
|
|
4379
|
+
entry.values[sourceLocale] = decodeInline(src[1], addMeta);
|
|
4380
|
+
seen(sourceLocale);
|
|
4381
|
+
}
|
|
4382
|
+
if (tgt && tgt[2] !== "" && targetLocale !== void 0) {
|
|
4383
|
+
entry.values[targetLocale] = decodeInline(tgt[2], addMeta);
|
|
4384
|
+
seen(targetLocale);
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
return { locales, keys, warnings };
|
|
4389
|
+
}
|
|
4390
|
+
};
|
|
4391
|
+
|
|
4392
|
+
// src/server/import/parsers/gettext-po.ts
|
|
4393
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync14 } from "fs";
|
|
4394
|
+
import { join as join10 } from "path";
|
|
4395
|
+
var LOCALE_RE6 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4396
|
+
var DIRECTIVE_RE = /^(msgctxt|msgid_plural|msgid|msgstr)(?:\[(\d+)\])?[ \t]+"(.*)"\s*$/;
|
|
4397
|
+
var CONT_RE = /^[ \t]*"(.*)"\s*$/;
|
|
4398
|
+
function unescapePo(s) {
|
|
4399
|
+
return s.replace(
|
|
4400
|
+
/\\([\\"ntr])/g,
|
|
4401
|
+
(_, c) => c === "n" ? "\n" : c === "t" ? " " : c === "r" ? "\r" : c
|
|
4402
|
+
);
|
|
4403
|
+
}
|
|
4404
|
+
function parseEntries(text) {
|
|
4405
|
+
const entries = [];
|
|
4406
|
+
let cur = null;
|
|
4407
|
+
let append = null;
|
|
4408
|
+
const flush = () => {
|
|
4409
|
+
if (cur && cur.msgid !== void 0) entries.push(cur);
|
|
4410
|
+
cur = null;
|
|
4411
|
+
append = null;
|
|
4412
|
+
};
|
|
4413
|
+
for (const line of text.split("\n")) {
|
|
4414
|
+
if (line.trim() === "") {
|
|
4415
|
+
flush();
|
|
4416
|
+
continue;
|
|
4417
|
+
}
|
|
4418
|
+
if (line.startsWith("#")) continue;
|
|
4419
|
+
const m = line.match(DIRECTIVE_RE);
|
|
4420
|
+
if (m) {
|
|
4421
|
+
const kw = m[1];
|
|
4422
|
+
const idx = m[2];
|
|
4423
|
+
const body = unescapePo(m[3]);
|
|
4424
|
+
if (cur && (kw === "msgctxt" || kw === "msgid" && cur.msgid !== void 0)) flush();
|
|
4425
|
+
cur ??= { plurals: /* @__PURE__ */ new Map() };
|
|
4426
|
+
const entry = cur;
|
|
4427
|
+
if (kw === "msgctxt") {
|
|
4428
|
+
entry.msgctxt = body;
|
|
4429
|
+
append = (c) => {
|
|
4430
|
+
entry.msgctxt = (entry.msgctxt ?? "") + c;
|
|
4431
|
+
};
|
|
4432
|
+
} else if (kw === "msgid") {
|
|
4433
|
+
entry.msgid = body;
|
|
4434
|
+
append = (c) => {
|
|
4435
|
+
entry.msgid = (entry.msgid ?? "") + c;
|
|
4436
|
+
};
|
|
4437
|
+
} else if (kw === "msgid_plural") {
|
|
4438
|
+
entry.msgidPlural = body;
|
|
4439
|
+
append = (c) => {
|
|
4440
|
+
entry.msgidPlural = (entry.msgidPlural ?? "") + c;
|
|
4441
|
+
};
|
|
4442
|
+
} else if (idx !== void 0) {
|
|
4443
|
+
const i = Number(idx);
|
|
4444
|
+
entry.plurals.set(i, body);
|
|
4445
|
+
append = (c) => {
|
|
4446
|
+
entry.plurals.set(i, (entry.plurals.get(i) ?? "") + c);
|
|
4447
|
+
};
|
|
4448
|
+
} else {
|
|
4449
|
+
entry.msgstr = body;
|
|
4450
|
+
append = (c) => {
|
|
4451
|
+
entry.msgstr = (entry.msgstr ?? "") + c;
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
continue;
|
|
4455
|
+
}
|
|
4456
|
+
const cont = line.match(CONT_RE);
|
|
4457
|
+
if (cont && append) append(unescapePo(cont[1]));
|
|
4458
|
+
}
|
|
4459
|
+
flush();
|
|
4460
|
+
return entries;
|
|
4461
|
+
}
|
|
4462
|
+
function discoverPoFiles(root) {
|
|
4463
|
+
const found = [];
|
|
4464
|
+
const entries = readdirSync9(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
4465
|
+
for (const e of entries) {
|
|
4466
|
+
if (e.isFile() && e.name.endsWith(".po")) {
|
|
4467
|
+
const base = e.name.slice(0, -3);
|
|
4468
|
+
found.push({ path: join10(root, e.name), rel: e.name, locale: LOCALE_RE6.test(base) ? base : null });
|
|
4469
|
+
} else if (e.isDirectory() && LOCALE_RE6.test(e.name)) {
|
|
4470
|
+
for (const sub of [join10(e.name, "LC_MESSAGES"), e.name]) {
|
|
4471
|
+
let names;
|
|
4472
|
+
try {
|
|
4473
|
+
names = readdirSync9(join10(root, sub)).sort();
|
|
4474
|
+
} catch {
|
|
4475
|
+
continue;
|
|
4476
|
+
}
|
|
4477
|
+
for (const f of names) {
|
|
4478
|
+
if (f.endsWith(".po")) found.push({ path: join10(root, sub, f), rel: join10(sub, f), locale: e.name });
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
return found;
|
|
4484
|
+
}
|
|
4485
|
+
var gettextPo2 = {
|
|
4486
|
+
name: "gettext-po",
|
|
4487
|
+
parse(localeRoot, opts) {
|
|
4488
|
+
const warnings = [];
|
|
4489
|
+
const keys = {};
|
|
4490
|
+
const locales = [];
|
|
4491
|
+
for (const file of discoverPoFiles(localeRoot)) {
|
|
4492
|
+
let entries;
|
|
4493
|
+
try {
|
|
4494
|
+
entries = parseEntries(readFileSync14(file.path, "utf8"));
|
|
4495
|
+
} catch (e) {
|
|
4496
|
+
warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
|
|
4497
|
+
continue;
|
|
4498
|
+
}
|
|
4499
|
+
const header = entries.find((e) => e.msgid === "" && e.msgctxt === void 0);
|
|
4500
|
+
const headerLang = header?.msgstr?.match(/^Language:[ \t]*([A-Za-z0-9_-]+)/m)?.[1];
|
|
4501
|
+
const locale = file.locale ?? headerLang;
|
|
4502
|
+
if (!locale) {
|
|
4503
|
+
warnings.push(`gettext-po: cannot determine locale for ${file.rel}; skipped`);
|
|
4504
|
+
continue;
|
|
4505
|
+
}
|
|
4506
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4507
|
+
if (!locales.includes(locale)) locales.push(locale);
|
|
4508
|
+
const cats = categoriesFor(locale);
|
|
4509
|
+
for (const entry of entries) {
|
|
4510
|
+
if (entry === header) continue;
|
|
4511
|
+
const key = entry.msgctxt ?? entry.msgid;
|
|
4512
|
+
if (!key) continue;
|
|
4513
|
+
if (entry.msgidPlural !== void 0) {
|
|
4514
|
+
const forms = {};
|
|
4515
|
+
for (const [i, body] of [...entry.plurals].sort((a, b) => a[0] - b[0])) {
|
|
4516
|
+
if (body === "") continue;
|
|
4517
|
+
const cat = cats[i];
|
|
4518
|
+
if (!cat) {
|
|
4519
|
+
warnings.push(
|
|
4520
|
+
`gettext-po: ${file.rel} "${key}": msgstr[${i}] exceeds the ${cats.length} plural forms of "${locale}"; ignored`
|
|
4521
|
+
);
|
|
4522
|
+
continue;
|
|
4523
|
+
}
|
|
4524
|
+
forms[cat] = body.split("%d").join("{count}");
|
|
4525
|
+
}
|
|
4526
|
+
if (!forms.other) continue;
|
|
4527
|
+
(keys[key] ??= { values: {} }).values[locale] = formsToIcu("count", forms);
|
|
4528
|
+
} else {
|
|
4529
|
+
if (!entry.msgstr) continue;
|
|
4530
|
+
(keys[key] ??= { values: {} }).values[locale] = entry.msgstr;
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
return { locales, keys, warnings };
|
|
4535
|
+
}
|
|
4536
|
+
};
|
|
4537
|
+
|
|
4538
|
+
// src/server/import/parsers/i18next-json.ts
|
|
4539
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync15, statSync as statSync5 } from "fs";
|
|
4540
|
+
import { join as join11 } from "path";
|
|
4541
|
+
var LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4542
|
+
var PLURAL_SUFFIX_RE = /^(.+)_(zero|one|two|few|many|other)$/;
|
|
4543
|
+
var PLURAL_ARG = "count";
|
|
4544
|
+
var DEFAULT_NAMESPACE = "translation";
|
|
4545
|
+
function safeIsDir2(p) {
|
|
4546
|
+
try {
|
|
4547
|
+
return statSync5(p).isDirectory();
|
|
4548
|
+
} catch {
|
|
4549
|
+
return false;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
function fromI18next(value) {
|
|
4553
|
+
if (isIcuPluralOrSelect(value)) return value;
|
|
4554
|
+
return value.replace(/\{\{(\w+)\}\}/g, "{$1}");
|
|
4555
|
+
}
|
|
4556
|
+
function ingestFile(path, label, prefix, locale, keys, warnings) {
|
|
4557
|
+
let data;
|
|
4558
|
+
try {
|
|
4559
|
+
data = JSON.parse(readFileSync15(path, "utf8"));
|
|
4560
|
+
} catch (e) {
|
|
4561
|
+
warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
|
|
4562
|
+
return false;
|
|
4563
|
+
}
|
|
4564
|
+
const fileWarnings = [];
|
|
4565
|
+
const flat = flattenObject(data, "", fileWarnings);
|
|
4566
|
+
for (const w of fileWarnings) warnings.push(`i18next-json: ${label}: ${w}`);
|
|
4567
|
+
const families = /* @__PURE__ */ new Set();
|
|
4568
|
+
for (const [k, v] of Object.entries(flat)) {
|
|
4569
|
+
const m = PLURAL_SUFFIX_RE.exec(k);
|
|
4570
|
+
if (m && m[2] === "other" && v !== "") families.add(m[1]);
|
|
4571
|
+
}
|
|
4572
|
+
const pluralForms = {};
|
|
4573
|
+
for (const [k, raw] of Object.entries(flat)) {
|
|
4574
|
+
if (raw === "") continue;
|
|
4575
|
+
const value = fromI18next(raw);
|
|
4576
|
+
const m = PLURAL_SUFFIX_RE.exec(k);
|
|
4577
|
+
if (m && families.has(m[1])) {
|
|
4578
|
+
(pluralForms[m[1]] ??= {})[m[2]] = value;
|
|
4579
|
+
continue;
|
|
4580
|
+
}
|
|
4581
|
+
if (families.has(k)) {
|
|
4582
|
+
warnings.push(
|
|
4583
|
+
`i18next-json: ${label}: key "${k}" collides with its own plural suffix family; the plural wins`
|
|
4584
|
+
);
|
|
4585
|
+
continue;
|
|
4586
|
+
}
|
|
4587
|
+
(keys[prefix + k] ??= { values: {} }).values[locale] = value;
|
|
4588
|
+
}
|
|
4589
|
+
for (const [base, forms] of Object.entries(pluralForms)) {
|
|
4590
|
+
(keys[prefix + base] ??= { values: {} }).values[locale] = formsToIcu(PLURAL_ARG, forms);
|
|
4591
|
+
}
|
|
4592
|
+
return true;
|
|
4593
|
+
}
|
|
4594
|
+
var i18nextJson2 = {
|
|
4595
|
+
name: "i18next-json",
|
|
4596
|
+
parse(localeRoot, opts) {
|
|
4597
|
+
const warnings = [];
|
|
4598
|
+
const keys = {};
|
|
4599
|
+
const locales = [];
|
|
4600
|
+
for (const entry of readdirSync10(localeRoot).sort()) {
|
|
4601
|
+
const full = join11(localeRoot, entry);
|
|
4602
|
+
if (safeIsDir2(full)) {
|
|
4603
|
+
if (!LOCALE_RE7.test(entry)) continue;
|
|
4604
|
+
if (opts?.locales && !opts.locales.includes(entry)) continue;
|
|
4605
|
+
let any = false;
|
|
4606
|
+
for (const file of readdirSync10(full).sort()) {
|
|
4607
|
+
if (!file.endsWith(".json")) continue;
|
|
4608
|
+
const ns = file.slice(0, -".json".length);
|
|
4609
|
+
const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
|
|
4610
|
+
if (ingestFile(join11(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
|
|
4611
|
+
}
|
|
4612
|
+
if (any && !locales.includes(entry)) locales.push(entry);
|
|
4613
|
+
} else if (entry.endsWith(".json")) {
|
|
4614
|
+
const locale = entry.slice(0, -".json".length);
|
|
4615
|
+
if (!LOCALE_RE7.test(locale)) continue;
|
|
4616
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4617
|
+
if (ingestFile(full, entry, "", locale, keys, warnings) && !locales.includes(locale)) {
|
|
4618
|
+
locales.push(locale);
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
return { locales, keys, warnings };
|
|
4623
|
+
}
|
|
4624
|
+
};
|
|
4625
|
+
|
|
4626
|
+
// src/server/import/parsers/rails-yaml.ts
|
|
4627
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync16 } from "fs";
|
|
4628
|
+
import { join as join12 } from "path";
|
|
4629
|
+
var LOCALE_RE8 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/i;
|
|
4630
|
+
var CATEGORY_SET = new Set(PLURAL_CATEGORIES);
|
|
4631
|
+
function fromRuby(value) {
|
|
4632
|
+
return value.replace(/%\{(\w+)\}/g, "{$1}");
|
|
4633
|
+
}
|
|
4634
|
+
function makeNode() {
|
|
4635
|
+
return /* @__PURE__ */ Object.create(null);
|
|
4636
|
+
}
|
|
4637
|
+
function decodeDouble(body) {
|
|
4638
|
+
let out = "";
|
|
4639
|
+
for (let i = 0; i < body.length; i++) {
|
|
4640
|
+
const c = body[i];
|
|
4641
|
+
if (c !== "\\") {
|
|
4642
|
+
out += c;
|
|
4643
|
+
continue;
|
|
4644
|
+
}
|
|
4645
|
+
const n = body[++i];
|
|
4646
|
+
if (n === void 0) break;
|
|
4647
|
+
out += n === "n" ? "\n" : n === "r" ? "\r" : n === "t" ? " " : n;
|
|
4648
|
+
}
|
|
4649
|
+
return out;
|
|
4650
|
+
}
|
|
4651
|
+
function scanQuoted(s, start) {
|
|
4652
|
+
const q = s[start];
|
|
4653
|
+
if (q === '"') {
|
|
4654
|
+
for (let i = start + 1; i < s.length; i++) {
|
|
4655
|
+
if (s[i] === "\\") i++;
|
|
4656
|
+
else if (s[i] === '"') return { text: decodeDouble(s.slice(start + 1, i)), end: i + 1 };
|
|
4657
|
+
}
|
|
4658
|
+
return null;
|
|
4659
|
+
}
|
|
4660
|
+
let out = "";
|
|
4661
|
+
for (let i = start + 1; i < s.length; i++) {
|
|
4662
|
+
if (s[i] === "'") {
|
|
4663
|
+
if (s[i + 1] === "'") {
|
|
4664
|
+
out += "'";
|
|
4665
|
+
i++;
|
|
4666
|
+
} else {
|
|
4667
|
+
return { text: out, end: i + 1 };
|
|
4668
|
+
}
|
|
4669
|
+
} else {
|
|
4670
|
+
out += s[i];
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
return null;
|
|
4674
|
+
}
|
|
4675
|
+
function stripPlainComment(s) {
|
|
4676
|
+
const m = /(^|\s)#/.exec(s);
|
|
4677
|
+
return (m && m.index >= 0 ? s.slice(0, m.index) : s).trim();
|
|
4678
|
+
}
|
|
4679
|
+
function onlyTrailing(s) {
|
|
4680
|
+
return /^\s*(#.*)?$/.test(s);
|
|
4681
|
+
}
|
|
4682
|
+
function parseYamlSubset(text, file, warnings) {
|
|
4683
|
+
const roots = {};
|
|
4684
|
+
const lines = text.split(/\r?\n/);
|
|
4685
|
+
let stack = [];
|
|
4686
|
+
let skipDeeperThan = null;
|
|
4687
|
+
let lastLeafIndent = null;
|
|
4688
|
+
for (let n = 0; n < lines.length; n++) {
|
|
4689
|
+
const raw = lines[n];
|
|
4690
|
+
const lineNo = n + 1;
|
|
4691
|
+
if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
|
|
4692
|
+
if (raw.trim() === "---") continue;
|
|
4693
|
+
const indentMatch = /^[ \t]*/.exec(raw)[0];
|
|
4694
|
+
if (indentMatch.includes(" ")) {
|
|
4695
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: tab in indentation; line skipped`);
|
|
4696
|
+
continue;
|
|
4697
|
+
}
|
|
4698
|
+
const indent = indentMatch.length;
|
|
4699
|
+
if (skipDeeperThan !== null) {
|
|
4700
|
+
if (indent > skipDeeperThan) continue;
|
|
4701
|
+
skipDeeperThan = null;
|
|
4702
|
+
}
|
|
4703
|
+
const content = raw.slice(indent);
|
|
4704
|
+
if (content.startsWith("- ") || content === "-") {
|
|
4705
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: sequences are not supported; node skipped`);
|
|
4706
|
+
skipDeeperThan = indent;
|
|
4707
|
+
continue;
|
|
4708
|
+
}
|
|
4709
|
+
let key;
|
|
4710
|
+
let rest;
|
|
4711
|
+
if (content[0] === '"' || content[0] === "'") {
|
|
4712
|
+
const k = scanQuoted(content, 0);
|
|
4713
|
+
if (!k || content[k.end] !== ":") {
|
|
4714
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unparseable quoted key; line skipped`);
|
|
4715
|
+
skipDeeperThan = indent;
|
|
4716
|
+
continue;
|
|
4717
|
+
}
|
|
4718
|
+
key = k.text;
|
|
4719
|
+
rest = content.slice(k.end + 1);
|
|
4720
|
+
} else {
|
|
4721
|
+
const m = /^(.*?):(?=\s|$)/.exec(content);
|
|
4722
|
+
if (!m) {
|
|
4723
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: not a "key: value" mapping line; line skipped`);
|
|
4724
|
+
skipDeeperThan = indent;
|
|
4725
|
+
continue;
|
|
4726
|
+
}
|
|
4727
|
+
key = m[1].trim();
|
|
4728
|
+
rest = content.slice(m[0].length);
|
|
4729
|
+
}
|
|
4730
|
+
if (lastLeafIndent !== null && indent > lastLeafIndent) {
|
|
4731
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unexpected indentation under a scalar; line skipped`);
|
|
4732
|
+
skipDeeperThan = indent - 1;
|
|
4733
|
+
continue;
|
|
4734
|
+
}
|
|
4735
|
+
while (stack.length > 0 && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
4736
|
+
const trimmed = rest.trim();
|
|
4737
|
+
let value;
|
|
4738
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
4739
|
+
value = null;
|
|
4740
|
+
} else if (trimmed[0] === "&" || trimmed[0] === "*") {
|
|
4741
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: YAML anchors/aliases are not supported; node skipped`);
|
|
4742
|
+
skipDeeperThan = indent;
|
|
4743
|
+
continue;
|
|
4744
|
+
} else if (trimmed[0] === "|" || trimmed[0] === ">") {
|
|
4745
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: block scalars are not supported; node skipped`);
|
|
4746
|
+
skipDeeperThan = indent;
|
|
4747
|
+
continue;
|
|
4748
|
+
} else if (trimmed[0] === "[" || trimmed[0] === "{") {
|
|
4749
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: flow collections are not supported; node skipped`);
|
|
4750
|
+
skipDeeperThan = indent;
|
|
4751
|
+
continue;
|
|
4752
|
+
} else if (trimmed[0] === '"' || trimmed[0] === "'") {
|
|
4753
|
+
const v = scanQuoted(trimmed, 0);
|
|
4754
|
+
if (!v || !onlyTrailing(trimmed.slice(v.end))) {
|
|
4755
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unterminated or trailing-garbage quoted value; line skipped`);
|
|
4756
|
+
continue;
|
|
4757
|
+
}
|
|
4758
|
+
value = v.text;
|
|
4759
|
+
} else {
|
|
4760
|
+
value = stripPlainComment(trimmed);
|
|
4761
|
+
}
|
|
4762
|
+
if (stack.length === 0) {
|
|
4763
|
+
if (value !== null) {
|
|
4764
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: top-level key "${key}" has a scalar value; skipped`);
|
|
4765
|
+
lastLeafIndent = indent;
|
|
4766
|
+
continue;
|
|
4767
|
+
}
|
|
4768
|
+
const root = roots[key] ??= makeNode();
|
|
4769
|
+
stack = [{ indent, node: root }];
|
|
4770
|
+
lastLeafIndent = null;
|
|
4771
|
+
continue;
|
|
4772
|
+
}
|
|
4773
|
+
const parent = stack[stack.length - 1].node;
|
|
4774
|
+
if (key in parent) {
|
|
4775
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: duplicate key "${key}"; later value wins`);
|
|
4776
|
+
}
|
|
4777
|
+
if (value === null) {
|
|
4778
|
+
const child = makeNode();
|
|
4779
|
+
parent[key] = child;
|
|
4780
|
+
stack.push({ indent, node: child });
|
|
4781
|
+
lastLeafIndent = null;
|
|
4782
|
+
} else {
|
|
4783
|
+
parent[key] = value;
|
|
4784
|
+
lastLeafIndent = indent;
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
return { roots };
|
|
4788
|
+
}
|
|
4789
|
+
function asPluralForms(node) {
|
|
4790
|
+
const entries = Object.entries(node);
|
|
4791
|
+
if (entries.length === 0) return null;
|
|
4792
|
+
const forms = {};
|
|
4793
|
+
for (const [k, v] of entries) {
|
|
4794
|
+
if (!CATEGORY_SET.has(k) || typeof v !== "string") return null;
|
|
4795
|
+
if (v !== "") forms[k] = v;
|
|
4796
|
+
}
|
|
4797
|
+
if (!("other" in forms)) return null;
|
|
4798
|
+
return forms;
|
|
4799
|
+
}
|
|
4800
|
+
function synthesizeIcu(forms, file, key, warnings) {
|
|
4801
|
+
const parts = [];
|
|
4802
|
+
for (const cat of PLURAL_CATEGORIES) {
|
|
4803
|
+
const body = forms[cat];
|
|
4804
|
+
if (body === void 0) continue;
|
|
4805
|
+
if (body.includes("#")) {
|
|
4806
|
+
warnings.push(
|
|
4807
|
+
`rails-yaml: ${file}: plural "${key}" form "${cat}" contains "#", which ICU reads as the count placeholder`
|
|
4808
|
+
);
|
|
4809
|
+
}
|
|
4810
|
+
parts.push(`${cat} {${fromRuby(body)}}`);
|
|
4811
|
+
}
|
|
4812
|
+
return `{count, plural, ${parts.join(" ")}}`;
|
|
4813
|
+
}
|
|
4814
|
+
var railsYaml2 = {
|
|
4815
|
+
name: "rails-yaml",
|
|
4816
|
+
parse(localeRoot, opts) {
|
|
4817
|
+
const warnings = [];
|
|
4818
|
+
const keys = {};
|
|
4819
|
+
const locales = [];
|
|
4820
|
+
const wanted = opts?.locales?.map((l) => l.toLowerCase());
|
|
4821
|
+
const addValue = (key, locale, value) => {
|
|
4822
|
+
(keys[key] ??= { values: {} }).values[locale] = value;
|
|
4823
|
+
};
|
|
4824
|
+
const flatten = (node, prefix, locale, file) => {
|
|
4825
|
+
for (const [k, v] of Object.entries(node)) {
|
|
4826
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
4827
|
+
if (typeof v === "string") {
|
|
4828
|
+
if (v !== "") addValue(key, locale, fromRuby(v));
|
|
4829
|
+
continue;
|
|
4830
|
+
}
|
|
4831
|
+
const forms = asPluralForms(v);
|
|
4832
|
+
if (forms) addValue(key, locale, synthesizeIcu(forms, file, key, warnings));
|
|
4833
|
+
else flatten(v, key, locale, file);
|
|
4834
|
+
}
|
|
4835
|
+
};
|
|
4836
|
+
for (const file of readdirSync11(localeRoot).sort()) {
|
|
4837
|
+
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
4838
|
+
let text;
|
|
4839
|
+
try {
|
|
4840
|
+
text = readFileSync16(join12(localeRoot, file), "utf8");
|
|
4841
|
+
} catch (e) {
|
|
4842
|
+
warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
|
|
4843
|
+
continue;
|
|
4844
|
+
}
|
|
4845
|
+
const { roots } = parseYamlSubset(text, file, warnings);
|
|
4846
|
+
for (const token of Object.keys(roots).sort()) {
|
|
4847
|
+
if (!LOCALE_RE8.test(token)) {
|
|
4848
|
+
warnings.push(`rails-yaml: ${file}: top-level key "${token}" is not a locale; subtree skipped`);
|
|
4849
|
+
continue;
|
|
4850
|
+
}
|
|
4851
|
+
if (wanted && !wanted.includes(token.toLowerCase())) continue;
|
|
4852
|
+
if (!locales.includes(token)) locales.push(token);
|
|
4853
|
+
flatten(roots[token], "", token, file);
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
return { locales, keys, warnings };
|
|
4857
|
+
}
|
|
4858
|
+
};
|
|
4859
|
+
|
|
4860
|
+
// src/server/import/parsers/apple-stringsdict.ts
|
|
4861
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync17, statSync as statSync6 } from "fs";
|
|
4862
|
+
import { join as join13 } from "path";
|
|
4863
|
+
var LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4864
|
+
var TABLE2 = "Localizable.stringsdict";
|
|
4865
|
+
function localeFromLproj2(dir) {
|
|
4866
|
+
const m = dir.match(/^(.+)\.lproj$/);
|
|
4867
|
+
if (!m) return null;
|
|
4868
|
+
return LOCALE_RE9.test(m[1]) ? m[1] : null;
|
|
4869
|
+
}
|
|
4870
|
+
function decodeEntities2(s) {
|
|
4871
|
+
return s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d))).replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
4872
|
+
}
|
|
4873
|
+
function parsePlistDict(xml) {
|
|
4874
|
+
let i = 0;
|
|
4875
|
+
const n = xml.length;
|
|
4876
|
+
const skipTrivia = () => {
|
|
4877
|
+
for (; ; ) {
|
|
4878
|
+
while (i < n && /\s/.test(xml[i])) i++;
|
|
4879
|
+
if (xml.startsWith("<!--", i)) {
|
|
4880
|
+
const end = xml.indexOf("-->", i + 4);
|
|
4881
|
+
if (end === -1) throw new Error("unterminated comment");
|
|
4882
|
+
i = end + 3;
|
|
4883
|
+
continue;
|
|
4884
|
+
}
|
|
4885
|
+
if (xml.startsWith("<?", i) || xml.startsWith("<!", i) && !xml.startsWith("<!--", i)) {
|
|
4886
|
+
const end = xml.indexOf(">", i);
|
|
4887
|
+
if (end === -1) throw new Error("unterminated declaration");
|
|
4888
|
+
i = end + 1;
|
|
4889
|
+
continue;
|
|
4890
|
+
}
|
|
4891
|
+
break;
|
|
4892
|
+
}
|
|
4893
|
+
};
|
|
4894
|
+
const readTag = () => {
|
|
4895
|
+
if (xml[i] !== "<") throw new Error(`expected a tag at offset ${i}`);
|
|
4896
|
+
const end = xml.indexOf(">", i);
|
|
4897
|
+
if (end === -1) throw new Error("unterminated tag");
|
|
4898
|
+
let body = xml.slice(i + 1, end).trim();
|
|
4899
|
+
i = end + 1;
|
|
4900
|
+
const closing = body.startsWith("/");
|
|
4901
|
+
if (closing) body = body.slice(1).trim();
|
|
4902
|
+
const selfClosing = body.endsWith("/");
|
|
4903
|
+
if (selfClosing) body = body.slice(0, -1).trim();
|
|
4904
|
+
const name = body.split(/\s/)[0];
|
|
4905
|
+
if (!name) throw new Error(`empty tag at offset ${end}`);
|
|
4906
|
+
return { name, closing, selfClosing };
|
|
4907
|
+
};
|
|
4908
|
+
const readElementText = (name) => {
|
|
4909
|
+
const re = new RegExp(`</${name}\\s*>`, "g");
|
|
4910
|
+
re.lastIndex = i;
|
|
4911
|
+
const m = re.exec(xml);
|
|
4912
|
+
if (!m) throw new Error(`unterminated <${name}>`);
|
|
4913
|
+
const text = xml.slice(i, m.index);
|
|
4914
|
+
i = m.index + m[0].length;
|
|
4915
|
+
return decodeEntities2(text);
|
|
4916
|
+
};
|
|
4917
|
+
const readValue = (tag2) => {
|
|
4918
|
+
if (tag2.name === "dict") return tag2.selfClosing ? {} : readDict();
|
|
4919
|
+
if (tag2.name === "true" || tag2.name === "false") {
|
|
4920
|
+
if (!tag2.selfClosing) readElementText(tag2.name);
|
|
4921
|
+
return tag2.name;
|
|
4922
|
+
}
|
|
4923
|
+
if (["string", "integer", "real", "date", "data"].includes(tag2.name)) {
|
|
4924
|
+
return tag2.selfClosing ? "" : readElementText(tag2.name);
|
|
4925
|
+
}
|
|
4926
|
+
throw new Error(`unsupported plist element <${tag2.name}>`);
|
|
4927
|
+
};
|
|
4928
|
+
const readDict = () => {
|
|
4929
|
+
const out = {};
|
|
4930
|
+
for (; ; ) {
|
|
4931
|
+
skipTrivia();
|
|
4932
|
+
const tag2 = readTag();
|
|
4933
|
+
if (tag2.closing) {
|
|
4934
|
+
if (tag2.name !== "dict") throw new Error(`unexpected </${tag2.name}> inside <dict>`);
|
|
4935
|
+
return out;
|
|
4936
|
+
}
|
|
4937
|
+
if (tag2.name !== "key") throw new Error(`expected <key> inside <dict>, got <${tag2.name}>`);
|
|
4938
|
+
const key = readElementText("key");
|
|
4939
|
+
skipTrivia();
|
|
4940
|
+
const vt = readTag();
|
|
4941
|
+
if (vt.closing) throw new Error(`<key>${key}</key> has no value`);
|
|
4942
|
+
out[key] = readValue(vt);
|
|
4943
|
+
}
|
|
4944
|
+
};
|
|
4945
|
+
skipTrivia();
|
|
4946
|
+
let tag = readTag();
|
|
4947
|
+
if (tag.name === "plist" && !tag.closing && !tag.selfClosing) {
|
|
4948
|
+
skipTrivia();
|
|
4949
|
+
tag = readTag();
|
|
4950
|
+
}
|
|
4951
|
+
if (tag.name !== "dict" || tag.closing) throw new Error("expected a root <dict>");
|
|
4952
|
+
return tag.selfClosing ? {} : readDict();
|
|
4953
|
+
}
|
|
4954
|
+
var VAR_RE = /%#@([^@]*)@/g;
|
|
4955
|
+
function entryToIcu(key, entry, file, warnings) {
|
|
4956
|
+
const warn = (msg) => {
|
|
4957
|
+
warnings.push(`apple-stringsdict: ${file}: key "${key}": ${msg}`);
|
|
4958
|
+
return null;
|
|
4959
|
+
};
|
|
4960
|
+
if (typeof entry !== "object") return warn("value is not a dict; skipped");
|
|
4961
|
+
const fmt = entry["NSStringLocalizedFormatKey"];
|
|
4962
|
+
if (typeof fmt !== "string") return warn("missing NSStringLocalizedFormatKey; skipped");
|
|
4963
|
+
const vars = [...fmt.matchAll(VAR_RE)];
|
|
4964
|
+
if (vars.length !== 1) {
|
|
4965
|
+
return warn(`format key has ${vars.length} %#@\u2026@ variables; only exactly one is supported; skipped`);
|
|
4966
|
+
}
|
|
4967
|
+
const arg = vars[0][1];
|
|
4968
|
+
if (!/^\w+$/.test(arg)) return warn(`variable name "${arg}" is not a valid ICU argument; skipped`);
|
|
4969
|
+
const prefix = fmt.slice(0, vars[0].index);
|
|
4970
|
+
const suffix = fmt.slice(vars[0].index + vars[0][0].length);
|
|
4971
|
+
const varDict = entry[arg];
|
|
4972
|
+
if (typeof varDict !== "object") return warn(`variable "${arg}" has no dict; skipped`);
|
|
4973
|
+
const specType = varDict["NSStringFormatSpecTypeKey"];
|
|
4974
|
+
if (specType !== void 0 && specType !== "NSStringPluralRuleType") {
|
|
4975
|
+
return warn(`variable "${arg}" is not a plural rule (${String(specType)}); skipped`);
|
|
4976
|
+
}
|
|
4977
|
+
const valueType = varDict["NSStringFormatValueTypeKey"];
|
|
4978
|
+
const token = `%${typeof valueType === "string" && valueType ? valueType : "d"}`;
|
|
4979
|
+
const forms = {};
|
|
4980
|
+
for (const cat of PLURAL_CATEGORIES) {
|
|
4981
|
+
const body = varDict[cat];
|
|
4982
|
+
if (typeof body !== "string") continue;
|
|
4983
|
+
forms[cat] = prefix + body.split(token).join(`{${arg}}`) + suffix;
|
|
4984
|
+
}
|
|
4985
|
+
if (forms.other === void 0) return warn(`variable "${arg}" has no "other" form; skipped`);
|
|
4986
|
+
return formsToIcu(arg, forms);
|
|
4987
|
+
}
|
|
4988
|
+
var appleStringsdict2 = {
|
|
4989
|
+
name: "apple-stringsdict",
|
|
4990
|
+
parse(localeRoot, opts) {
|
|
4991
|
+
const warnings = [];
|
|
4992
|
+
const keys = {};
|
|
4993
|
+
const locales = [];
|
|
4994
|
+
for (const dir of readdirSync12(localeRoot).sort()) {
|
|
4995
|
+
const locale = localeFromLproj2(dir);
|
|
4996
|
+
if (!locale) continue;
|
|
4997
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4998
|
+
const file = join13(localeRoot, dir, TABLE2);
|
|
4999
|
+
let text;
|
|
5000
|
+
try {
|
|
5001
|
+
if (!statSync6(file).isFile()) continue;
|
|
5002
|
+
text = readFileSync17(file, "utf8");
|
|
5003
|
+
} catch {
|
|
5004
|
+
continue;
|
|
5005
|
+
}
|
|
5006
|
+
locales.push(locale);
|
|
5007
|
+
const others = readdirSync12(join13(localeRoot, dir)).filter(
|
|
5008
|
+
(f) => f.endsWith(".stringsdict") && f !== TABLE2
|
|
5009
|
+
);
|
|
5010
|
+
if (others.length) {
|
|
5011
|
+
warnings.push(
|
|
5012
|
+
`apple-stringsdict: ${dir} has other .stringsdict tables (${others.join(", ")}); only ${TABLE2} is imported`
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5015
|
+
let root;
|
|
5016
|
+
try {
|
|
5017
|
+
root = parsePlistDict(text);
|
|
5018
|
+
} catch (e) {
|
|
5019
|
+
warnings.push(`apple-stringsdict: failed to parse ${file}: ${e.message}`);
|
|
5020
|
+
continue;
|
|
5021
|
+
}
|
|
5022
|
+
for (const key of Object.keys(root).sort()) {
|
|
5023
|
+
const icu = entryToIcu(key, root[key], file, warnings);
|
|
5024
|
+
if (icu === null) continue;
|
|
5025
|
+
(keys[key] ??= { values: {} }).values[locale] = icu;
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
return { locales, keys, warnings };
|
|
5029
|
+
}
|
|
5030
|
+
};
|
|
5031
|
+
|
|
4109
5032
|
// src/server/import/parsers/index.ts
|
|
4110
5033
|
var REGISTRY = {
|
|
4111
5034
|
[vueI18nJson2.name]: vueI18nJson2,
|
|
4112
5035
|
[laravelPhp2.name]: laravelPhp2,
|
|
4113
5036
|
[flutterArb2.name]: flutterArb2,
|
|
4114
|
-
[appleStrings2.name]: appleStrings2
|
|
5037
|
+
[appleStrings2.name]: appleStrings2,
|
|
5038
|
+
[angularXliff2.name]: angularXliff2,
|
|
5039
|
+
[gettextPo2.name]: gettextPo2,
|
|
5040
|
+
[i18nextJson2.name]: i18nextJson2,
|
|
5041
|
+
[railsYaml2.name]: railsYaml2,
|
|
5042
|
+
[appleStringsdict2.name]: appleStringsdict2
|
|
4115
5043
|
};
|
|
4116
5044
|
function getParser(name) {
|
|
4117
5045
|
const p = REGISTRY[name];
|
|
@@ -4124,7 +5052,14 @@ var OUTPUT_BY_FORMAT = {
|
|
|
4124
5052
|
"laravel-php": { adapter: "laravel-php", path: "lang/{locale}/{namespace}.php" },
|
|
4125
5053
|
"vue-i18n-json": { adapter: "vue-i18n-json", path: "src/locale/{locale}.json" },
|
|
4126
5054
|
"flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" },
|
|
4127
|
-
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true }
|
|
5055
|
+
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true },
|
|
5056
|
+
// skipSourceLocale: ng extract-i18n owns messages.xlf (the source file); glotfile
|
|
5057
|
+
// only writes the translation files back next to it.
|
|
5058
|
+
"angular-xliff": { adapter: "angular-xliff", path: "messages.{locale}.xlf", rootRelative: true, skipSourceLocale: true },
|
|
5059
|
+
"gettext-po": { adapter: "gettext-po", path: "{locale}.po", rootRelative: true },
|
|
5060
|
+
"i18next-json": { adapter: "i18next-json", path: "{locale}/translation.json", rootRelative: true },
|
|
5061
|
+
"rails-yaml": { adapter: "rails-yaml", path: "config/locales/{locale}.yml" },
|
|
5062
|
+
"apple-stringsdict": { adapter: "apple-stringsdict", path: "{locale}.lproj/Localizable.stringsdict", rootRelative: true }
|
|
4128
5063
|
};
|
|
4129
5064
|
function assemble2(parsed, opts) {
|
|
4130
5065
|
const warnings = [...parsed.warnings];
|
|
@@ -4242,7 +5177,7 @@ function runImport(opts) {
|
|
|
4242
5177
|
}
|
|
4243
5178
|
|
|
4244
5179
|
// src/server/export-run.ts
|
|
4245
|
-
import { existsSync as existsSync10, readFileSync as
|
|
5180
|
+
import { existsSync as existsSync10, readFileSync as readFileSync18, readdirSync as readdirSync13, rmdirSync, statSync as statSync7, unlinkSync } from "fs";
|
|
4246
5181
|
import { dirname as dirname2, resolve as resolve7, sep } from "path";
|
|
4247
5182
|
function effectiveLocales(config) {
|
|
4248
5183
|
const limit = config.exportLocales;
|
|
@@ -4285,7 +5220,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
4285
5220
|
if (!segment.includes("{locale}") && !segment.includes("{namespace}")) {
|
|
4286
5221
|
const next = resolve7(dir, segment);
|
|
4287
5222
|
if (isLast) {
|
|
4288
|
-
if (stale(locale) && existsSync10(next) &&
|
|
5223
|
+
if (stale(locale) && existsSync10(next) && statSync7(next).isFile()) {
|
|
4289
5224
|
unlinkSync(next);
|
|
4290
5225
|
deleted++;
|
|
4291
5226
|
removeEmptyDirs(dir, root);
|
|
@@ -4298,7 +5233,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
4298
5233
|
const re = segmentRegExp(segment);
|
|
4299
5234
|
let entries;
|
|
4300
5235
|
try {
|
|
4301
|
-
entries =
|
|
5236
|
+
entries = readdirSync13(dir, { withFileTypes: true });
|
|
4302
5237
|
} catch {
|
|
4303
5238
|
return;
|
|
4304
5239
|
}
|
|
@@ -4341,7 +5276,7 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
4341
5276
|
writtenPaths.add(abs);
|
|
4342
5277
|
let current = null;
|
|
4343
5278
|
try {
|
|
4344
|
-
current =
|
|
5279
|
+
current = readFileSync18(abs, "utf8");
|
|
4345
5280
|
} catch {
|
|
4346
5281
|
}
|
|
4347
5282
|
if (current === f.contents) {
|
|
@@ -4358,17 +5293,17 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
4358
5293
|
}
|
|
4359
5294
|
|
|
4360
5295
|
// src/server/ui-prefs.ts
|
|
4361
|
-
import { readFileSync as
|
|
5296
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
4362
5297
|
import { homedir } from "os";
|
|
4363
|
-
import { join as
|
|
5298
|
+
import { join as join14 } from "path";
|
|
4364
5299
|
var THEMES = ["system", "light", "dark"];
|
|
4365
5300
|
var isThemeMode = (v) => THEMES.includes(v);
|
|
4366
5301
|
var isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
4367
|
-
var defaultUiPrefsPath = () =>
|
|
5302
|
+
var defaultUiPrefsPath = () => join14(homedir(), ".glotfile", "ui.json");
|
|
4368
5303
|
var DEFAULTS = { theme: "system" };
|
|
4369
5304
|
function readJson(path) {
|
|
4370
5305
|
try {
|
|
4371
|
-
const parsed = JSON.parse(
|
|
5306
|
+
const parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
4372
5307
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4373
5308
|
} catch {
|
|
4374
5309
|
return {};
|
|
@@ -4387,7 +5322,7 @@ function saveUiPrefs(path, prefs) {
|
|
|
4387
5322
|
}
|
|
4388
5323
|
|
|
4389
5324
|
// src/server/local-settings.ts
|
|
4390
|
-
import { readFileSync as
|
|
5325
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
4391
5326
|
import { resolve as resolve8 } from "path";
|
|
4392
5327
|
var EDITOR_IDS = ["vscode", "zed", "phpstorm"];
|
|
4393
5328
|
var isEditorId = (v) => EDITOR_IDS.includes(v);
|
|
@@ -4402,7 +5337,7 @@ var DEFAULT_EDITOR = "vscode";
|
|
|
4402
5337
|
var settingsPath = (projectRoot) => resolve8(projectRoot, ".glotfile", "settings.json");
|
|
4403
5338
|
function readJson2(path) {
|
|
4404
5339
|
try {
|
|
4405
|
-
const parsed = JSON.parse(
|
|
5340
|
+
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
4406
5341
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4407
5342
|
} catch {
|
|
4408
5343
|
return {};
|
|
@@ -4475,7 +5410,7 @@ function projectName(root) {
|
|
|
4475
5410
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
4476
5411
|
if (existsSync11(nameFile)) {
|
|
4477
5412
|
try {
|
|
4478
|
-
const name =
|
|
5413
|
+
const name = readFileSync21(nameFile, "utf8").trim();
|
|
4479
5414
|
if (name) return name;
|
|
4480
5415
|
} catch {
|
|
4481
5416
|
}
|
|
@@ -4600,7 +5535,7 @@ function createApi(deps) {
|
|
|
4600
5535
|
if (depth > 4) return;
|
|
4601
5536
|
let entries = [];
|
|
4602
5537
|
try {
|
|
4603
|
-
entries =
|
|
5538
|
+
entries = readdirSync14(dir);
|
|
4604
5539
|
} catch {
|
|
4605
5540
|
return;
|
|
4606
5541
|
}
|
|
@@ -4614,7 +5549,7 @@ function createApi(deps) {
|
|
|
4614
5549
|
filePath = abs;
|
|
4615
5550
|
} else {
|
|
4616
5551
|
try {
|
|
4617
|
-
if (
|
|
5552
|
+
if (statSync8(abs).isDirectory()) walk(abs, depth + 1);
|
|
4618
5553
|
} catch {
|
|
4619
5554
|
}
|
|
4620
5555
|
continue;
|
|
@@ -5395,7 +6330,7 @@ function createApi(deps) {
|
|
|
5395
6330
|
|
|
5396
6331
|
// src/server/server.ts
|
|
5397
6332
|
var here = dirname4(fileURLToPath(import.meta.url));
|
|
5398
|
-
var DEFAULT_UI_DIR =
|
|
6333
|
+
var DEFAULT_UI_DIR = join15(here, "..", "ui");
|
|
5399
6334
|
var MIME = {
|
|
5400
6335
|
".html": "text/html; charset=utf-8",
|
|
5401
6336
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -5449,7 +6384,7 @@ function buildApp(opts) {
|
|
|
5449
6384
|
const file = await readFileResponse(target);
|
|
5450
6385
|
if (file) return file;
|
|
5451
6386
|
}
|
|
5452
|
-
const index = await readFileResponse(
|
|
6387
|
+
const index = await readFileResponse(join15(root, "index.html"));
|
|
5453
6388
|
if (index) return index;
|
|
5454
6389
|
return c.notFound();
|
|
5455
6390
|
});
|