glotfile 0.5.4 → 0.6.0
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 +976 -45
- package/dist/server/server.js +924 -43
- 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
|
|
@@ -3675,7 +3692,7 @@ function readLog(projectRoot, limit = 100) {
|
|
|
3675
3692
|
import { relative as relative3 } from "path";
|
|
3676
3693
|
|
|
3677
3694
|
// src/server/import/detect.ts
|
|
3678
|
-
import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
3695
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
|
|
3679
3696
|
import { join as join4 } from "path";
|
|
3680
3697
|
var LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
3681
3698
|
var VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
|
|
@@ -3764,12 +3781,134 @@ function detectApple(root) {
|
|
|
3764
3781
|
}
|
|
3765
3782
|
return best;
|
|
3766
3783
|
}
|
|
3767
|
-
var
|
|
3784
|
+
var ANGULAR_DIR_CANDIDATES = [".", "src/locale", "src/locales", "src/i18n", "locale", "locales", "i18n", "translations"];
|
|
3785
|
+
function detectAngularXliff(root) {
|
|
3786
|
+
for (const rel of ANGULAR_DIR_CANDIDATES) {
|
|
3787
|
+
const localeRoot = rel === "." ? root : join4(root, rel);
|
|
3788
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
3789
|
+
const files = readdirSync3(localeRoot).filter((f) => /^messages(\..+)?\.xlf$/.test(f)).sort();
|
|
3790
|
+
if (files.length === 0) continue;
|
|
3791
|
+
const locales = files.map((f) => f.match(/^messages\.(.+)\.xlf$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l));
|
|
3792
|
+
const attrFile = files.includes("messages.xlf") ? "messages.xlf" : files[0];
|
|
3793
|
+
let sourceLocale;
|
|
3794
|
+
try {
|
|
3795
|
+
sourceLocale = readFileSync9(join4(localeRoot, attrFile), "utf8").match(/source-language="([^"]+)"/)?.[1];
|
|
3796
|
+
} catch {
|
|
3797
|
+
}
|
|
3798
|
+
if (!sourceLocale && locales.length === 0) continue;
|
|
3799
|
+
sourceLocale ??= pickSource(locales, () => 0);
|
|
3800
|
+
if (!locales.includes(sourceLocale)) locales.unshift(sourceLocale);
|
|
3801
|
+
return { format: "angular-xliff", localeRoot, locales, sourceLocale };
|
|
3802
|
+
}
|
|
3803
|
+
return null;
|
|
3804
|
+
}
|
|
3805
|
+
function detectRails(root) {
|
|
3806
|
+
const localeRoot = join4(root, "config", "locales");
|
|
3807
|
+
if (!safeIsDir(localeRoot)) return null;
|
|
3808
|
+
const locales = [];
|
|
3809
|
+
for (const file of readdirSync3(localeRoot).sort()) {
|
|
3810
|
+
if (!/\.ya?ml$/.test(file)) continue;
|
|
3811
|
+
let text;
|
|
3812
|
+
try {
|
|
3813
|
+
text = readFileSync9(join4(localeRoot, file), "utf8");
|
|
3814
|
+
} catch {
|
|
3815
|
+
continue;
|
|
3816
|
+
}
|
|
3817
|
+
for (const m of text.matchAll(/^(["']?)([A-Za-z][\w-]*)\1:\s*(?:#.*)?$/gm)) {
|
|
3818
|
+
const token = m[2];
|
|
3819
|
+
if (LOCALE_RE.test(token) && !locales.includes(token)) locales.push(token);
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
if (locales.length === 0) return null;
|
|
3823
|
+
return { format: "rails-yaml", localeRoot, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
3824
|
+
}
|
|
3825
|
+
var I18NEXT_DIR_CANDIDATES = ["public/locales", "static/locales", "locales", "src/locales", "src/i18n/locales"];
|
|
3826
|
+
function detectI18next(root) {
|
|
3827
|
+
for (const rel of I18NEXT_DIR_CANDIDATES) {
|
|
3828
|
+
const localeRoot = join4(root, rel);
|
|
3829
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
3830
|
+
const locales = listDirs(localeRoot).filter(
|
|
3831
|
+
(d) => LOCALE_RE.test(d) && readdirSync3(join4(localeRoot, d)).some((f) => f.endsWith(".json"))
|
|
3832
|
+
);
|
|
3833
|
+
if (locales.length === 0) continue;
|
|
3834
|
+
const sourceLocale = pickSource(locales, (loc) => {
|
|
3835
|
+
try {
|
|
3836
|
+
return readdirSync3(join4(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync2(join4(localeRoot, loc, f)).size, 0);
|
|
3837
|
+
} catch {
|
|
3838
|
+
return 0;
|
|
3839
|
+
}
|
|
3840
|
+
});
|
|
3841
|
+
return { format: "i18next-json", localeRoot, locales, sourceLocale };
|
|
3842
|
+
}
|
|
3843
|
+
return null;
|
|
3844
|
+
}
|
|
3845
|
+
function gettextLocales(dir) {
|
|
3846
|
+
const locales = [];
|
|
3847
|
+
for (const entry of readdirSync3(dir).sort()) {
|
|
3848
|
+
const flat = entry.match(/^(.+)\.po$/)?.[1];
|
|
3849
|
+
if (flat && LOCALE_RE.test(flat)) {
|
|
3850
|
+
if (!locales.includes(flat)) locales.push(flat);
|
|
3851
|
+
continue;
|
|
3852
|
+
}
|
|
3853
|
+
if (!LOCALE_RE.test(entry) || !safeIsDir(join4(dir, entry))) continue;
|
|
3854
|
+
const sub = join4(dir, entry);
|
|
3855
|
+
const hasPo = (d) => {
|
|
3856
|
+
try {
|
|
3857
|
+
return readdirSync3(d).some((f) => f.endsWith(".po"));
|
|
3858
|
+
} catch {
|
|
3859
|
+
return false;
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
if (hasPo(join4(sub, "LC_MESSAGES")) || hasPo(sub)) {
|
|
3863
|
+
if (!locales.includes(entry)) locales.push(entry);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
return locales;
|
|
3867
|
+
}
|
|
3868
|
+
var GETTEXT_DIR_CANDIDATES = ["locale", "locales", "po", "translations"];
|
|
3869
|
+
function detectGettext(root) {
|
|
3870
|
+
for (const rel of GETTEXT_DIR_CANDIDATES) {
|
|
3871
|
+
const localeRoot = join4(root, rel);
|
|
3872
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
3873
|
+
const locales = gettextLocales(localeRoot);
|
|
3874
|
+
if (locales.length === 0) continue;
|
|
3875
|
+
return { format: "gettext-po", localeRoot, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
3876
|
+
}
|
|
3877
|
+
return null;
|
|
3878
|
+
}
|
|
3879
|
+
function detectAppleStringsdict(root) {
|
|
3880
|
+
const candidates = [root, ...listDirs(root).map((d) => join4(root, d))];
|
|
3881
|
+
let best = null;
|
|
3882
|
+
for (const dir of candidates) {
|
|
3883
|
+
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync9(join4(dir, `${l}.lproj`, "Localizable.stringsdict")));
|
|
3884
|
+
if (locales.length === 0) continue;
|
|
3885
|
+
if (!best || locales.length > best.locales.length) {
|
|
3886
|
+
best = { format: "apple-stringsdict", localeRoot: dir, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
return best;
|
|
3890
|
+
}
|
|
3891
|
+
var DETECTORS = [
|
|
3892
|
+
detectLaravel,
|
|
3893
|
+
detectVue,
|
|
3894
|
+
detectArb,
|
|
3895
|
+
detectApple,
|
|
3896
|
+
detectAngularXliff,
|
|
3897
|
+
detectRails,
|
|
3898
|
+
detectI18next,
|
|
3899
|
+
detectGettext,
|
|
3900
|
+
detectAppleStringsdict
|
|
3901
|
+
];
|
|
3768
3902
|
var BY_FORMAT = {
|
|
3769
3903
|
"laravel-php": detectLaravel,
|
|
3770
3904
|
"vue-i18n-json": (root) => detectVue(root, true),
|
|
3771
3905
|
"flutter-arb": detectArb,
|
|
3772
|
-
"apple-strings": detectApple
|
|
3906
|
+
"apple-strings": detectApple,
|
|
3907
|
+
"angular-xliff": detectAngularXliff,
|
|
3908
|
+
"rails-yaml": detectRails,
|
|
3909
|
+
"i18next-json": detectI18next,
|
|
3910
|
+
"gettext-po": detectGettext,
|
|
3911
|
+
"apple-stringsdict": detectAppleStringsdict
|
|
3773
3912
|
};
|
|
3774
3913
|
function detect(root, formatOverride) {
|
|
3775
3914
|
if (!existsSync9(root)) return null;
|
|
@@ -3786,7 +3925,7 @@ function detect(root, formatOverride) {
|
|
|
3786
3925
|
}
|
|
3787
3926
|
|
|
3788
3927
|
// src/server/import/parsers/vue-i18n-json.ts
|
|
3789
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
3928
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
|
|
3790
3929
|
import { join as join5 } from "path";
|
|
3791
3930
|
|
|
3792
3931
|
// src/server/import/flatten.ts
|
|
@@ -3826,7 +3965,7 @@ var vueI18nJson2 = {
|
|
|
3826
3965
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
3827
3966
|
let data;
|
|
3828
3967
|
try {
|
|
3829
|
-
data = JSON.parse(
|
|
3968
|
+
data = JSON.parse(readFileSync10(join5(localeRoot, file), "utf8"));
|
|
3830
3969
|
} catch (e) {
|
|
3831
3970
|
warnings.push(`vue-i18n-json: failed to parse ${file}: ${e.message}`);
|
|
3832
3971
|
continue;
|
|
@@ -3918,7 +4057,7 @@ var laravelPhp2 = {
|
|
|
3918
4057
|
};
|
|
3919
4058
|
|
|
3920
4059
|
// src/server/import/parsers/flutter-arb.ts
|
|
3921
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
4060
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
3922
4061
|
import { join as join7 } from "path";
|
|
3923
4062
|
var LOCALE_RE3 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
3924
4063
|
function localeFromArbName(file) {
|
|
@@ -3955,7 +4094,7 @@ var flutterArb2 = {
|
|
|
3955
4094
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
3956
4095
|
let data;
|
|
3957
4096
|
try {
|
|
3958
|
-
data = JSON.parse(
|
|
4097
|
+
data = JSON.parse(readFileSync11(join7(localeRoot, file), "utf8"));
|
|
3959
4098
|
} catch (e) {
|
|
3960
4099
|
warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
|
|
3961
4100
|
continue;
|
|
@@ -3980,7 +4119,7 @@ var flutterArb2 = {
|
|
|
3980
4119
|
};
|
|
3981
4120
|
|
|
3982
4121
|
// src/server/import/parsers/apple-strings.ts
|
|
3983
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
4122
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
3984
4123
|
import { join as join8 } from "path";
|
|
3985
4124
|
var LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
3986
4125
|
var TABLE = "Localizable.strings";
|
|
@@ -4089,7 +4228,7 @@ var appleStrings2 = {
|
|
|
4089
4228
|
let text;
|
|
4090
4229
|
try {
|
|
4091
4230
|
if (!statSync4(file).isFile()) continue;
|
|
4092
|
-
text =
|
|
4231
|
+
text = readFileSync12(file, "utf8");
|
|
4093
4232
|
} catch {
|
|
4094
4233
|
continue;
|
|
4095
4234
|
}
|
|
@@ -4106,12 +4245,747 @@ var appleStrings2 = {
|
|
|
4106
4245
|
}
|
|
4107
4246
|
};
|
|
4108
4247
|
|
|
4248
|
+
// src/server/import/parsers/angular-xliff.ts
|
|
4249
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
|
|
4250
|
+
import { join as join9 } from "path";
|
|
4251
|
+
var LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4252
|
+
var FILE_RE = /^messages(?:\.(.+))?\.xlf$/;
|
|
4253
|
+
function decodeEntities(s) {
|
|
4254
|
+
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, "&");
|
|
4255
|
+
}
|
|
4256
|
+
function parseAttrs(s) {
|
|
4257
|
+
const out = {};
|
|
4258
|
+
for (const m of s.matchAll(/([\w-]+)="([^"]*)"/g)) out[m[1]] = decodeEntities(m[2]);
|
|
4259
|
+
return out;
|
|
4260
|
+
}
|
|
4261
|
+
function decodeInline(raw, addMeta) {
|
|
4262
|
+
let out = "";
|
|
4263
|
+
let last = 0;
|
|
4264
|
+
for (const m of raw.matchAll(/<x\b([^>]*?)\/>/g)) {
|
|
4265
|
+
out += decodeEntities(raw.slice(last, m.index));
|
|
4266
|
+
const attrs = parseAttrs(m[1]);
|
|
4267
|
+
const id = attrs["id"] ?? "X";
|
|
4268
|
+
const equiv = attrs["equiv-text"];
|
|
4269
|
+
const simple = equiv?.match(/^\{\{\s*(\w+)\s*\}\}$/);
|
|
4270
|
+
if (simple) {
|
|
4271
|
+
out += `{${simple[1]}}`;
|
|
4272
|
+
} else {
|
|
4273
|
+
out += `{${id}}`;
|
|
4274
|
+
const meta = {};
|
|
4275
|
+
if (attrs["ctype"]) meta.type = attrs["ctype"];
|
|
4276
|
+
if (equiv !== void 0) meta.example = equiv;
|
|
4277
|
+
addMeta(id, meta);
|
|
4278
|
+
}
|
|
4279
|
+
last = m.index + m[0].length;
|
|
4280
|
+
}
|
|
4281
|
+
return out + decodeEntities(raw.slice(last));
|
|
4282
|
+
}
|
|
4283
|
+
var angularXliff2 = {
|
|
4284
|
+
name: "angular-xliff",
|
|
4285
|
+
parse(localeRoot, opts) {
|
|
4286
|
+
const warnings = [];
|
|
4287
|
+
const keys = {};
|
|
4288
|
+
const locales = [];
|
|
4289
|
+
const seen = (loc) => {
|
|
4290
|
+
if (!locales.includes(loc)) locales.push(loc);
|
|
4291
|
+
};
|
|
4292
|
+
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));
|
|
4293
|
+
for (const file of files) {
|
|
4294
|
+
const fnameLocale = file.match(FILE_RE)[1];
|
|
4295
|
+
if (fnameLocale !== void 0 && !LOCALE_RE5.test(fnameLocale)) continue;
|
|
4296
|
+
let xml;
|
|
4297
|
+
try {
|
|
4298
|
+
xml = readFileSync13(join9(localeRoot, file), "utf8");
|
|
4299
|
+
} catch (e) {
|
|
4300
|
+
warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
|
|
4301
|
+
continue;
|
|
4302
|
+
}
|
|
4303
|
+
const sourceLocale = xml.match(/source-language="([^"]+)"/)?.[1];
|
|
4304
|
+
if (!sourceLocale) {
|
|
4305
|
+
warnings.push(`angular-xliff: ${file} has no source-language attribute; skipped`);
|
|
4306
|
+
continue;
|
|
4307
|
+
}
|
|
4308
|
+
const targetLocale = xml.match(/target-language="([^"]+)"/)?.[1] ?? fnameLocale;
|
|
4309
|
+
if (opts?.locales && !opts.locales.includes(targetLocale ?? sourceLocale)) continue;
|
|
4310
|
+
for (const unit of xml.matchAll(/<trans-unit\b([^>]*)>([\s\S]*?)<\/trans-unit>/g)) {
|
|
4311
|
+
const id = parseAttrs(unit[1])["id"];
|
|
4312
|
+
if (!id) {
|
|
4313
|
+
warnings.push(`angular-xliff: ${file} has a trans-unit without an id; skipped`);
|
|
4314
|
+
continue;
|
|
4315
|
+
}
|
|
4316
|
+
const body = unit[2];
|
|
4317
|
+
const src = body.match(/<source\b[^>]*>([\s\S]*?)<\/source>/);
|
|
4318
|
+
let tgt = body.match(/<target\b([^>]*)>([\s\S]*?)<\/target>/);
|
|
4319
|
+
if (tgt && /\bstate="new"/.test(tgt[1])) tgt = null;
|
|
4320
|
+
const entry = keys[id] ??= { values: {} };
|
|
4321
|
+
const addMeta = (name, meta) => {
|
|
4322
|
+
(entry.placeholders ??= {})[name] ??= meta;
|
|
4323
|
+
};
|
|
4324
|
+
if (src && entry.values[sourceLocale] === void 0) {
|
|
4325
|
+
entry.values[sourceLocale] = decodeInline(src[1], addMeta);
|
|
4326
|
+
seen(sourceLocale);
|
|
4327
|
+
}
|
|
4328
|
+
if (tgt && tgt[2] !== "" && targetLocale !== void 0) {
|
|
4329
|
+
entry.values[targetLocale] = decodeInline(tgt[2], addMeta);
|
|
4330
|
+
seen(targetLocale);
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
return { locales, keys, warnings };
|
|
4335
|
+
}
|
|
4336
|
+
};
|
|
4337
|
+
|
|
4338
|
+
// src/server/import/parsers/gettext-po.ts
|
|
4339
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync14 } from "fs";
|
|
4340
|
+
import { join as join10 } from "path";
|
|
4341
|
+
var LOCALE_RE6 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4342
|
+
var DIRECTIVE_RE = /^(msgctxt|msgid_plural|msgid|msgstr)(?:\[(\d+)\])?[ \t]+"(.*)"\s*$/;
|
|
4343
|
+
var CONT_RE = /^[ \t]*"(.*)"\s*$/;
|
|
4344
|
+
function unescapePo(s) {
|
|
4345
|
+
return s.replace(
|
|
4346
|
+
/\\([\\"ntr])/g,
|
|
4347
|
+
(_, c) => c === "n" ? "\n" : c === "t" ? " " : c === "r" ? "\r" : c
|
|
4348
|
+
);
|
|
4349
|
+
}
|
|
4350
|
+
function parseEntries(text) {
|
|
4351
|
+
const entries = [];
|
|
4352
|
+
let cur = null;
|
|
4353
|
+
let append = null;
|
|
4354
|
+
const flush = () => {
|
|
4355
|
+
if (cur && cur.msgid !== void 0) entries.push(cur);
|
|
4356
|
+
cur = null;
|
|
4357
|
+
append = null;
|
|
4358
|
+
};
|
|
4359
|
+
for (const line of text.split("\n")) {
|
|
4360
|
+
if (line.trim() === "") {
|
|
4361
|
+
flush();
|
|
4362
|
+
continue;
|
|
4363
|
+
}
|
|
4364
|
+
if (line.startsWith("#")) continue;
|
|
4365
|
+
const m = line.match(DIRECTIVE_RE);
|
|
4366
|
+
if (m) {
|
|
4367
|
+
const kw = m[1];
|
|
4368
|
+
const idx = m[2];
|
|
4369
|
+
const body = unescapePo(m[3]);
|
|
4370
|
+
if (cur && (kw === "msgctxt" || kw === "msgid" && cur.msgid !== void 0)) flush();
|
|
4371
|
+
cur ??= { plurals: /* @__PURE__ */ new Map() };
|
|
4372
|
+
const entry = cur;
|
|
4373
|
+
if (kw === "msgctxt") {
|
|
4374
|
+
entry.msgctxt = body;
|
|
4375
|
+
append = (c) => {
|
|
4376
|
+
entry.msgctxt = (entry.msgctxt ?? "") + c;
|
|
4377
|
+
};
|
|
4378
|
+
} else if (kw === "msgid") {
|
|
4379
|
+
entry.msgid = body;
|
|
4380
|
+
append = (c) => {
|
|
4381
|
+
entry.msgid = (entry.msgid ?? "") + c;
|
|
4382
|
+
};
|
|
4383
|
+
} else if (kw === "msgid_plural") {
|
|
4384
|
+
entry.msgidPlural = body;
|
|
4385
|
+
append = (c) => {
|
|
4386
|
+
entry.msgidPlural = (entry.msgidPlural ?? "") + c;
|
|
4387
|
+
};
|
|
4388
|
+
} else if (idx !== void 0) {
|
|
4389
|
+
const i = Number(idx);
|
|
4390
|
+
entry.plurals.set(i, body);
|
|
4391
|
+
append = (c) => {
|
|
4392
|
+
entry.plurals.set(i, (entry.plurals.get(i) ?? "") + c);
|
|
4393
|
+
};
|
|
4394
|
+
} else {
|
|
4395
|
+
entry.msgstr = body;
|
|
4396
|
+
append = (c) => {
|
|
4397
|
+
entry.msgstr = (entry.msgstr ?? "") + c;
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4400
|
+
continue;
|
|
4401
|
+
}
|
|
4402
|
+
const cont = line.match(CONT_RE);
|
|
4403
|
+
if (cont && append) append(unescapePo(cont[1]));
|
|
4404
|
+
}
|
|
4405
|
+
flush();
|
|
4406
|
+
return entries;
|
|
4407
|
+
}
|
|
4408
|
+
function discoverPoFiles(root) {
|
|
4409
|
+
const found = [];
|
|
4410
|
+
const entries = readdirSync9(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
4411
|
+
for (const e of entries) {
|
|
4412
|
+
if (e.isFile() && e.name.endsWith(".po")) {
|
|
4413
|
+
const base = e.name.slice(0, -3);
|
|
4414
|
+
found.push({ path: join10(root, e.name), rel: e.name, locale: LOCALE_RE6.test(base) ? base : null });
|
|
4415
|
+
} else if (e.isDirectory() && LOCALE_RE6.test(e.name)) {
|
|
4416
|
+
for (const sub of [join10(e.name, "LC_MESSAGES"), e.name]) {
|
|
4417
|
+
let names;
|
|
4418
|
+
try {
|
|
4419
|
+
names = readdirSync9(join10(root, sub)).sort();
|
|
4420
|
+
} catch {
|
|
4421
|
+
continue;
|
|
4422
|
+
}
|
|
4423
|
+
for (const f of names) {
|
|
4424
|
+
if (f.endsWith(".po")) found.push({ path: join10(root, sub, f), rel: join10(sub, f), locale: e.name });
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
return found;
|
|
4430
|
+
}
|
|
4431
|
+
var gettextPo2 = {
|
|
4432
|
+
name: "gettext-po",
|
|
4433
|
+
parse(localeRoot, opts) {
|
|
4434
|
+
const warnings = [];
|
|
4435
|
+
const keys = {};
|
|
4436
|
+
const locales = [];
|
|
4437
|
+
for (const file of discoverPoFiles(localeRoot)) {
|
|
4438
|
+
let entries;
|
|
4439
|
+
try {
|
|
4440
|
+
entries = parseEntries(readFileSync14(file.path, "utf8"));
|
|
4441
|
+
} catch (e) {
|
|
4442
|
+
warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
|
|
4443
|
+
continue;
|
|
4444
|
+
}
|
|
4445
|
+
const header = entries.find((e) => e.msgid === "" && e.msgctxt === void 0);
|
|
4446
|
+
const headerLang = header?.msgstr?.match(/^Language:[ \t]*([A-Za-z0-9_-]+)/m)?.[1];
|
|
4447
|
+
const locale = file.locale ?? headerLang;
|
|
4448
|
+
if (!locale) {
|
|
4449
|
+
warnings.push(`gettext-po: cannot determine locale for ${file.rel}; skipped`);
|
|
4450
|
+
continue;
|
|
4451
|
+
}
|
|
4452
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4453
|
+
if (!locales.includes(locale)) locales.push(locale);
|
|
4454
|
+
const cats = categoriesFor(locale);
|
|
4455
|
+
for (const entry of entries) {
|
|
4456
|
+
if (entry === header) continue;
|
|
4457
|
+
const key = entry.msgctxt ?? entry.msgid;
|
|
4458
|
+
if (!key) continue;
|
|
4459
|
+
if (entry.msgidPlural !== void 0) {
|
|
4460
|
+
const forms = {};
|
|
4461
|
+
for (const [i, body] of [...entry.plurals].sort((a, b) => a[0] - b[0])) {
|
|
4462
|
+
if (body === "") continue;
|
|
4463
|
+
const cat = cats[i];
|
|
4464
|
+
if (!cat) {
|
|
4465
|
+
warnings.push(
|
|
4466
|
+
`gettext-po: ${file.rel} "${key}": msgstr[${i}] exceeds the ${cats.length} plural forms of "${locale}"; ignored`
|
|
4467
|
+
);
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
forms[cat] = body.split("%d").join("{count}");
|
|
4471
|
+
}
|
|
4472
|
+
if (!forms.other) continue;
|
|
4473
|
+
(keys[key] ??= { values: {} }).values[locale] = formsToIcu("count", forms);
|
|
4474
|
+
} else {
|
|
4475
|
+
if (!entry.msgstr) continue;
|
|
4476
|
+
(keys[key] ??= { values: {} }).values[locale] = entry.msgstr;
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
return { locales, keys, warnings };
|
|
4481
|
+
}
|
|
4482
|
+
};
|
|
4483
|
+
|
|
4484
|
+
// src/server/import/parsers/i18next-json.ts
|
|
4485
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync15, statSync as statSync5 } from "fs";
|
|
4486
|
+
import { join as join11 } from "path";
|
|
4487
|
+
var LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4488
|
+
var PLURAL_SUFFIX_RE = /^(.+)_(zero|one|two|few|many|other)$/;
|
|
4489
|
+
var PLURAL_ARG = "count";
|
|
4490
|
+
var DEFAULT_NAMESPACE = "translation";
|
|
4491
|
+
function safeIsDir2(p) {
|
|
4492
|
+
try {
|
|
4493
|
+
return statSync5(p).isDirectory();
|
|
4494
|
+
} catch {
|
|
4495
|
+
return false;
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
function fromI18next(value) {
|
|
4499
|
+
if (isIcuPluralOrSelect(value)) return value;
|
|
4500
|
+
return value.replace(/\{\{(\w+)\}\}/g, "{$1}");
|
|
4501
|
+
}
|
|
4502
|
+
function ingestFile(path, label, prefix, locale, keys, warnings) {
|
|
4503
|
+
let data;
|
|
4504
|
+
try {
|
|
4505
|
+
data = JSON.parse(readFileSync15(path, "utf8"));
|
|
4506
|
+
} catch (e) {
|
|
4507
|
+
warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
|
|
4508
|
+
return false;
|
|
4509
|
+
}
|
|
4510
|
+
const fileWarnings = [];
|
|
4511
|
+
const flat = flattenObject(data, "", fileWarnings);
|
|
4512
|
+
for (const w of fileWarnings) warnings.push(`i18next-json: ${label}: ${w}`);
|
|
4513
|
+
const families = /* @__PURE__ */ new Set();
|
|
4514
|
+
for (const [k, v] of Object.entries(flat)) {
|
|
4515
|
+
const m = PLURAL_SUFFIX_RE.exec(k);
|
|
4516
|
+
if (m && m[2] === "other" && v !== "") families.add(m[1]);
|
|
4517
|
+
}
|
|
4518
|
+
const pluralForms = {};
|
|
4519
|
+
for (const [k, raw] of Object.entries(flat)) {
|
|
4520
|
+
if (raw === "") continue;
|
|
4521
|
+
const value = fromI18next(raw);
|
|
4522
|
+
const m = PLURAL_SUFFIX_RE.exec(k);
|
|
4523
|
+
if (m && families.has(m[1])) {
|
|
4524
|
+
(pluralForms[m[1]] ??= {})[m[2]] = value;
|
|
4525
|
+
continue;
|
|
4526
|
+
}
|
|
4527
|
+
if (families.has(k)) {
|
|
4528
|
+
warnings.push(
|
|
4529
|
+
`i18next-json: ${label}: key "${k}" collides with its own plural suffix family; the plural wins`
|
|
4530
|
+
);
|
|
4531
|
+
continue;
|
|
4532
|
+
}
|
|
4533
|
+
(keys[prefix + k] ??= { values: {} }).values[locale] = value;
|
|
4534
|
+
}
|
|
4535
|
+
for (const [base, forms] of Object.entries(pluralForms)) {
|
|
4536
|
+
(keys[prefix + base] ??= { values: {} }).values[locale] = formsToIcu(PLURAL_ARG, forms);
|
|
4537
|
+
}
|
|
4538
|
+
return true;
|
|
4539
|
+
}
|
|
4540
|
+
var i18nextJson2 = {
|
|
4541
|
+
name: "i18next-json",
|
|
4542
|
+
parse(localeRoot, opts) {
|
|
4543
|
+
const warnings = [];
|
|
4544
|
+
const keys = {};
|
|
4545
|
+
const locales = [];
|
|
4546
|
+
for (const entry of readdirSync10(localeRoot).sort()) {
|
|
4547
|
+
const full = join11(localeRoot, entry);
|
|
4548
|
+
if (safeIsDir2(full)) {
|
|
4549
|
+
if (!LOCALE_RE7.test(entry)) continue;
|
|
4550
|
+
if (opts?.locales && !opts.locales.includes(entry)) continue;
|
|
4551
|
+
let any = false;
|
|
4552
|
+
for (const file of readdirSync10(full).sort()) {
|
|
4553
|
+
if (!file.endsWith(".json")) continue;
|
|
4554
|
+
const ns = file.slice(0, -".json".length);
|
|
4555
|
+
const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
|
|
4556
|
+
if (ingestFile(join11(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
|
|
4557
|
+
}
|
|
4558
|
+
if (any && !locales.includes(entry)) locales.push(entry);
|
|
4559
|
+
} else if (entry.endsWith(".json")) {
|
|
4560
|
+
const locale = entry.slice(0, -".json".length);
|
|
4561
|
+
if (!LOCALE_RE7.test(locale)) continue;
|
|
4562
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4563
|
+
if (ingestFile(full, entry, "", locale, keys, warnings) && !locales.includes(locale)) {
|
|
4564
|
+
locales.push(locale);
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
return { locales, keys, warnings };
|
|
4569
|
+
}
|
|
4570
|
+
};
|
|
4571
|
+
|
|
4572
|
+
// src/server/import/parsers/rails-yaml.ts
|
|
4573
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync16 } from "fs";
|
|
4574
|
+
import { join as join12 } from "path";
|
|
4575
|
+
var LOCALE_RE8 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/i;
|
|
4576
|
+
var CATEGORY_SET = new Set(PLURAL_CATEGORIES);
|
|
4577
|
+
function fromRuby(value) {
|
|
4578
|
+
return value.replace(/%\{(\w+)\}/g, "{$1}");
|
|
4579
|
+
}
|
|
4580
|
+
function makeNode() {
|
|
4581
|
+
return /* @__PURE__ */ Object.create(null);
|
|
4582
|
+
}
|
|
4583
|
+
function decodeDouble(body) {
|
|
4584
|
+
let out = "";
|
|
4585
|
+
for (let i = 0; i < body.length; i++) {
|
|
4586
|
+
const c = body[i];
|
|
4587
|
+
if (c !== "\\") {
|
|
4588
|
+
out += c;
|
|
4589
|
+
continue;
|
|
4590
|
+
}
|
|
4591
|
+
const n = body[++i];
|
|
4592
|
+
if (n === void 0) break;
|
|
4593
|
+
out += n === "n" ? "\n" : n === "r" ? "\r" : n === "t" ? " " : n;
|
|
4594
|
+
}
|
|
4595
|
+
return out;
|
|
4596
|
+
}
|
|
4597
|
+
function scanQuoted(s, start) {
|
|
4598
|
+
const q = s[start];
|
|
4599
|
+
if (q === '"') {
|
|
4600
|
+
for (let i = start + 1; i < s.length; i++) {
|
|
4601
|
+
if (s[i] === "\\") i++;
|
|
4602
|
+
else if (s[i] === '"') return { text: decodeDouble(s.slice(start + 1, i)), end: i + 1 };
|
|
4603
|
+
}
|
|
4604
|
+
return null;
|
|
4605
|
+
}
|
|
4606
|
+
let out = "";
|
|
4607
|
+
for (let i = start + 1; i < s.length; i++) {
|
|
4608
|
+
if (s[i] === "'") {
|
|
4609
|
+
if (s[i + 1] === "'") {
|
|
4610
|
+
out += "'";
|
|
4611
|
+
i++;
|
|
4612
|
+
} else {
|
|
4613
|
+
return { text: out, end: i + 1 };
|
|
4614
|
+
}
|
|
4615
|
+
} else {
|
|
4616
|
+
out += s[i];
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
return null;
|
|
4620
|
+
}
|
|
4621
|
+
function stripPlainComment(s) {
|
|
4622
|
+
const m = /(^|\s)#/.exec(s);
|
|
4623
|
+
return (m && m.index >= 0 ? s.slice(0, m.index) : s).trim();
|
|
4624
|
+
}
|
|
4625
|
+
function onlyTrailing(s) {
|
|
4626
|
+
return /^\s*(#.*)?$/.test(s);
|
|
4627
|
+
}
|
|
4628
|
+
function parseYamlSubset(text, file, warnings) {
|
|
4629
|
+
const roots = {};
|
|
4630
|
+
const lines = text.split(/\r?\n/);
|
|
4631
|
+
let stack = [];
|
|
4632
|
+
let skipDeeperThan = null;
|
|
4633
|
+
let lastLeafIndent = null;
|
|
4634
|
+
for (let n = 0; n < lines.length; n++) {
|
|
4635
|
+
const raw = lines[n];
|
|
4636
|
+
const lineNo = n + 1;
|
|
4637
|
+
if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
|
|
4638
|
+
if (raw.trim() === "---") continue;
|
|
4639
|
+
const indentMatch = /^[ \t]*/.exec(raw)[0];
|
|
4640
|
+
if (indentMatch.includes(" ")) {
|
|
4641
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: tab in indentation; line skipped`);
|
|
4642
|
+
continue;
|
|
4643
|
+
}
|
|
4644
|
+
const indent = indentMatch.length;
|
|
4645
|
+
if (skipDeeperThan !== null) {
|
|
4646
|
+
if (indent > skipDeeperThan) continue;
|
|
4647
|
+
skipDeeperThan = null;
|
|
4648
|
+
}
|
|
4649
|
+
const content = raw.slice(indent);
|
|
4650
|
+
if (content.startsWith("- ") || content === "-") {
|
|
4651
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: sequences are not supported; node skipped`);
|
|
4652
|
+
skipDeeperThan = indent;
|
|
4653
|
+
continue;
|
|
4654
|
+
}
|
|
4655
|
+
let key;
|
|
4656
|
+
let rest;
|
|
4657
|
+
if (content[0] === '"' || content[0] === "'") {
|
|
4658
|
+
const k = scanQuoted(content, 0);
|
|
4659
|
+
if (!k || content[k.end] !== ":") {
|
|
4660
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unparseable quoted key; line skipped`);
|
|
4661
|
+
skipDeeperThan = indent;
|
|
4662
|
+
continue;
|
|
4663
|
+
}
|
|
4664
|
+
key = k.text;
|
|
4665
|
+
rest = content.slice(k.end + 1);
|
|
4666
|
+
} else {
|
|
4667
|
+
const m = /^(.*?):(?=\s|$)/.exec(content);
|
|
4668
|
+
if (!m) {
|
|
4669
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: not a "key: value" mapping line; line skipped`);
|
|
4670
|
+
skipDeeperThan = indent;
|
|
4671
|
+
continue;
|
|
4672
|
+
}
|
|
4673
|
+
key = m[1].trim();
|
|
4674
|
+
rest = content.slice(m[0].length);
|
|
4675
|
+
}
|
|
4676
|
+
if (lastLeafIndent !== null && indent > lastLeafIndent) {
|
|
4677
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unexpected indentation under a scalar; line skipped`);
|
|
4678
|
+
skipDeeperThan = indent - 1;
|
|
4679
|
+
continue;
|
|
4680
|
+
}
|
|
4681
|
+
while (stack.length > 0 && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
4682
|
+
const trimmed = rest.trim();
|
|
4683
|
+
let value;
|
|
4684
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
4685
|
+
value = null;
|
|
4686
|
+
} else if (trimmed[0] === "&" || trimmed[0] === "*") {
|
|
4687
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: YAML anchors/aliases are not supported; node skipped`);
|
|
4688
|
+
skipDeeperThan = indent;
|
|
4689
|
+
continue;
|
|
4690
|
+
} else if (trimmed[0] === "|" || trimmed[0] === ">") {
|
|
4691
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: block scalars are not supported; node skipped`);
|
|
4692
|
+
skipDeeperThan = indent;
|
|
4693
|
+
continue;
|
|
4694
|
+
} else if (trimmed[0] === "[" || trimmed[0] === "{") {
|
|
4695
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: flow collections are not supported; node skipped`);
|
|
4696
|
+
skipDeeperThan = indent;
|
|
4697
|
+
continue;
|
|
4698
|
+
} else if (trimmed[0] === '"' || trimmed[0] === "'") {
|
|
4699
|
+
const v = scanQuoted(trimmed, 0);
|
|
4700
|
+
if (!v || !onlyTrailing(trimmed.slice(v.end))) {
|
|
4701
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unterminated or trailing-garbage quoted value; line skipped`);
|
|
4702
|
+
continue;
|
|
4703
|
+
}
|
|
4704
|
+
value = v.text;
|
|
4705
|
+
} else {
|
|
4706
|
+
value = stripPlainComment(trimmed);
|
|
4707
|
+
}
|
|
4708
|
+
if (stack.length === 0) {
|
|
4709
|
+
if (value !== null) {
|
|
4710
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: top-level key "${key}" has a scalar value; skipped`);
|
|
4711
|
+
lastLeafIndent = indent;
|
|
4712
|
+
continue;
|
|
4713
|
+
}
|
|
4714
|
+
const root = roots[key] ??= makeNode();
|
|
4715
|
+
stack = [{ indent, node: root }];
|
|
4716
|
+
lastLeafIndent = null;
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
const parent = stack[stack.length - 1].node;
|
|
4720
|
+
if (key in parent) {
|
|
4721
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: duplicate key "${key}"; later value wins`);
|
|
4722
|
+
}
|
|
4723
|
+
if (value === null) {
|
|
4724
|
+
const child = makeNode();
|
|
4725
|
+
parent[key] = child;
|
|
4726
|
+
stack.push({ indent, node: child });
|
|
4727
|
+
lastLeafIndent = null;
|
|
4728
|
+
} else {
|
|
4729
|
+
parent[key] = value;
|
|
4730
|
+
lastLeafIndent = indent;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
return { roots };
|
|
4734
|
+
}
|
|
4735
|
+
function asPluralForms(node) {
|
|
4736
|
+
const entries = Object.entries(node);
|
|
4737
|
+
if (entries.length === 0) return null;
|
|
4738
|
+
const forms = {};
|
|
4739
|
+
for (const [k, v] of entries) {
|
|
4740
|
+
if (!CATEGORY_SET.has(k) || typeof v !== "string") return null;
|
|
4741
|
+
if (v !== "") forms[k] = v;
|
|
4742
|
+
}
|
|
4743
|
+
if (!("other" in forms)) return null;
|
|
4744
|
+
return forms;
|
|
4745
|
+
}
|
|
4746
|
+
function synthesizeIcu(forms, file, key, warnings) {
|
|
4747
|
+
const parts = [];
|
|
4748
|
+
for (const cat of PLURAL_CATEGORIES) {
|
|
4749
|
+
const body = forms[cat];
|
|
4750
|
+
if (body === void 0) continue;
|
|
4751
|
+
if (body.includes("#")) {
|
|
4752
|
+
warnings.push(
|
|
4753
|
+
`rails-yaml: ${file}: plural "${key}" form "${cat}" contains "#", which ICU reads as the count placeholder`
|
|
4754
|
+
);
|
|
4755
|
+
}
|
|
4756
|
+
parts.push(`${cat} {${fromRuby(body)}}`);
|
|
4757
|
+
}
|
|
4758
|
+
return `{count, plural, ${parts.join(" ")}}`;
|
|
4759
|
+
}
|
|
4760
|
+
var railsYaml2 = {
|
|
4761
|
+
name: "rails-yaml",
|
|
4762
|
+
parse(localeRoot, opts) {
|
|
4763
|
+
const warnings = [];
|
|
4764
|
+
const keys = {};
|
|
4765
|
+
const locales = [];
|
|
4766
|
+
const wanted = opts?.locales?.map((l) => l.toLowerCase());
|
|
4767
|
+
const addValue = (key, locale, value) => {
|
|
4768
|
+
(keys[key] ??= { values: {} }).values[locale] = value;
|
|
4769
|
+
};
|
|
4770
|
+
const flatten = (node, prefix, locale, file) => {
|
|
4771
|
+
for (const [k, v] of Object.entries(node)) {
|
|
4772
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
4773
|
+
if (typeof v === "string") {
|
|
4774
|
+
if (v !== "") addValue(key, locale, fromRuby(v));
|
|
4775
|
+
continue;
|
|
4776
|
+
}
|
|
4777
|
+
const forms = asPluralForms(v);
|
|
4778
|
+
if (forms) addValue(key, locale, synthesizeIcu(forms, file, key, warnings));
|
|
4779
|
+
else flatten(v, key, locale, file);
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4782
|
+
for (const file of readdirSync11(localeRoot).sort()) {
|
|
4783
|
+
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
4784
|
+
let text;
|
|
4785
|
+
try {
|
|
4786
|
+
text = readFileSync16(join12(localeRoot, file), "utf8");
|
|
4787
|
+
} catch (e) {
|
|
4788
|
+
warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
|
|
4789
|
+
continue;
|
|
4790
|
+
}
|
|
4791
|
+
const { roots } = parseYamlSubset(text, file, warnings);
|
|
4792
|
+
for (const token of Object.keys(roots).sort()) {
|
|
4793
|
+
if (!LOCALE_RE8.test(token)) {
|
|
4794
|
+
warnings.push(`rails-yaml: ${file}: top-level key "${token}" is not a locale; subtree skipped`);
|
|
4795
|
+
continue;
|
|
4796
|
+
}
|
|
4797
|
+
if (wanted && !wanted.includes(token.toLowerCase())) continue;
|
|
4798
|
+
if (!locales.includes(token)) locales.push(token);
|
|
4799
|
+
flatten(roots[token], "", token, file);
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
return { locales, keys, warnings };
|
|
4803
|
+
}
|
|
4804
|
+
};
|
|
4805
|
+
|
|
4806
|
+
// src/server/import/parsers/apple-stringsdict.ts
|
|
4807
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync17, statSync as statSync6 } from "fs";
|
|
4808
|
+
import { join as join13 } from "path";
|
|
4809
|
+
var LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4810
|
+
var TABLE2 = "Localizable.stringsdict";
|
|
4811
|
+
function localeFromLproj2(dir) {
|
|
4812
|
+
const m = dir.match(/^(.+)\.lproj$/);
|
|
4813
|
+
if (!m) return null;
|
|
4814
|
+
return LOCALE_RE9.test(m[1]) ? m[1] : null;
|
|
4815
|
+
}
|
|
4816
|
+
function decodeEntities2(s) {
|
|
4817
|
+
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, "&");
|
|
4818
|
+
}
|
|
4819
|
+
function parsePlistDict(xml) {
|
|
4820
|
+
let i = 0;
|
|
4821
|
+
const n = xml.length;
|
|
4822
|
+
const skipTrivia = () => {
|
|
4823
|
+
for (; ; ) {
|
|
4824
|
+
while (i < n && /\s/.test(xml[i])) i++;
|
|
4825
|
+
if (xml.startsWith("<!--", i)) {
|
|
4826
|
+
const end = xml.indexOf("-->", i + 4);
|
|
4827
|
+
if (end === -1) throw new Error("unterminated comment");
|
|
4828
|
+
i = end + 3;
|
|
4829
|
+
continue;
|
|
4830
|
+
}
|
|
4831
|
+
if (xml.startsWith("<?", i) || xml.startsWith("<!", i) && !xml.startsWith("<!--", i)) {
|
|
4832
|
+
const end = xml.indexOf(">", i);
|
|
4833
|
+
if (end === -1) throw new Error("unterminated declaration");
|
|
4834
|
+
i = end + 1;
|
|
4835
|
+
continue;
|
|
4836
|
+
}
|
|
4837
|
+
break;
|
|
4838
|
+
}
|
|
4839
|
+
};
|
|
4840
|
+
const readTag = () => {
|
|
4841
|
+
if (xml[i] !== "<") throw new Error(`expected a tag at offset ${i}`);
|
|
4842
|
+
const end = xml.indexOf(">", i);
|
|
4843
|
+
if (end === -1) throw new Error("unterminated tag");
|
|
4844
|
+
let body = xml.slice(i + 1, end).trim();
|
|
4845
|
+
i = end + 1;
|
|
4846
|
+
const closing = body.startsWith("/");
|
|
4847
|
+
if (closing) body = body.slice(1).trim();
|
|
4848
|
+
const selfClosing = body.endsWith("/");
|
|
4849
|
+
if (selfClosing) body = body.slice(0, -1).trim();
|
|
4850
|
+
const name = body.split(/\s/)[0];
|
|
4851
|
+
if (!name) throw new Error(`empty tag at offset ${end}`);
|
|
4852
|
+
return { name, closing, selfClosing };
|
|
4853
|
+
};
|
|
4854
|
+
const readElementText = (name) => {
|
|
4855
|
+
const re = new RegExp(`</${name}\\s*>`, "g");
|
|
4856
|
+
re.lastIndex = i;
|
|
4857
|
+
const m = re.exec(xml);
|
|
4858
|
+
if (!m) throw new Error(`unterminated <${name}>`);
|
|
4859
|
+
const text = xml.slice(i, m.index);
|
|
4860
|
+
i = m.index + m[0].length;
|
|
4861
|
+
return decodeEntities2(text);
|
|
4862
|
+
};
|
|
4863
|
+
const readValue = (tag2) => {
|
|
4864
|
+
if (tag2.name === "dict") return tag2.selfClosing ? {} : readDict();
|
|
4865
|
+
if (tag2.name === "true" || tag2.name === "false") {
|
|
4866
|
+
if (!tag2.selfClosing) readElementText(tag2.name);
|
|
4867
|
+
return tag2.name;
|
|
4868
|
+
}
|
|
4869
|
+
if (["string", "integer", "real", "date", "data"].includes(tag2.name)) {
|
|
4870
|
+
return tag2.selfClosing ? "" : readElementText(tag2.name);
|
|
4871
|
+
}
|
|
4872
|
+
throw new Error(`unsupported plist element <${tag2.name}>`);
|
|
4873
|
+
};
|
|
4874
|
+
const readDict = () => {
|
|
4875
|
+
const out = {};
|
|
4876
|
+
for (; ; ) {
|
|
4877
|
+
skipTrivia();
|
|
4878
|
+
const tag2 = readTag();
|
|
4879
|
+
if (tag2.closing) {
|
|
4880
|
+
if (tag2.name !== "dict") throw new Error(`unexpected </${tag2.name}> inside <dict>`);
|
|
4881
|
+
return out;
|
|
4882
|
+
}
|
|
4883
|
+
if (tag2.name !== "key") throw new Error(`expected <key> inside <dict>, got <${tag2.name}>`);
|
|
4884
|
+
const key = readElementText("key");
|
|
4885
|
+
skipTrivia();
|
|
4886
|
+
const vt = readTag();
|
|
4887
|
+
if (vt.closing) throw new Error(`<key>${key}</key> has no value`);
|
|
4888
|
+
out[key] = readValue(vt);
|
|
4889
|
+
}
|
|
4890
|
+
};
|
|
4891
|
+
skipTrivia();
|
|
4892
|
+
let tag = readTag();
|
|
4893
|
+
if (tag.name === "plist" && !tag.closing && !tag.selfClosing) {
|
|
4894
|
+
skipTrivia();
|
|
4895
|
+
tag = readTag();
|
|
4896
|
+
}
|
|
4897
|
+
if (tag.name !== "dict" || tag.closing) throw new Error("expected a root <dict>");
|
|
4898
|
+
return tag.selfClosing ? {} : readDict();
|
|
4899
|
+
}
|
|
4900
|
+
var VAR_RE = /%#@([^@]*)@/g;
|
|
4901
|
+
function entryToIcu(key, entry, file, warnings) {
|
|
4902
|
+
const warn = (msg) => {
|
|
4903
|
+
warnings.push(`apple-stringsdict: ${file}: key "${key}": ${msg}`);
|
|
4904
|
+
return null;
|
|
4905
|
+
};
|
|
4906
|
+
if (typeof entry !== "object") return warn("value is not a dict; skipped");
|
|
4907
|
+
const fmt = entry["NSStringLocalizedFormatKey"];
|
|
4908
|
+
if (typeof fmt !== "string") return warn("missing NSStringLocalizedFormatKey; skipped");
|
|
4909
|
+
const vars = [...fmt.matchAll(VAR_RE)];
|
|
4910
|
+
if (vars.length !== 1) {
|
|
4911
|
+
return warn(`format key has ${vars.length} %#@\u2026@ variables; only exactly one is supported; skipped`);
|
|
4912
|
+
}
|
|
4913
|
+
const arg = vars[0][1];
|
|
4914
|
+
if (!/^\w+$/.test(arg)) return warn(`variable name "${arg}" is not a valid ICU argument; skipped`);
|
|
4915
|
+
const prefix = fmt.slice(0, vars[0].index);
|
|
4916
|
+
const suffix = fmt.slice(vars[0].index + vars[0][0].length);
|
|
4917
|
+
const varDict = entry[arg];
|
|
4918
|
+
if (typeof varDict !== "object") return warn(`variable "${arg}" has no dict; skipped`);
|
|
4919
|
+
const specType = varDict["NSStringFormatSpecTypeKey"];
|
|
4920
|
+
if (specType !== void 0 && specType !== "NSStringPluralRuleType") {
|
|
4921
|
+
return warn(`variable "${arg}" is not a plural rule (${String(specType)}); skipped`);
|
|
4922
|
+
}
|
|
4923
|
+
const valueType = varDict["NSStringFormatValueTypeKey"];
|
|
4924
|
+
const token = `%${typeof valueType === "string" && valueType ? valueType : "d"}`;
|
|
4925
|
+
const forms = {};
|
|
4926
|
+
for (const cat of PLURAL_CATEGORIES) {
|
|
4927
|
+
const body = varDict[cat];
|
|
4928
|
+
if (typeof body !== "string") continue;
|
|
4929
|
+
forms[cat] = prefix + body.split(token).join(`{${arg}}`) + suffix;
|
|
4930
|
+
}
|
|
4931
|
+
if (forms.other === void 0) return warn(`variable "${arg}" has no "other" form; skipped`);
|
|
4932
|
+
return formsToIcu(arg, forms);
|
|
4933
|
+
}
|
|
4934
|
+
var appleStringsdict2 = {
|
|
4935
|
+
name: "apple-stringsdict",
|
|
4936
|
+
parse(localeRoot, opts) {
|
|
4937
|
+
const warnings = [];
|
|
4938
|
+
const keys = {};
|
|
4939
|
+
const locales = [];
|
|
4940
|
+
for (const dir of readdirSync12(localeRoot).sort()) {
|
|
4941
|
+
const locale = localeFromLproj2(dir);
|
|
4942
|
+
if (!locale) continue;
|
|
4943
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4944
|
+
const file = join13(localeRoot, dir, TABLE2);
|
|
4945
|
+
let text;
|
|
4946
|
+
try {
|
|
4947
|
+
if (!statSync6(file).isFile()) continue;
|
|
4948
|
+
text = readFileSync17(file, "utf8");
|
|
4949
|
+
} catch {
|
|
4950
|
+
continue;
|
|
4951
|
+
}
|
|
4952
|
+
locales.push(locale);
|
|
4953
|
+
const others = readdirSync12(join13(localeRoot, dir)).filter(
|
|
4954
|
+
(f) => f.endsWith(".stringsdict") && f !== TABLE2
|
|
4955
|
+
);
|
|
4956
|
+
if (others.length) {
|
|
4957
|
+
warnings.push(
|
|
4958
|
+
`apple-stringsdict: ${dir} has other .stringsdict tables (${others.join(", ")}); only ${TABLE2} is imported`
|
|
4959
|
+
);
|
|
4960
|
+
}
|
|
4961
|
+
let root;
|
|
4962
|
+
try {
|
|
4963
|
+
root = parsePlistDict(text);
|
|
4964
|
+
} catch (e) {
|
|
4965
|
+
warnings.push(`apple-stringsdict: failed to parse ${file}: ${e.message}`);
|
|
4966
|
+
continue;
|
|
4967
|
+
}
|
|
4968
|
+
for (const key of Object.keys(root).sort()) {
|
|
4969
|
+
const icu = entryToIcu(key, root[key], file, warnings);
|
|
4970
|
+
if (icu === null) continue;
|
|
4971
|
+
(keys[key] ??= { values: {} }).values[locale] = icu;
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
return { locales, keys, warnings };
|
|
4975
|
+
}
|
|
4976
|
+
};
|
|
4977
|
+
|
|
4109
4978
|
// src/server/import/parsers/index.ts
|
|
4110
4979
|
var REGISTRY = {
|
|
4111
4980
|
[vueI18nJson2.name]: vueI18nJson2,
|
|
4112
4981
|
[laravelPhp2.name]: laravelPhp2,
|
|
4113
4982
|
[flutterArb2.name]: flutterArb2,
|
|
4114
|
-
[appleStrings2.name]: appleStrings2
|
|
4983
|
+
[appleStrings2.name]: appleStrings2,
|
|
4984
|
+
[angularXliff2.name]: angularXliff2,
|
|
4985
|
+
[gettextPo2.name]: gettextPo2,
|
|
4986
|
+
[i18nextJson2.name]: i18nextJson2,
|
|
4987
|
+
[railsYaml2.name]: railsYaml2,
|
|
4988
|
+
[appleStringsdict2.name]: appleStringsdict2
|
|
4115
4989
|
};
|
|
4116
4990
|
function getParser(name) {
|
|
4117
4991
|
const p = REGISTRY[name];
|
|
@@ -4124,7 +4998,14 @@ var OUTPUT_BY_FORMAT = {
|
|
|
4124
4998
|
"laravel-php": { adapter: "laravel-php", path: "lang/{locale}/{namespace}.php" },
|
|
4125
4999
|
"vue-i18n-json": { adapter: "vue-i18n-json", path: "src/locale/{locale}.json" },
|
|
4126
5000
|
"flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" },
|
|
4127
|
-
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true }
|
|
5001
|
+
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true },
|
|
5002
|
+
// skipSourceLocale: ng extract-i18n owns messages.xlf (the source file); glotfile
|
|
5003
|
+
// only writes the translation files back next to it.
|
|
5004
|
+
"angular-xliff": { adapter: "angular-xliff", path: "messages.{locale}.xlf", rootRelative: true, skipSourceLocale: true },
|
|
5005
|
+
"gettext-po": { adapter: "gettext-po", path: "{locale}.po", rootRelative: true },
|
|
5006
|
+
"i18next-json": { adapter: "i18next-json", path: "{locale}/translation.json", rootRelative: true },
|
|
5007
|
+
"rails-yaml": { adapter: "rails-yaml", path: "config/locales/{locale}.yml" },
|
|
5008
|
+
"apple-stringsdict": { adapter: "apple-stringsdict", path: "{locale}.lproj/Localizable.stringsdict", rootRelative: true }
|
|
4128
5009
|
};
|
|
4129
5010
|
function assemble2(parsed, opts) {
|
|
4130
5011
|
const warnings = [...parsed.warnings];
|
|
@@ -4242,7 +5123,7 @@ function runImport(opts) {
|
|
|
4242
5123
|
}
|
|
4243
5124
|
|
|
4244
5125
|
// src/server/export-run.ts
|
|
4245
|
-
import { existsSync as existsSync10, readFileSync as
|
|
5126
|
+
import { existsSync as existsSync10, readFileSync as readFileSync18, readdirSync as readdirSync13, rmdirSync, statSync as statSync7, unlinkSync } from "fs";
|
|
4246
5127
|
import { dirname as dirname2, resolve as resolve7, sep } from "path";
|
|
4247
5128
|
function effectiveLocales(config) {
|
|
4248
5129
|
const limit = config.exportLocales;
|
|
@@ -4285,7 +5166,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
4285
5166
|
if (!segment.includes("{locale}") && !segment.includes("{namespace}")) {
|
|
4286
5167
|
const next = resolve7(dir, segment);
|
|
4287
5168
|
if (isLast) {
|
|
4288
|
-
if (stale(locale) && existsSync10(next) &&
|
|
5169
|
+
if (stale(locale) && existsSync10(next) && statSync7(next).isFile()) {
|
|
4289
5170
|
unlinkSync(next);
|
|
4290
5171
|
deleted++;
|
|
4291
5172
|
removeEmptyDirs(dir, root);
|
|
@@ -4298,7 +5179,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
4298
5179
|
const re = segmentRegExp(segment);
|
|
4299
5180
|
let entries;
|
|
4300
5181
|
try {
|
|
4301
|
-
entries =
|
|
5182
|
+
entries = readdirSync13(dir, { withFileTypes: true });
|
|
4302
5183
|
} catch {
|
|
4303
5184
|
return;
|
|
4304
5185
|
}
|
|
@@ -4341,7 +5222,7 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
4341
5222
|
writtenPaths.add(abs);
|
|
4342
5223
|
let current = null;
|
|
4343
5224
|
try {
|
|
4344
|
-
current =
|
|
5225
|
+
current = readFileSync18(abs, "utf8");
|
|
4345
5226
|
} catch {
|
|
4346
5227
|
}
|
|
4347
5228
|
if (current === f.contents) {
|
|
@@ -4358,17 +5239,17 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
4358
5239
|
}
|
|
4359
5240
|
|
|
4360
5241
|
// src/server/ui-prefs.ts
|
|
4361
|
-
import { readFileSync as
|
|
5242
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
4362
5243
|
import { homedir } from "os";
|
|
4363
|
-
import { join as
|
|
5244
|
+
import { join as join14 } from "path";
|
|
4364
5245
|
var THEMES = ["system", "light", "dark"];
|
|
4365
5246
|
var isThemeMode = (v) => THEMES.includes(v);
|
|
4366
5247
|
var isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
4367
|
-
var defaultUiPrefsPath = () =>
|
|
5248
|
+
var defaultUiPrefsPath = () => join14(homedir(), ".glotfile", "ui.json");
|
|
4368
5249
|
var DEFAULTS = { theme: "system" };
|
|
4369
5250
|
function readJson(path) {
|
|
4370
5251
|
try {
|
|
4371
|
-
const parsed = JSON.parse(
|
|
5252
|
+
const parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
4372
5253
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4373
5254
|
} catch {
|
|
4374
5255
|
return {};
|
|
@@ -4387,7 +5268,7 @@ function saveUiPrefs(path, prefs) {
|
|
|
4387
5268
|
}
|
|
4388
5269
|
|
|
4389
5270
|
// src/server/local-settings.ts
|
|
4390
|
-
import { readFileSync as
|
|
5271
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
4391
5272
|
import { resolve as resolve8 } from "path";
|
|
4392
5273
|
var EDITOR_IDS = ["vscode", "zed", "phpstorm"];
|
|
4393
5274
|
var isEditorId = (v) => EDITOR_IDS.includes(v);
|
|
@@ -4402,7 +5283,7 @@ var DEFAULT_EDITOR = "vscode";
|
|
|
4402
5283
|
var settingsPath = (projectRoot) => resolve8(projectRoot, ".glotfile", "settings.json");
|
|
4403
5284
|
function readJson2(path) {
|
|
4404
5285
|
try {
|
|
4405
|
-
const parsed = JSON.parse(
|
|
5286
|
+
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
4406
5287
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4407
5288
|
} catch {
|
|
4408
5289
|
return {};
|
|
@@ -4475,7 +5356,7 @@ function projectName(root) {
|
|
|
4475
5356
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
4476
5357
|
if (existsSync11(nameFile)) {
|
|
4477
5358
|
try {
|
|
4478
|
-
const name =
|
|
5359
|
+
const name = readFileSync21(nameFile, "utf8").trim();
|
|
4479
5360
|
if (name) return name;
|
|
4480
5361
|
} catch {
|
|
4481
5362
|
}
|
|
@@ -4600,7 +5481,7 @@ function createApi(deps) {
|
|
|
4600
5481
|
if (depth > 4) return;
|
|
4601
5482
|
let entries = [];
|
|
4602
5483
|
try {
|
|
4603
|
-
entries =
|
|
5484
|
+
entries = readdirSync14(dir);
|
|
4604
5485
|
} catch {
|
|
4605
5486
|
return;
|
|
4606
5487
|
}
|
|
@@ -4614,7 +5495,7 @@ function createApi(deps) {
|
|
|
4614
5495
|
filePath = abs;
|
|
4615
5496
|
} else {
|
|
4616
5497
|
try {
|
|
4617
|
-
if (
|
|
5498
|
+
if (statSync8(abs).isDirectory()) walk(abs, depth + 1);
|
|
4618
5499
|
} catch {
|
|
4619
5500
|
}
|
|
4620
5501
|
continue;
|
|
@@ -5395,7 +6276,7 @@ function createApi(deps) {
|
|
|
5395
6276
|
|
|
5396
6277
|
// src/server/server.ts
|
|
5397
6278
|
var here = dirname4(fileURLToPath(import.meta.url));
|
|
5398
|
-
var DEFAULT_UI_DIR =
|
|
6279
|
+
var DEFAULT_UI_DIR = join15(here, "..", "ui");
|
|
5399
6280
|
var MIME = {
|
|
5400
6281
|
".html": "text/html; charset=utf-8",
|
|
5401
6282
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -5449,7 +6330,7 @@ function buildApp(opts) {
|
|
|
5449
6330
|
const file = await readFileResponse(target);
|
|
5450
6331
|
if (file) return file;
|
|
5451
6332
|
}
|
|
5452
|
-
const index = await readFileResponse(
|
|
6333
|
+
const index = await readFileResponse(join15(root, "index.html"));
|
|
5453
6334
|
if (index) return index;
|
|
5454
6335
|
return c.notFound();
|
|
5455
6336
|
});
|