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/cli.js
CHANGED
|
@@ -122,6 +122,7 @@ function validate(raw) {
|
|
|
122
122
|
if (o.indent !== void 0 && typeof o.indent !== "number") fail("config.outputs[].indent must be a number");
|
|
123
123
|
if (o.finalNewline !== void 0 && typeof o.finalNewline !== "boolean") fail("config.outputs[].finalNewline must be a boolean");
|
|
124
124
|
if (o.includeLocale !== void 0 && typeof o.includeLocale !== "boolean") fail("config.outputs[].includeLocale must be a boolean");
|
|
125
|
+
if (o.skipSourceLocale !== void 0 && typeof o.skipSourceLocale !== "boolean") fail("config.outputs[].skipSourceLocale must be a boolean");
|
|
125
126
|
if (o.localeAliases !== void 0) {
|
|
126
127
|
if (!isObject(o.localeAliases)) fail("config.outputs[].localeAliases must be an object");
|
|
127
128
|
for (const [k, v] of Object.entries(o.localeAliases)) {
|
|
@@ -1574,27 +1575,41 @@ var init_vue_i18n_json = __esm({
|
|
|
1574
1575
|
function xmlEscape2(s) {
|
|
1575
1576
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1576
1577
|
}
|
|
1577
|
-
function
|
|
1578
|
+
function attrEscape(s) {
|
|
1579
|
+
return xmlEscape2(s).replace(/"/g, """);
|
|
1580
|
+
}
|
|
1581
|
+
function angularXMeta(placeholders, name) {
|
|
1582
|
+
return /^[A-Z][A-Z0-9_]*$/.test(name) ? placeholders?.[name] : void 0;
|
|
1583
|
+
}
|
|
1584
|
+
function renderInterpolations(text, ids, placeholders) {
|
|
1578
1585
|
let out = "";
|
|
1579
1586
|
let last = 0;
|
|
1580
1587
|
for (const m of text.matchAll(/\{(\w+)\}/g)) {
|
|
1581
1588
|
const name = m[1];
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1589
|
+
out += xmlEscape2(text.slice(last, m.index));
|
|
1590
|
+
const meta = angularXMeta(placeholders, name);
|
|
1591
|
+
if (meta) {
|
|
1592
|
+
const ctype = meta.type ? ` ctype="${attrEscape(meta.type)}"` : "";
|
|
1593
|
+
const equiv = meta.example !== void 0 ? ` equiv-text="${attrEscape(meta.example)}"` : "";
|
|
1594
|
+
out += `<x id="${attrEscape(name)}"${ctype}${equiv}/>`;
|
|
1595
|
+
} else {
|
|
1596
|
+
let id = ids.get(name);
|
|
1597
|
+
if (id === void 0) {
|
|
1598
|
+
id = ids.size === 0 ? "INTERPOLATION" : `INTERPOLATION_${ids.size}`;
|
|
1599
|
+
ids.set(name, id);
|
|
1600
|
+
}
|
|
1601
|
+
out += `<x id="${id}" equiv-text="{{${name}}}"/>`;
|
|
1586
1602
|
}
|
|
1587
|
-
out += xmlEscape2(text.slice(last, m.index)) + `<x id="${id}" equiv-text="{{${name}}}"/>`;
|
|
1588
1603
|
last = m.index + m[0].length;
|
|
1589
1604
|
}
|
|
1590
1605
|
return out + xmlEscape2(text.slice(last));
|
|
1591
1606
|
}
|
|
1592
|
-
function renderPluralIcu(forms, ids) {
|
|
1607
|
+
function renderPluralIcu(forms, ids, placeholders) {
|
|
1593
1608
|
const cats = [
|
|
1594
1609
|
...Object.keys(forms).filter((c) => c.startsWith("=")),
|
|
1595
1610
|
...PLURAL_CATEGORIES.filter((c) => forms[c] !== void 0)
|
|
1596
1611
|
];
|
|
1597
|
-
const branches = cats.map((cat) => `${cat} {${renderInterpolations(forms[cat] ?? "", ids)}}`);
|
|
1612
|
+
const branches = cats.map((cat) => `${cat} {${renderInterpolations(forms[cat] ?? "", ids, placeholders)}}`);
|
|
1598
1613
|
return `{VAR_PLURAL, plural, ${branches.join(" ")}}`;
|
|
1599
1614
|
}
|
|
1600
1615
|
function renderEmbeddedIcu(value) {
|
|
@@ -1604,8 +1619,8 @@ function renderEmbeddedIcu(value) {
|
|
|
1604
1619
|
);
|
|
1605
1620
|
return xmlEscape2(renamed);
|
|
1606
1621
|
}
|
|
1607
|
-
function renderScalar(value, ids) {
|
|
1608
|
-
return isIcuPluralOrSelect(value) ? renderEmbeddedIcu(value) : renderInterpolations(value, ids);
|
|
1622
|
+
function renderScalar(value, ids, placeholders) {
|
|
1623
|
+
return isIcuPluralOrSelect(value) ? renderEmbeddedIcu(value) : renderInterpolations(value, ids, placeholders);
|
|
1609
1624
|
}
|
|
1610
1625
|
var DEFAULT_LOCALE_CASE8, angularXliff;
|
|
1611
1626
|
var init_angular_xliff = __esm({
|
|
@@ -1636,6 +1651,7 @@ var init_angular_xliff = __esm({
|
|
|
1636
1651
|
const emptyAs = resolveEmptyAs(output, "source");
|
|
1637
1652
|
const keys = Object.keys(state.keys).sort();
|
|
1638
1653
|
for (const locale of state.config.locales) {
|
|
1654
|
+
if (output.skipSourceLocale && locale === sourceLocale) continue;
|
|
1639
1655
|
const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE8);
|
|
1640
1656
|
const units = [];
|
|
1641
1657
|
for (const key of keys) {
|
|
@@ -1646,17 +1662,18 @@ var init_angular_xliff = __esm({
|
|
|
1646
1662
|
if (entry.plural) {
|
|
1647
1663
|
const targetForms = resolveForms(entry, locale, sourceLocale, emptyAs);
|
|
1648
1664
|
if (targetForms === null) continue;
|
|
1649
|
-
source = renderPluralIcu(entry.values[sourceLocale]?.forms ?? {}, ids);
|
|
1650
|
-
target = renderPluralIcu(targetForms, ids);
|
|
1665
|
+
source = renderPluralIcu(entry.values[sourceLocale]?.forms ?? {}, ids, entry.placeholders);
|
|
1666
|
+
target = renderPluralIcu(targetForms, ids, entry.placeholders);
|
|
1651
1667
|
} else {
|
|
1652
1668
|
const targetValue = resolveScalar(entry, locale, sourceLocale, emptyAs);
|
|
1653
1669
|
if (targetValue === null) continue;
|
|
1654
|
-
source = renderScalar(entry.values[sourceLocale]?.value ?? "", ids);
|
|
1655
|
-
target = renderScalar(targetValue, ids);
|
|
1670
|
+
source = renderScalar(entry.values[sourceLocale]?.value ?? "", ids, entry.placeholders);
|
|
1671
|
+
target = renderScalar(targetValue, ids, entry.placeholders);
|
|
1656
1672
|
}
|
|
1673
|
+
const translated = locale === sourceLocale || (entry.plural ? entry.values[locale]?.forms !== void 0 : !!entry.values[locale]?.value);
|
|
1657
1674
|
units.push(` <trans-unit id="${xmlEscape2(key)}" datatype="html">`);
|
|
1658
1675
|
units.push(` <source>${source}</source>`);
|
|
1659
|
-
units.push(` <target>${target}</target>`);
|
|
1676
|
+
units.push(` <target${translated ? "" : ' state="new"'}>${target}</target>`);
|
|
1660
1677
|
if (entry.description) {
|
|
1661
1678
|
units.push(` <note priority="1" from="description">${xmlEscape2(entry.description)}</note>`);
|
|
1662
1679
|
}
|
|
@@ -1964,7 +1981,7 @@ function buildSystemPrompt(hasPluralItems) {
|
|
|
1964
1981
|
"- Preserve ICU plural/select structure verbatim (e.g. {count, plural, one {\u2026} other {\u2026}}); translate only the human-readable text inside each branch.",
|
|
1965
1982
|
"- 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.",
|
|
1966
1983
|
"- Respect the max length (characters) when given; prefer a shorter natural phrasing over exceeding it.",
|
|
1967
|
-
|
|
1984
|
+
'- 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.',
|
|
1968
1985
|
"- Match the register and capitalization conventions of the target language and of UI microcopy.",
|
|
1969
1986
|
"- Return ONLY the translated string for each item \u2014 no quotes, notes, or explanations."
|
|
1970
1987
|
];
|
|
@@ -2054,12 +2071,66 @@ var init_provider = __esm({
|
|
|
2054
2071
|
});
|
|
2055
2072
|
|
|
2056
2073
|
// src/server/ai/batch.ts
|
|
2074
|
+
function repairUnescapedQuotes(text) {
|
|
2075
|
+
const skipWs = (from) => {
|
|
2076
|
+
let i = from;
|
|
2077
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
2078
|
+
return i;
|
|
2079
|
+
};
|
|
2080
|
+
const stack = [];
|
|
2081
|
+
let out = "";
|
|
2082
|
+
let inString = false;
|
|
2083
|
+
let isKey = false;
|
|
2084
|
+
for (let i = 0; i < text.length; i++) {
|
|
2085
|
+
const ch = text[i];
|
|
2086
|
+
const top = stack[stack.length - 1];
|
|
2087
|
+
if (inString) {
|
|
2088
|
+
if (ch === "\\") {
|
|
2089
|
+
out += ch + (text[i + 1] ?? "");
|
|
2090
|
+
i++;
|
|
2091
|
+
} else if (ch !== '"') {
|
|
2092
|
+
out += ch;
|
|
2093
|
+
} else {
|
|
2094
|
+
const next = text[skipWs(i + 1)];
|
|
2095
|
+
const startsNextMember = () => {
|
|
2096
|
+
const after = text[skipWs(skipWs(i + 1) + 1)];
|
|
2097
|
+
return top?.type === "obj" ? after === '"' : after === "{" || after === "[" || after === '"';
|
|
2098
|
+
};
|
|
2099
|
+
const closes = isKey ? next === ":" : next === "}" || next === "]" || next === void 0 || next === "," && startsNextMember();
|
|
2100
|
+
if (closes) {
|
|
2101
|
+
inString = false;
|
|
2102
|
+
out += ch;
|
|
2103
|
+
} else {
|
|
2104
|
+
out += '\\"';
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
out += ch;
|
|
2110
|
+
if (ch === '"') {
|
|
2111
|
+
inString = true;
|
|
2112
|
+
isKey = top?.type === "obj" && top.expectingKey;
|
|
2113
|
+
} else if (ch === "{") stack.push({ type: "obj", expectingKey: true });
|
|
2114
|
+
else if (ch === "[") stack.push({ type: "arr", expectingKey: false });
|
|
2115
|
+
else if (ch === "}" || ch === "]") stack.pop();
|
|
2116
|
+
else if (ch === "," && top?.type === "obj") top.expectingKey = true;
|
|
2117
|
+
else if (ch === ":" && top) top.expectingKey = false;
|
|
2118
|
+
}
|
|
2119
|
+
try {
|
|
2120
|
+
JSON.parse(out);
|
|
2121
|
+
return out;
|
|
2122
|
+
} catch {
|
|
2123
|
+
return void 0;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2057
2126
|
function parseReplyItems(text) {
|
|
2058
2127
|
let parsed;
|
|
2059
2128
|
try {
|
|
2060
2129
|
parsed = JSON.parse(text);
|
|
2061
2130
|
} catch {
|
|
2062
|
-
|
|
2131
|
+
const repaired = repairUnescapedQuotes(text);
|
|
2132
|
+
if (repaired === void 0) throw new MalformedReplyError(text);
|
|
2133
|
+
parsed = JSON.parse(repaired);
|
|
2063
2134
|
}
|
|
2064
2135
|
if (!Array.isArray(parsed.items)) throw new MalformedReplyError(text);
|
|
2065
2136
|
return parsed.items;
|
|
@@ -4032,7 +4103,7 @@ var init_accept = __esm({
|
|
|
4032
4103
|
});
|
|
4033
4104
|
|
|
4034
4105
|
// src/server/import/detect.ts
|
|
4035
|
-
import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
4106
|
+
import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
4036
4107
|
import { join as join4 } from "path";
|
|
4037
4108
|
function safeIsDir(p) {
|
|
4038
4109
|
try {
|
|
@@ -4119,6 +4190,110 @@ function detectApple(root) {
|
|
|
4119
4190
|
}
|
|
4120
4191
|
return best;
|
|
4121
4192
|
}
|
|
4193
|
+
function detectAngularXliff(root) {
|
|
4194
|
+
for (const rel of ANGULAR_DIR_CANDIDATES) {
|
|
4195
|
+
const localeRoot = rel === "." ? root : join4(root, rel);
|
|
4196
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
4197
|
+
const files = readdirSync4(localeRoot).filter((f) => /^messages(\..+)?\.xlf$/.test(f)).sort();
|
|
4198
|
+
if (files.length === 0) continue;
|
|
4199
|
+
const locales = files.map((f) => f.match(/^messages\.(.+)\.xlf$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l));
|
|
4200
|
+
const attrFile = files.includes("messages.xlf") ? "messages.xlf" : files[0];
|
|
4201
|
+
let sourceLocale;
|
|
4202
|
+
try {
|
|
4203
|
+
sourceLocale = readFileSync11(join4(localeRoot, attrFile), "utf8").match(/source-language="([^"]+)"/)?.[1];
|
|
4204
|
+
} catch {
|
|
4205
|
+
}
|
|
4206
|
+
if (!sourceLocale && locales.length === 0) continue;
|
|
4207
|
+
sourceLocale ??= pickSource(locales, () => 0);
|
|
4208
|
+
if (!locales.includes(sourceLocale)) locales.unshift(sourceLocale);
|
|
4209
|
+
return { format: "angular-xliff", localeRoot, locales, sourceLocale };
|
|
4210
|
+
}
|
|
4211
|
+
return null;
|
|
4212
|
+
}
|
|
4213
|
+
function detectRails(root) {
|
|
4214
|
+
const localeRoot = join4(root, "config", "locales");
|
|
4215
|
+
if (!safeIsDir(localeRoot)) return null;
|
|
4216
|
+
const locales = [];
|
|
4217
|
+
for (const file of readdirSync4(localeRoot).sort()) {
|
|
4218
|
+
if (!/\.ya?ml$/.test(file)) continue;
|
|
4219
|
+
let text;
|
|
4220
|
+
try {
|
|
4221
|
+
text = readFileSync11(join4(localeRoot, file), "utf8");
|
|
4222
|
+
} catch {
|
|
4223
|
+
continue;
|
|
4224
|
+
}
|
|
4225
|
+
for (const m of text.matchAll(/^(["']?)([A-Za-z][\w-]*)\1:\s*(?:#.*)?$/gm)) {
|
|
4226
|
+
const token = m[2];
|
|
4227
|
+
if (LOCALE_RE.test(token) && !locales.includes(token)) locales.push(token);
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
if (locales.length === 0) return null;
|
|
4231
|
+
return { format: "rails-yaml", localeRoot, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
4232
|
+
}
|
|
4233
|
+
function detectI18next(root) {
|
|
4234
|
+
for (const rel of I18NEXT_DIR_CANDIDATES) {
|
|
4235
|
+
const localeRoot = join4(root, rel);
|
|
4236
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
4237
|
+
const locales = listDirs(localeRoot).filter(
|
|
4238
|
+
(d) => LOCALE_RE.test(d) && readdirSync4(join4(localeRoot, d)).some((f) => f.endsWith(".json"))
|
|
4239
|
+
);
|
|
4240
|
+
if (locales.length === 0) continue;
|
|
4241
|
+
const sourceLocale = pickSource(locales, (loc) => {
|
|
4242
|
+
try {
|
|
4243
|
+
return readdirSync4(join4(localeRoot, loc)).filter((f) => f.endsWith(".json")).reduce((sum, f) => sum + statSync3(join4(localeRoot, loc, f)).size, 0);
|
|
4244
|
+
} catch {
|
|
4245
|
+
return 0;
|
|
4246
|
+
}
|
|
4247
|
+
});
|
|
4248
|
+
return { format: "i18next-json", localeRoot, locales, sourceLocale };
|
|
4249
|
+
}
|
|
4250
|
+
return null;
|
|
4251
|
+
}
|
|
4252
|
+
function gettextLocales(dir) {
|
|
4253
|
+
const locales = [];
|
|
4254
|
+
for (const entry of readdirSync4(dir).sort()) {
|
|
4255
|
+
const flat = entry.match(/^(.+)\.po$/)?.[1];
|
|
4256
|
+
if (flat && LOCALE_RE.test(flat)) {
|
|
4257
|
+
if (!locales.includes(flat)) locales.push(flat);
|
|
4258
|
+
continue;
|
|
4259
|
+
}
|
|
4260
|
+
if (!LOCALE_RE.test(entry) || !safeIsDir(join4(dir, entry))) continue;
|
|
4261
|
+
const sub = join4(dir, entry);
|
|
4262
|
+
const hasPo = (d) => {
|
|
4263
|
+
try {
|
|
4264
|
+
return readdirSync4(d).some((f) => f.endsWith(".po"));
|
|
4265
|
+
} catch {
|
|
4266
|
+
return false;
|
|
4267
|
+
}
|
|
4268
|
+
};
|
|
4269
|
+
if (hasPo(join4(sub, "LC_MESSAGES")) || hasPo(sub)) {
|
|
4270
|
+
if (!locales.includes(entry)) locales.push(entry);
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
return locales;
|
|
4274
|
+
}
|
|
4275
|
+
function detectGettext(root) {
|
|
4276
|
+
for (const rel of GETTEXT_DIR_CANDIDATES) {
|
|
4277
|
+
const localeRoot = join4(root, rel);
|
|
4278
|
+
if (!safeIsDir(localeRoot)) continue;
|
|
4279
|
+
const locales = gettextLocales(localeRoot);
|
|
4280
|
+
if (locales.length === 0) continue;
|
|
4281
|
+
return { format: "gettext-po", localeRoot, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
4282
|
+
}
|
|
4283
|
+
return null;
|
|
4284
|
+
}
|
|
4285
|
+
function detectAppleStringsdict(root) {
|
|
4286
|
+
const candidates = [root, ...listDirs(root).map((d) => join4(root, d))];
|
|
4287
|
+
let best = null;
|
|
4288
|
+
for (const dir of candidates) {
|
|
4289
|
+
const locales = listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync10(join4(dir, `${l}.lproj`, "Localizable.stringsdict")));
|
|
4290
|
+
if (locales.length === 0) continue;
|
|
4291
|
+
if (!best || locales.length > best.locales.length) {
|
|
4292
|
+
best = { format: "apple-stringsdict", localeRoot: dir, locales, sourceLocale: pickSource(locales, () => 0) };
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
return best;
|
|
4296
|
+
}
|
|
4122
4297
|
function detect(root, formatOverride) {
|
|
4123
4298
|
if (!existsSync10(root)) return null;
|
|
4124
4299
|
if (formatOverride) {
|
|
@@ -4132,18 +4307,36 @@ function detect(root, formatOverride) {
|
|
|
4132
4307
|
}
|
|
4133
4308
|
return null;
|
|
4134
4309
|
}
|
|
4135
|
-
var LOCALE_RE, VUE_DIR_CANDIDATES, DETECTORS, BY_FORMAT;
|
|
4310
|
+
var LOCALE_RE, VUE_DIR_CANDIDATES, ANGULAR_DIR_CANDIDATES, I18NEXT_DIR_CANDIDATES, GETTEXT_DIR_CANDIDATES, DETECTORS, BY_FORMAT;
|
|
4136
4311
|
var init_detect = __esm({
|
|
4137
4312
|
"src/server/import/detect.ts"() {
|
|
4138
4313
|
"use strict";
|
|
4139
4314
|
LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4140
4315
|
VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
|
|
4141
|
-
|
|
4316
|
+
ANGULAR_DIR_CANDIDATES = [".", "src/locale", "src/locales", "src/i18n", "locale", "locales", "i18n", "translations"];
|
|
4317
|
+
I18NEXT_DIR_CANDIDATES = ["public/locales", "static/locales", "locales", "src/locales", "src/i18n/locales"];
|
|
4318
|
+
GETTEXT_DIR_CANDIDATES = ["locale", "locales", "po", "translations"];
|
|
4319
|
+
DETECTORS = [
|
|
4320
|
+
detectLaravel,
|
|
4321
|
+
detectVue,
|
|
4322
|
+
detectArb,
|
|
4323
|
+
detectApple,
|
|
4324
|
+
detectAngularXliff,
|
|
4325
|
+
detectRails,
|
|
4326
|
+
detectI18next,
|
|
4327
|
+
detectGettext,
|
|
4328
|
+
detectAppleStringsdict
|
|
4329
|
+
];
|
|
4142
4330
|
BY_FORMAT = {
|
|
4143
4331
|
"laravel-php": detectLaravel,
|
|
4144
4332
|
"vue-i18n-json": (root) => detectVue(root, true),
|
|
4145
4333
|
"flutter-arb": detectArb,
|
|
4146
|
-
"apple-strings": detectApple
|
|
4334
|
+
"apple-strings": detectApple,
|
|
4335
|
+
"angular-xliff": detectAngularXliff,
|
|
4336
|
+
"rails-yaml": detectRails,
|
|
4337
|
+
"i18next-json": detectI18next,
|
|
4338
|
+
"gettext-po": detectGettext,
|
|
4339
|
+
"apple-stringsdict": detectAppleStringsdict
|
|
4147
4340
|
};
|
|
4148
4341
|
}
|
|
4149
4342
|
});
|
|
@@ -4176,7 +4369,7 @@ var init_flatten = __esm({
|
|
|
4176
4369
|
});
|
|
4177
4370
|
|
|
4178
4371
|
// src/server/import/parsers/vue-i18n-json.ts
|
|
4179
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
4372
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
4180
4373
|
import { join as join5 } from "path";
|
|
4181
4374
|
var LOCALE_RE2, vueI18nJson2;
|
|
4182
4375
|
var init_vue_i18n_json2 = __esm({
|
|
@@ -4197,7 +4390,7 @@ var init_vue_i18n_json2 = __esm({
|
|
|
4197
4390
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4198
4391
|
let data;
|
|
4199
4392
|
try {
|
|
4200
|
-
data = JSON.parse(
|
|
4393
|
+
data = JSON.parse(readFileSync12(join5(localeRoot, file), "utf8"));
|
|
4201
4394
|
} catch (e) {
|
|
4202
4395
|
warnings.push(`vue-i18n-json: failed to parse ${file}: ${e.message}`);
|
|
4203
4396
|
continue;
|
|
@@ -4302,7 +4495,7 @@ var init_laravel_php2 = __esm({
|
|
|
4302
4495
|
});
|
|
4303
4496
|
|
|
4304
4497
|
// src/server/import/parsers/flutter-arb.ts
|
|
4305
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
4498
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync13 } from "fs";
|
|
4306
4499
|
import { join as join7 } from "path";
|
|
4307
4500
|
function localeFromArbName(file) {
|
|
4308
4501
|
const m = file.match(/^(.+)\.arb$/);
|
|
@@ -4343,7 +4536,7 @@ var init_flutter_arb2 = __esm({
|
|
|
4343
4536
|
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4344
4537
|
let data;
|
|
4345
4538
|
try {
|
|
4346
|
-
data = JSON.parse(
|
|
4539
|
+
data = JSON.parse(readFileSync13(join7(localeRoot, file), "utf8"));
|
|
4347
4540
|
} catch (e) {
|
|
4348
4541
|
warnings.push(`flutter-arb: failed to parse ${file}: ${e.message}`);
|
|
4349
4542
|
continue;
|
|
@@ -4370,7 +4563,7 @@ var init_flutter_arb2 = __esm({
|
|
|
4370
4563
|
});
|
|
4371
4564
|
|
|
4372
4565
|
// src/server/import/parsers/apple-strings.ts
|
|
4373
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
4566
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
|
|
4374
4567
|
import { join as join8 } from "path";
|
|
4375
4568
|
function localeFromLproj(dir) {
|
|
4376
4569
|
const m = dir.match(/^(.+)\.lproj$/);
|
|
@@ -4483,7 +4676,7 @@ var init_apple_strings2 = __esm({
|
|
|
4483
4676
|
let text;
|
|
4484
4677
|
try {
|
|
4485
4678
|
if (!statSync5(file).isFile()) continue;
|
|
4486
|
-
text =
|
|
4679
|
+
text = readFileSync14(file, "utf8");
|
|
4487
4680
|
} catch {
|
|
4488
4681
|
continue;
|
|
4489
4682
|
}
|
|
@@ -4502,6 +4695,773 @@ var init_apple_strings2 = __esm({
|
|
|
4502
4695
|
}
|
|
4503
4696
|
});
|
|
4504
4697
|
|
|
4698
|
+
// src/server/import/parsers/angular-xliff.ts
|
|
4699
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync15 } from "fs";
|
|
4700
|
+
import { join as join9 } from "path";
|
|
4701
|
+
function decodeEntities(s) {
|
|
4702
|
+
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, "&");
|
|
4703
|
+
}
|
|
4704
|
+
function parseAttrs(s) {
|
|
4705
|
+
const out = {};
|
|
4706
|
+
for (const m of s.matchAll(/([\w-]+)="([^"]*)"/g)) out[m[1]] = decodeEntities(m[2]);
|
|
4707
|
+
return out;
|
|
4708
|
+
}
|
|
4709
|
+
function decodeInline(raw, addMeta) {
|
|
4710
|
+
let out = "";
|
|
4711
|
+
let last = 0;
|
|
4712
|
+
for (const m of raw.matchAll(/<x\b([^>]*?)\/>/g)) {
|
|
4713
|
+
out += decodeEntities(raw.slice(last, m.index));
|
|
4714
|
+
const attrs = parseAttrs(m[1]);
|
|
4715
|
+
const id = attrs["id"] ?? "X";
|
|
4716
|
+
const equiv = attrs["equiv-text"];
|
|
4717
|
+
const simple = equiv?.match(/^\{\{\s*(\w+)\s*\}\}$/);
|
|
4718
|
+
if (simple) {
|
|
4719
|
+
out += `{${simple[1]}}`;
|
|
4720
|
+
} else {
|
|
4721
|
+
out += `{${id}}`;
|
|
4722
|
+
const meta = {};
|
|
4723
|
+
if (attrs["ctype"]) meta.type = attrs["ctype"];
|
|
4724
|
+
if (equiv !== void 0) meta.example = equiv;
|
|
4725
|
+
addMeta(id, meta);
|
|
4726
|
+
}
|
|
4727
|
+
last = m.index + m[0].length;
|
|
4728
|
+
}
|
|
4729
|
+
return out + decodeEntities(raw.slice(last));
|
|
4730
|
+
}
|
|
4731
|
+
var LOCALE_RE5, FILE_RE, angularXliff2;
|
|
4732
|
+
var init_angular_xliff2 = __esm({
|
|
4733
|
+
"src/server/import/parsers/angular-xliff.ts"() {
|
|
4734
|
+
"use strict";
|
|
4735
|
+
LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4736
|
+
FILE_RE = /^messages(?:\.(.+))?\.xlf$/;
|
|
4737
|
+
angularXliff2 = {
|
|
4738
|
+
name: "angular-xliff",
|
|
4739
|
+
parse(localeRoot, opts) {
|
|
4740
|
+
const warnings = [];
|
|
4741
|
+
const keys = {};
|
|
4742
|
+
const locales = [];
|
|
4743
|
+
const seen = (loc) => {
|
|
4744
|
+
if (!locales.includes(loc)) locales.push(loc);
|
|
4745
|
+
};
|
|
4746
|
+
const files = readdirSync9(localeRoot).filter((f) => FILE_RE.test(f)).sort((a, b) => (a === "messages.xlf" ? -1 : 0) - (b === "messages.xlf" ? -1 : 0) || a.localeCompare(b));
|
|
4747
|
+
for (const file of files) {
|
|
4748
|
+
const fnameLocale = file.match(FILE_RE)[1];
|
|
4749
|
+
if (fnameLocale !== void 0 && !LOCALE_RE5.test(fnameLocale)) continue;
|
|
4750
|
+
let xml;
|
|
4751
|
+
try {
|
|
4752
|
+
xml = readFileSync15(join9(localeRoot, file), "utf8");
|
|
4753
|
+
} catch (e) {
|
|
4754
|
+
warnings.push(`angular-xliff: failed to read ${file}: ${e.message}`);
|
|
4755
|
+
continue;
|
|
4756
|
+
}
|
|
4757
|
+
const sourceLocale = xml.match(/source-language="([^"]+)"/)?.[1];
|
|
4758
|
+
if (!sourceLocale) {
|
|
4759
|
+
warnings.push(`angular-xliff: ${file} has no source-language attribute; skipped`);
|
|
4760
|
+
continue;
|
|
4761
|
+
}
|
|
4762
|
+
const targetLocale = xml.match(/target-language="([^"]+)"/)?.[1] ?? fnameLocale;
|
|
4763
|
+
if (opts?.locales && !opts.locales.includes(targetLocale ?? sourceLocale)) continue;
|
|
4764
|
+
for (const unit of xml.matchAll(/<trans-unit\b([^>]*)>([\s\S]*?)<\/trans-unit>/g)) {
|
|
4765
|
+
const id = parseAttrs(unit[1])["id"];
|
|
4766
|
+
if (!id) {
|
|
4767
|
+
warnings.push(`angular-xliff: ${file} has a trans-unit without an id; skipped`);
|
|
4768
|
+
continue;
|
|
4769
|
+
}
|
|
4770
|
+
const body = unit[2];
|
|
4771
|
+
const src = body.match(/<source\b[^>]*>([\s\S]*?)<\/source>/);
|
|
4772
|
+
let tgt = body.match(/<target\b([^>]*)>([\s\S]*?)<\/target>/);
|
|
4773
|
+
if (tgt && /\bstate="new"/.test(tgt[1])) tgt = null;
|
|
4774
|
+
const entry = keys[id] ??= { values: {} };
|
|
4775
|
+
const addMeta = (name, meta) => {
|
|
4776
|
+
(entry.placeholders ??= {})[name] ??= meta;
|
|
4777
|
+
};
|
|
4778
|
+
if (src && entry.values[sourceLocale] === void 0) {
|
|
4779
|
+
entry.values[sourceLocale] = decodeInline(src[1], addMeta);
|
|
4780
|
+
seen(sourceLocale);
|
|
4781
|
+
}
|
|
4782
|
+
if (tgt && tgt[2] !== "" && targetLocale !== void 0) {
|
|
4783
|
+
entry.values[targetLocale] = decodeInline(tgt[2], addMeta);
|
|
4784
|
+
seen(targetLocale);
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
return { locales, keys, warnings };
|
|
4789
|
+
}
|
|
4790
|
+
};
|
|
4791
|
+
}
|
|
4792
|
+
});
|
|
4793
|
+
|
|
4794
|
+
// src/server/import/parsers/gettext-po.ts
|
|
4795
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync16 } from "fs";
|
|
4796
|
+
import { join as join10 } from "path";
|
|
4797
|
+
function unescapePo(s) {
|
|
4798
|
+
return s.replace(
|
|
4799
|
+
/\\([\\"ntr])/g,
|
|
4800
|
+
(_, c) => c === "n" ? "\n" : c === "t" ? " " : c === "r" ? "\r" : c
|
|
4801
|
+
);
|
|
4802
|
+
}
|
|
4803
|
+
function parseEntries(text) {
|
|
4804
|
+
const entries = [];
|
|
4805
|
+
let cur = null;
|
|
4806
|
+
let append = null;
|
|
4807
|
+
const flush = () => {
|
|
4808
|
+
if (cur && cur.msgid !== void 0) entries.push(cur);
|
|
4809
|
+
cur = null;
|
|
4810
|
+
append = null;
|
|
4811
|
+
};
|
|
4812
|
+
for (const line of text.split("\n")) {
|
|
4813
|
+
if (line.trim() === "") {
|
|
4814
|
+
flush();
|
|
4815
|
+
continue;
|
|
4816
|
+
}
|
|
4817
|
+
if (line.startsWith("#")) continue;
|
|
4818
|
+
const m = line.match(DIRECTIVE_RE);
|
|
4819
|
+
if (m) {
|
|
4820
|
+
const kw = m[1];
|
|
4821
|
+
const idx = m[2];
|
|
4822
|
+
const body = unescapePo(m[3]);
|
|
4823
|
+
if (cur && (kw === "msgctxt" || kw === "msgid" && cur.msgid !== void 0)) flush();
|
|
4824
|
+
cur ??= { plurals: /* @__PURE__ */ new Map() };
|
|
4825
|
+
const entry = cur;
|
|
4826
|
+
if (kw === "msgctxt") {
|
|
4827
|
+
entry.msgctxt = body;
|
|
4828
|
+
append = (c) => {
|
|
4829
|
+
entry.msgctxt = (entry.msgctxt ?? "") + c;
|
|
4830
|
+
};
|
|
4831
|
+
} else if (kw === "msgid") {
|
|
4832
|
+
entry.msgid = body;
|
|
4833
|
+
append = (c) => {
|
|
4834
|
+
entry.msgid = (entry.msgid ?? "") + c;
|
|
4835
|
+
};
|
|
4836
|
+
} else if (kw === "msgid_plural") {
|
|
4837
|
+
entry.msgidPlural = body;
|
|
4838
|
+
append = (c) => {
|
|
4839
|
+
entry.msgidPlural = (entry.msgidPlural ?? "") + c;
|
|
4840
|
+
};
|
|
4841
|
+
} else if (idx !== void 0) {
|
|
4842
|
+
const i = Number(idx);
|
|
4843
|
+
entry.plurals.set(i, body);
|
|
4844
|
+
append = (c) => {
|
|
4845
|
+
entry.plurals.set(i, (entry.plurals.get(i) ?? "") + c);
|
|
4846
|
+
};
|
|
4847
|
+
} else {
|
|
4848
|
+
entry.msgstr = body;
|
|
4849
|
+
append = (c) => {
|
|
4850
|
+
entry.msgstr = (entry.msgstr ?? "") + c;
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
continue;
|
|
4854
|
+
}
|
|
4855
|
+
const cont = line.match(CONT_RE);
|
|
4856
|
+
if (cont && append) append(unescapePo(cont[1]));
|
|
4857
|
+
}
|
|
4858
|
+
flush();
|
|
4859
|
+
return entries;
|
|
4860
|
+
}
|
|
4861
|
+
function discoverPoFiles(root) {
|
|
4862
|
+
const found = [];
|
|
4863
|
+
const entries = readdirSync10(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
4864
|
+
for (const e of entries) {
|
|
4865
|
+
if (e.isFile() && e.name.endsWith(".po")) {
|
|
4866
|
+
const base = e.name.slice(0, -3);
|
|
4867
|
+
found.push({ path: join10(root, e.name), rel: e.name, locale: LOCALE_RE6.test(base) ? base : null });
|
|
4868
|
+
} else if (e.isDirectory() && LOCALE_RE6.test(e.name)) {
|
|
4869
|
+
for (const sub of [join10(e.name, "LC_MESSAGES"), e.name]) {
|
|
4870
|
+
let names;
|
|
4871
|
+
try {
|
|
4872
|
+
names = readdirSync10(join10(root, sub)).sort();
|
|
4873
|
+
} catch {
|
|
4874
|
+
continue;
|
|
4875
|
+
}
|
|
4876
|
+
for (const f of names) {
|
|
4877
|
+
if (f.endsWith(".po")) found.push({ path: join10(root, sub, f), rel: join10(sub, f), locale: e.name });
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
return found;
|
|
4883
|
+
}
|
|
4884
|
+
var LOCALE_RE6, DIRECTIVE_RE, CONT_RE, gettextPo2;
|
|
4885
|
+
var init_gettext_po2 = __esm({
|
|
4886
|
+
"src/server/import/parsers/gettext-po.ts"() {
|
|
4887
|
+
"use strict";
|
|
4888
|
+
init_plurals();
|
|
4889
|
+
LOCALE_RE6 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4890
|
+
DIRECTIVE_RE = /^(msgctxt|msgid_plural|msgid|msgstr)(?:\[(\d+)\])?[ \t]+"(.*)"\s*$/;
|
|
4891
|
+
CONT_RE = /^[ \t]*"(.*)"\s*$/;
|
|
4892
|
+
gettextPo2 = {
|
|
4893
|
+
name: "gettext-po",
|
|
4894
|
+
parse(localeRoot, opts) {
|
|
4895
|
+
const warnings = [];
|
|
4896
|
+
const keys = {};
|
|
4897
|
+
const locales = [];
|
|
4898
|
+
for (const file of discoverPoFiles(localeRoot)) {
|
|
4899
|
+
let entries;
|
|
4900
|
+
try {
|
|
4901
|
+
entries = parseEntries(readFileSync16(file.path, "utf8"));
|
|
4902
|
+
} catch (e) {
|
|
4903
|
+
warnings.push(`gettext-po: failed to parse ${file.rel}: ${e.message}`);
|
|
4904
|
+
continue;
|
|
4905
|
+
}
|
|
4906
|
+
const header = entries.find((e) => e.msgid === "" && e.msgctxt === void 0);
|
|
4907
|
+
const headerLang = header?.msgstr?.match(/^Language:[ \t]*([A-Za-z0-9_-]+)/m)?.[1];
|
|
4908
|
+
const locale = file.locale ?? headerLang;
|
|
4909
|
+
if (!locale) {
|
|
4910
|
+
warnings.push(`gettext-po: cannot determine locale for ${file.rel}; skipped`);
|
|
4911
|
+
continue;
|
|
4912
|
+
}
|
|
4913
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4914
|
+
if (!locales.includes(locale)) locales.push(locale);
|
|
4915
|
+
const cats = categoriesFor(locale);
|
|
4916
|
+
for (const entry of entries) {
|
|
4917
|
+
if (entry === header) continue;
|
|
4918
|
+
const key = entry.msgctxt ?? entry.msgid;
|
|
4919
|
+
if (!key) continue;
|
|
4920
|
+
if (entry.msgidPlural !== void 0) {
|
|
4921
|
+
const forms = {};
|
|
4922
|
+
for (const [i, body] of [...entry.plurals].sort((a, b) => a[0] - b[0])) {
|
|
4923
|
+
if (body === "") continue;
|
|
4924
|
+
const cat = cats[i];
|
|
4925
|
+
if (!cat) {
|
|
4926
|
+
warnings.push(
|
|
4927
|
+
`gettext-po: ${file.rel} "${key}": msgstr[${i}] exceeds the ${cats.length} plural forms of "${locale}"; ignored`
|
|
4928
|
+
);
|
|
4929
|
+
continue;
|
|
4930
|
+
}
|
|
4931
|
+
forms[cat] = body.split("%d").join("{count}");
|
|
4932
|
+
}
|
|
4933
|
+
if (!forms.other) continue;
|
|
4934
|
+
(keys[key] ??= { values: {} }).values[locale] = formsToIcu("count", forms);
|
|
4935
|
+
} else {
|
|
4936
|
+
if (!entry.msgstr) continue;
|
|
4937
|
+
(keys[key] ??= { values: {} }).values[locale] = entry.msgstr;
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
return { locales, keys, warnings };
|
|
4942
|
+
}
|
|
4943
|
+
};
|
|
4944
|
+
}
|
|
4945
|
+
});
|
|
4946
|
+
|
|
4947
|
+
// src/server/import/parsers/i18next-json.ts
|
|
4948
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync17, statSync as statSync6 } from "fs";
|
|
4949
|
+
import { join as join11 } from "path";
|
|
4950
|
+
function safeIsDir2(p) {
|
|
4951
|
+
try {
|
|
4952
|
+
return statSync6(p).isDirectory();
|
|
4953
|
+
} catch {
|
|
4954
|
+
return false;
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
function fromI18next(value) {
|
|
4958
|
+
if (isIcuPluralOrSelect(value)) return value;
|
|
4959
|
+
return value.replace(/\{\{(\w+)\}\}/g, "{$1}");
|
|
4960
|
+
}
|
|
4961
|
+
function ingestFile(path, label, prefix, locale, keys, warnings) {
|
|
4962
|
+
let data;
|
|
4963
|
+
try {
|
|
4964
|
+
data = JSON.parse(readFileSync17(path, "utf8"));
|
|
4965
|
+
} catch (e) {
|
|
4966
|
+
warnings.push(`i18next-json: failed to parse ${label}: ${e.message}`);
|
|
4967
|
+
return false;
|
|
4968
|
+
}
|
|
4969
|
+
const fileWarnings = [];
|
|
4970
|
+
const flat = flattenObject(data, "", fileWarnings);
|
|
4971
|
+
for (const w of fileWarnings) warnings.push(`i18next-json: ${label}: ${w}`);
|
|
4972
|
+
const families = /* @__PURE__ */ new Set();
|
|
4973
|
+
for (const [k, v] of Object.entries(flat)) {
|
|
4974
|
+
const m = PLURAL_SUFFIX_RE.exec(k);
|
|
4975
|
+
if (m && m[2] === "other" && v !== "") families.add(m[1]);
|
|
4976
|
+
}
|
|
4977
|
+
const pluralForms = {};
|
|
4978
|
+
for (const [k, raw] of Object.entries(flat)) {
|
|
4979
|
+
if (raw === "") continue;
|
|
4980
|
+
const value = fromI18next(raw);
|
|
4981
|
+
const m = PLURAL_SUFFIX_RE.exec(k);
|
|
4982
|
+
if (m && families.has(m[1])) {
|
|
4983
|
+
(pluralForms[m[1]] ??= {})[m[2]] = value;
|
|
4984
|
+
continue;
|
|
4985
|
+
}
|
|
4986
|
+
if (families.has(k)) {
|
|
4987
|
+
warnings.push(
|
|
4988
|
+
`i18next-json: ${label}: key "${k}" collides with its own plural suffix family; the plural wins`
|
|
4989
|
+
);
|
|
4990
|
+
continue;
|
|
4991
|
+
}
|
|
4992
|
+
(keys[prefix + k] ??= { values: {} }).values[locale] = value;
|
|
4993
|
+
}
|
|
4994
|
+
for (const [base, forms] of Object.entries(pluralForms)) {
|
|
4995
|
+
(keys[prefix + base] ??= { values: {} }).values[locale] = formsToIcu(PLURAL_ARG, forms);
|
|
4996
|
+
}
|
|
4997
|
+
return true;
|
|
4998
|
+
}
|
|
4999
|
+
var LOCALE_RE7, PLURAL_SUFFIX_RE, PLURAL_ARG, DEFAULT_NAMESPACE, i18nextJson2;
|
|
5000
|
+
var init_i18next_json2 = __esm({
|
|
5001
|
+
"src/server/import/parsers/i18next-json.ts"() {
|
|
5002
|
+
"use strict";
|
|
5003
|
+
init_flatten();
|
|
5004
|
+
init_plurals();
|
|
5005
|
+
init_placeholders();
|
|
5006
|
+
LOCALE_RE7 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5007
|
+
PLURAL_SUFFIX_RE = /^(.+)_(zero|one|two|few|many|other)$/;
|
|
5008
|
+
PLURAL_ARG = "count";
|
|
5009
|
+
DEFAULT_NAMESPACE = "translation";
|
|
5010
|
+
i18nextJson2 = {
|
|
5011
|
+
name: "i18next-json",
|
|
5012
|
+
parse(localeRoot, opts) {
|
|
5013
|
+
const warnings = [];
|
|
5014
|
+
const keys = {};
|
|
5015
|
+
const locales = [];
|
|
5016
|
+
for (const entry of readdirSync11(localeRoot).sort()) {
|
|
5017
|
+
const full = join11(localeRoot, entry);
|
|
5018
|
+
if (safeIsDir2(full)) {
|
|
5019
|
+
if (!LOCALE_RE7.test(entry)) continue;
|
|
5020
|
+
if (opts?.locales && !opts.locales.includes(entry)) continue;
|
|
5021
|
+
let any = false;
|
|
5022
|
+
for (const file of readdirSync11(full).sort()) {
|
|
5023
|
+
if (!file.endsWith(".json")) continue;
|
|
5024
|
+
const ns = file.slice(0, -".json".length);
|
|
5025
|
+
const prefix = ns === DEFAULT_NAMESPACE ? "" : `${ns}.`;
|
|
5026
|
+
if (ingestFile(join11(full, file), `${entry}/${file}`, prefix, entry, keys, warnings)) any = true;
|
|
5027
|
+
}
|
|
5028
|
+
if (any && !locales.includes(entry)) locales.push(entry);
|
|
5029
|
+
} else if (entry.endsWith(".json")) {
|
|
5030
|
+
const locale = entry.slice(0, -".json".length);
|
|
5031
|
+
if (!LOCALE_RE7.test(locale)) continue;
|
|
5032
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5033
|
+
if (ingestFile(full, entry, "", locale, keys, warnings) && !locales.includes(locale)) {
|
|
5034
|
+
locales.push(locale);
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
return { locales, keys, warnings };
|
|
5039
|
+
}
|
|
5040
|
+
};
|
|
5041
|
+
}
|
|
5042
|
+
});
|
|
5043
|
+
|
|
5044
|
+
// src/server/import/parsers/rails-yaml.ts
|
|
5045
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync18 } from "fs";
|
|
5046
|
+
import { join as join12 } from "path";
|
|
5047
|
+
function fromRuby(value) {
|
|
5048
|
+
return value.replace(/%\{(\w+)\}/g, "{$1}");
|
|
5049
|
+
}
|
|
5050
|
+
function makeNode() {
|
|
5051
|
+
return /* @__PURE__ */ Object.create(null);
|
|
5052
|
+
}
|
|
5053
|
+
function decodeDouble(body) {
|
|
5054
|
+
let out = "";
|
|
5055
|
+
for (let i = 0; i < body.length; i++) {
|
|
5056
|
+
const c = body[i];
|
|
5057
|
+
if (c !== "\\") {
|
|
5058
|
+
out += c;
|
|
5059
|
+
continue;
|
|
5060
|
+
}
|
|
5061
|
+
const n = body[++i];
|
|
5062
|
+
if (n === void 0) break;
|
|
5063
|
+
out += n === "n" ? "\n" : n === "r" ? "\r" : n === "t" ? " " : n;
|
|
5064
|
+
}
|
|
5065
|
+
return out;
|
|
5066
|
+
}
|
|
5067
|
+
function scanQuoted(s, start) {
|
|
5068
|
+
const q = s[start];
|
|
5069
|
+
if (q === '"') {
|
|
5070
|
+
for (let i = start + 1; i < s.length; i++) {
|
|
5071
|
+
if (s[i] === "\\") i++;
|
|
5072
|
+
else if (s[i] === '"') return { text: decodeDouble(s.slice(start + 1, i)), end: i + 1 };
|
|
5073
|
+
}
|
|
5074
|
+
return null;
|
|
5075
|
+
}
|
|
5076
|
+
let out = "";
|
|
5077
|
+
for (let i = start + 1; i < s.length; i++) {
|
|
5078
|
+
if (s[i] === "'") {
|
|
5079
|
+
if (s[i + 1] === "'") {
|
|
5080
|
+
out += "'";
|
|
5081
|
+
i++;
|
|
5082
|
+
} else {
|
|
5083
|
+
return { text: out, end: i + 1 };
|
|
5084
|
+
}
|
|
5085
|
+
} else {
|
|
5086
|
+
out += s[i];
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
return null;
|
|
5090
|
+
}
|
|
5091
|
+
function stripPlainComment(s) {
|
|
5092
|
+
const m = /(^|\s)#/.exec(s);
|
|
5093
|
+
return (m && m.index >= 0 ? s.slice(0, m.index) : s).trim();
|
|
5094
|
+
}
|
|
5095
|
+
function onlyTrailing(s) {
|
|
5096
|
+
return /^\s*(#.*)?$/.test(s);
|
|
5097
|
+
}
|
|
5098
|
+
function parseYamlSubset(text, file, warnings) {
|
|
5099
|
+
const roots = {};
|
|
5100
|
+
const lines = text.split(/\r?\n/);
|
|
5101
|
+
let stack = [];
|
|
5102
|
+
let skipDeeperThan = null;
|
|
5103
|
+
let lastLeafIndent = null;
|
|
5104
|
+
for (let n = 0; n < lines.length; n++) {
|
|
5105
|
+
const raw = lines[n];
|
|
5106
|
+
const lineNo = n + 1;
|
|
5107
|
+
if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
|
|
5108
|
+
if (raw.trim() === "---") continue;
|
|
5109
|
+
const indentMatch = /^[ \t]*/.exec(raw)[0];
|
|
5110
|
+
if (indentMatch.includes(" ")) {
|
|
5111
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: tab in indentation; line skipped`);
|
|
5112
|
+
continue;
|
|
5113
|
+
}
|
|
5114
|
+
const indent = indentMatch.length;
|
|
5115
|
+
if (skipDeeperThan !== null) {
|
|
5116
|
+
if (indent > skipDeeperThan) continue;
|
|
5117
|
+
skipDeeperThan = null;
|
|
5118
|
+
}
|
|
5119
|
+
const content = raw.slice(indent);
|
|
5120
|
+
if (content.startsWith("- ") || content === "-") {
|
|
5121
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: sequences are not supported; node skipped`);
|
|
5122
|
+
skipDeeperThan = indent;
|
|
5123
|
+
continue;
|
|
5124
|
+
}
|
|
5125
|
+
let key;
|
|
5126
|
+
let rest;
|
|
5127
|
+
if (content[0] === '"' || content[0] === "'") {
|
|
5128
|
+
const k = scanQuoted(content, 0);
|
|
5129
|
+
if (!k || content[k.end] !== ":") {
|
|
5130
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unparseable quoted key; line skipped`);
|
|
5131
|
+
skipDeeperThan = indent;
|
|
5132
|
+
continue;
|
|
5133
|
+
}
|
|
5134
|
+
key = k.text;
|
|
5135
|
+
rest = content.slice(k.end + 1);
|
|
5136
|
+
} else {
|
|
5137
|
+
const m = /^(.*?):(?=\s|$)/.exec(content);
|
|
5138
|
+
if (!m) {
|
|
5139
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: not a "key: value" mapping line; line skipped`);
|
|
5140
|
+
skipDeeperThan = indent;
|
|
5141
|
+
continue;
|
|
5142
|
+
}
|
|
5143
|
+
key = m[1].trim();
|
|
5144
|
+
rest = content.slice(m[0].length);
|
|
5145
|
+
}
|
|
5146
|
+
if (lastLeafIndent !== null && indent > lastLeafIndent) {
|
|
5147
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unexpected indentation under a scalar; line skipped`);
|
|
5148
|
+
skipDeeperThan = indent - 1;
|
|
5149
|
+
continue;
|
|
5150
|
+
}
|
|
5151
|
+
while (stack.length > 0 && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
5152
|
+
const trimmed = rest.trim();
|
|
5153
|
+
let value;
|
|
5154
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
5155
|
+
value = null;
|
|
5156
|
+
} else if (trimmed[0] === "&" || trimmed[0] === "*") {
|
|
5157
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: YAML anchors/aliases are not supported; node skipped`);
|
|
5158
|
+
skipDeeperThan = indent;
|
|
5159
|
+
continue;
|
|
5160
|
+
} else if (trimmed[0] === "|" || trimmed[0] === ">") {
|
|
5161
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: block scalars are not supported; node skipped`);
|
|
5162
|
+
skipDeeperThan = indent;
|
|
5163
|
+
continue;
|
|
5164
|
+
} else if (trimmed[0] === "[" || trimmed[0] === "{") {
|
|
5165
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: flow collections are not supported; node skipped`);
|
|
5166
|
+
skipDeeperThan = indent;
|
|
5167
|
+
continue;
|
|
5168
|
+
} else if (trimmed[0] === '"' || trimmed[0] === "'") {
|
|
5169
|
+
const v = scanQuoted(trimmed, 0);
|
|
5170
|
+
if (!v || !onlyTrailing(trimmed.slice(v.end))) {
|
|
5171
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: unterminated or trailing-garbage quoted value; line skipped`);
|
|
5172
|
+
continue;
|
|
5173
|
+
}
|
|
5174
|
+
value = v.text;
|
|
5175
|
+
} else {
|
|
5176
|
+
value = stripPlainComment(trimmed);
|
|
5177
|
+
}
|
|
5178
|
+
if (stack.length === 0) {
|
|
5179
|
+
if (value !== null) {
|
|
5180
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: top-level key "${key}" has a scalar value; skipped`);
|
|
5181
|
+
lastLeafIndent = indent;
|
|
5182
|
+
continue;
|
|
5183
|
+
}
|
|
5184
|
+
const root = roots[key] ??= makeNode();
|
|
5185
|
+
stack = [{ indent, node: root }];
|
|
5186
|
+
lastLeafIndent = null;
|
|
5187
|
+
continue;
|
|
5188
|
+
}
|
|
5189
|
+
const parent = stack[stack.length - 1].node;
|
|
5190
|
+
if (key in parent) {
|
|
5191
|
+
warnings.push(`rails-yaml: ${file}:${lineNo}: duplicate key "${key}"; later value wins`);
|
|
5192
|
+
}
|
|
5193
|
+
if (value === null) {
|
|
5194
|
+
const child = makeNode();
|
|
5195
|
+
parent[key] = child;
|
|
5196
|
+
stack.push({ indent, node: child });
|
|
5197
|
+
lastLeafIndent = null;
|
|
5198
|
+
} else {
|
|
5199
|
+
parent[key] = value;
|
|
5200
|
+
lastLeafIndent = indent;
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5203
|
+
return { roots };
|
|
5204
|
+
}
|
|
5205
|
+
function asPluralForms(node) {
|
|
5206
|
+
const entries = Object.entries(node);
|
|
5207
|
+
if (entries.length === 0) return null;
|
|
5208
|
+
const forms = {};
|
|
5209
|
+
for (const [k, v] of entries) {
|
|
5210
|
+
if (!CATEGORY_SET.has(k) || typeof v !== "string") return null;
|
|
5211
|
+
if (v !== "") forms[k] = v;
|
|
5212
|
+
}
|
|
5213
|
+
if (!("other" in forms)) return null;
|
|
5214
|
+
return forms;
|
|
5215
|
+
}
|
|
5216
|
+
function synthesizeIcu(forms, file, key, warnings) {
|
|
5217
|
+
const parts = [];
|
|
5218
|
+
for (const cat of PLURAL_CATEGORIES) {
|
|
5219
|
+
const body = forms[cat];
|
|
5220
|
+
if (body === void 0) continue;
|
|
5221
|
+
if (body.includes("#")) {
|
|
5222
|
+
warnings.push(
|
|
5223
|
+
`rails-yaml: ${file}: plural "${key}" form "${cat}" contains "#", which ICU reads as the count placeholder`
|
|
5224
|
+
);
|
|
5225
|
+
}
|
|
5226
|
+
parts.push(`${cat} {${fromRuby(body)}}`);
|
|
5227
|
+
}
|
|
5228
|
+
return `{count, plural, ${parts.join(" ")}}`;
|
|
5229
|
+
}
|
|
5230
|
+
var LOCALE_RE8, CATEGORY_SET, railsYaml2;
|
|
5231
|
+
var init_rails_yaml2 = __esm({
|
|
5232
|
+
"src/server/import/parsers/rails-yaml.ts"() {
|
|
5233
|
+
"use strict";
|
|
5234
|
+
init_schema();
|
|
5235
|
+
LOCALE_RE8 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/i;
|
|
5236
|
+
CATEGORY_SET = new Set(PLURAL_CATEGORIES);
|
|
5237
|
+
railsYaml2 = {
|
|
5238
|
+
name: "rails-yaml",
|
|
5239
|
+
parse(localeRoot, opts) {
|
|
5240
|
+
const warnings = [];
|
|
5241
|
+
const keys = {};
|
|
5242
|
+
const locales = [];
|
|
5243
|
+
const wanted = opts?.locales?.map((l) => l.toLowerCase());
|
|
5244
|
+
const addValue = (key, locale, value) => {
|
|
5245
|
+
(keys[key] ??= { values: {} }).values[locale] = value;
|
|
5246
|
+
};
|
|
5247
|
+
const flatten = (node, prefix, locale, file) => {
|
|
5248
|
+
for (const [k, v] of Object.entries(node)) {
|
|
5249
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
5250
|
+
if (typeof v === "string") {
|
|
5251
|
+
if (v !== "") addValue(key, locale, fromRuby(v));
|
|
5252
|
+
continue;
|
|
5253
|
+
}
|
|
5254
|
+
const forms = asPluralForms(v);
|
|
5255
|
+
if (forms) addValue(key, locale, synthesizeIcu(forms, file, key, warnings));
|
|
5256
|
+
else flatten(v, key, locale, file);
|
|
5257
|
+
}
|
|
5258
|
+
};
|
|
5259
|
+
for (const file of readdirSync12(localeRoot).sort()) {
|
|
5260
|
+
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
5261
|
+
let text;
|
|
5262
|
+
try {
|
|
5263
|
+
text = readFileSync18(join12(localeRoot, file), "utf8");
|
|
5264
|
+
} catch (e) {
|
|
5265
|
+
warnings.push(`rails-yaml: failed to read ${file}: ${e.message}`);
|
|
5266
|
+
continue;
|
|
5267
|
+
}
|
|
5268
|
+
const { roots } = parseYamlSubset(text, file, warnings);
|
|
5269
|
+
for (const token of Object.keys(roots).sort()) {
|
|
5270
|
+
if (!LOCALE_RE8.test(token)) {
|
|
5271
|
+
warnings.push(`rails-yaml: ${file}: top-level key "${token}" is not a locale; subtree skipped`);
|
|
5272
|
+
continue;
|
|
5273
|
+
}
|
|
5274
|
+
if (wanted && !wanted.includes(token.toLowerCase())) continue;
|
|
5275
|
+
if (!locales.includes(token)) locales.push(token);
|
|
5276
|
+
flatten(roots[token], "", token, file);
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
return { locales, keys, warnings };
|
|
5280
|
+
}
|
|
5281
|
+
};
|
|
5282
|
+
}
|
|
5283
|
+
});
|
|
5284
|
+
|
|
5285
|
+
// src/server/import/parsers/apple-stringsdict.ts
|
|
5286
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
|
|
5287
|
+
import { join as join13 } from "path";
|
|
5288
|
+
function localeFromLproj2(dir) {
|
|
5289
|
+
const m = dir.match(/^(.+)\.lproj$/);
|
|
5290
|
+
if (!m) return null;
|
|
5291
|
+
return LOCALE_RE9.test(m[1]) ? m[1] : null;
|
|
5292
|
+
}
|
|
5293
|
+
function decodeEntities2(s) {
|
|
5294
|
+
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, "&");
|
|
5295
|
+
}
|
|
5296
|
+
function parsePlistDict(xml) {
|
|
5297
|
+
let i = 0;
|
|
5298
|
+
const n = xml.length;
|
|
5299
|
+
const skipTrivia = () => {
|
|
5300
|
+
for (; ; ) {
|
|
5301
|
+
while (i < n && /\s/.test(xml[i])) i++;
|
|
5302
|
+
if (xml.startsWith("<!--", i)) {
|
|
5303
|
+
const end = xml.indexOf("-->", i + 4);
|
|
5304
|
+
if (end === -1) throw new Error("unterminated comment");
|
|
5305
|
+
i = end + 3;
|
|
5306
|
+
continue;
|
|
5307
|
+
}
|
|
5308
|
+
if (xml.startsWith("<?", i) || xml.startsWith("<!", i) && !xml.startsWith("<!--", i)) {
|
|
5309
|
+
const end = xml.indexOf(">", i);
|
|
5310
|
+
if (end === -1) throw new Error("unterminated declaration");
|
|
5311
|
+
i = end + 1;
|
|
5312
|
+
continue;
|
|
5313
|
+
}
|
|
5314
|
+
break;
|
|
5315
|
+
}
|
|
5316
|
+
};
|
|
5317
|
+
const readTag = () => {
|
|
5318
|
+
if (xml[i] !== "<") throw new Error(`expected a tag at offset ${i}`);
|
|
5319
|
+
const end = xml.indexOf(">", i);
|
|
5320
|
+
if (end === -1) throw new Error("unterminated tag");
|
|
5321
|
+
let body = xml.slice(i + 1, end).trim();
|
|
5322
|
+
i = end + 1;
|
|
5323
|
+
const closing = body.startsWith("/");
|
|
5324
|
+
if (closing) body = body.slice(1).trim();
|
|
5325
|
+
const selfClosing = body.endsWith("/");
|
|
5326
|
+
if (selfClosing) body = body.slice(0, -1).trim();
|
|
5327
|
+
const name = body.split(/\s/)[0];
|
|
5328
|
+
if (!name) throw new Error(`empty tag at offset ${end}`);
|
|
5329
|
+
return { name, closing, selfClosing };
|
|
5330
|
+
};
|
|
5331
|
+
const readElementText = (name) => {
|
|
5332
|
+
const re = new RegExp(`</${name}\\s*>`, "g");
|
|
5333
|
+
re.lastIndex = i;
|
|
5334
|
+
const m = re.exec(xml);
|
|
5335
|
+
if (!m) throw new Error(`unterminated <${name}>`);
|
|
5336
|
+
const text = xml.slice(i, m.index);
|
|
5337
|
+
i = m.index + m[0].length;
|
|
5338
|
+
return decodeEntities2(text);
|
|
5339
|
+
};
|
|
5340
|
+
const readValue = (tag2) => {
|
|
5341
|
+
if (tag2.name === "dict") return tag2.selfClosing ? {} : readDict();
|
|
5342
|
+
if (tag2.name === "true" || tag2.name === "false") {
|
|
5343
|
+
if (!tag2.selfClosing) readElementText(tag2.name);
|
|
5344
|
+
return tag2.name;
|
|
5345
|
+
}
|
|
5346
|
+
if (["string", "integer", "real", "date", "data"].includes(tag2.name)) {
|
|
5347
|
+
return tag2.selfClosing ? "" : readElementText(tag2.name);
|
|
5348
|
+
}
|
|
5349
|
+
throw new Error(`unsupported plist element <${tag2.name}>`);
|
|
5350
|
+
};
|
|
5351
|
+
const readDict = () => {
|
|
5352
|
+
const out = {};
|
|
5353
|
+
for (; ; ) {
|
|
5354
|
+
skipTrivia();
|
|
5355
|
+
const tag2 = readTag();
|
|
5356
|
+
if (tag2.closing) {
|
|
5357
|
+
if (tag2.name !== "dict") throw new Error(`unexpected </${tag2.name}> inside <dict>`);
|
|
5358
|
+
return out;
|
|
5359
|
+
}
|
|
5360
|
+
if (tag2.name !== "key") throw new Error(`expected <key> inside <dict>, got <${tag2.name}>`);
|
|
5361
|
+
const key = readElementText("key");
|
|
5362
|
+
skipTrivia();
|
|
5363
|
+
const vt = readTag();
|
|
5364
|
+
if (vt.closing) throw new Error(`<key>${key}</key> has no value`);
|
|
5365
|
+
out[key] = readValue(vt);
|
|
5366
|
+
}
|
|
5367
|
+
};
|
|
5368
|
+
skipTrivia();
|
|
5369
|
+
let tag = readTag();
|
|
5370
|
+
if (tag.name === "plist" && !tag.closing && !tag.selfClosing) {
|
|
5371
|
+
skipTrivia();
|
|
5372
|
+
tag = readTag();
|
|
5373
|
+
}
|
|
5374
|
+
if (tag.name !== "dict" || tag.closing) throw new Error("expected a root <dict>");
|
|
5375
|
+
return tag.selfClosing ? {} : readDict();
|
|
5376
|
+
}
|
|
5377
|
+
function entryToIcu(key, entry, file, warnings) {
|
|
5378
|
+
const warn = (msg) => {
|
|
5379
|
+
warnings.push(`apple-stringsdict: ${file}: key "${key}": ${msg}`);
|
|
5380
|
+
return null;
|
|
5381
|
+
};
|
|
5382
|
+
if (typeof entry !== "object") return warn("value is not a dict; skipped");
|
|
5383
|
+
const fmt = entry["NSStringLocalizedFormatKey"];
|
|
5384
|
+
if (typeof fmt !== "string") return warn("missing NSStringLocalizedFormatKey; skipped");
|
|
5385
|
+
const vars = [...fmt.matchAll(VAR_RE)];
|
|
5386
|
+
if (vars.length !== 1) {
|
|
5387
|
+
return warn(`format key has ${vars.length} %#@\u2026@ variables; only exactly one is supported; skipped`);
|
|
5388
|
+
}
|
|
5389
|
+
const arg = vars[0][1];
|
|
5390
|
+
if (!/^\w+$/.test(arg)) return warn(`variable name "${arg}" is not a valid ICU argument; skipped`);
|
|
5391
|
+
const prefix = fmt.slice(0, vars[0].index);
|
|
5392
|
+
const suffix = fmt.slice(vars[0].index + vars[0][0].length);
|
|
5393
|
+
const varDict = entry[arg];
|
|
5394
|
+
if (typeof varDict !== "object") return warn(`variable "${arg}" has no dict; skipped`);
|
|
5395
|
+
const specType = varDict["NSStringFormatSpecTypeKey"];
|
|
5396
|
+
if (specType !== void 0 && specType !== "NSStringPluralRuleType") {
|
|
5397
|
+
return warn(`variable "${arg}" is not a plural rule (${String(specType)}); skipped`);
|
|
5398
|
+
}
|
|
5399
|
+
const valueType = varDict["NSStringFormatValueTypeKey"];
|
|
5400
|
+
const token = `%${typeof valueType === "string" && valueType ? valueType : "d"}`;
|
|
5401
|
+
const forms = {};
|
|
5402
|
+
for (const cat of PLURAL_CATEGORIES) {
|
|
5403
|
+
const body = varDict[cat];
|
|
5404
|
+
if (typeof body !== "string") continue;
|
|
5405
|
+
forms[cat] = prefix + body.split(token).join(`{${arg}}`) + suffix;
|
|
5406
|
+
}
|
|
5407
|
+
if (forms.other === void 0) return warn(`variable "${arg}" has no "other" form; skipped`);
|
|
5408
|
+
return formsToIcu(arg, forms);
|
|
5409
|
+
}
|
|
5410
|
+
var LOCALE_RE9, TABLE2, VAR_RE, appleStringsdict2;
|
|
5411
|
+
var init_apple_stringsdict2 = __esm({
|
|
5412
|
+
"src/server/import/parsers/apple-stringsdict.ts"() {
|
|
5413
|
+
"use strict";
|
|
5414
|
+
init_schema();
|
|
5415
|
+
init_plurals();
|
|
5416
|
+
LOCALE_RE9 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
5417
|
+
TABLE2 = "Localizable.stringsdict";
|
|
5418
|
+
VAR_RE = /%#@([^@]*)@/g;
|
|
5419
|
+
appleStringsdict2 = {
|
|
5420
|
+
name: "apple-stringsdict",
|
|
5421
|
+
parse(localeRoot, opts) {
|
|
5422
|
+
const warnings = [];
|
|
5423
|
+
const keys = {};
|
|
5424
|
+
const locales = [];
|
|
5425
|
+
for (const dir of readdirSync13(localeRoot).sort()) {
|
|
5426
|
+
const locale = localeFromLproj2(dir);
|
|
5427
|
+
if (!locale) continue;
|
|
5428
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
5429
|
+
const file = join13(localeRoot, dir, TABLE2);
|
|
5430
|
+
let text;
|
|
5431
|
+
try {
|
|
5432
|
+
if (!statSync7(file).isFile()) continue;
|
|
5433
|
+
text = readFileSync19(file, "utf8");
|
|
5434
|
+
} catch {
|
|
5435
|
+
continue;
|
|
5436
|
+
}
|
|
5437
|
+
locales.push(locale);
|
|
5438
|
+
const others = readdirSync13(join13(localeRoot, dir)).filter(
|
|
5439
|
+
(f) => f.endsWith(".stringsdict") && f !== TABLE2
|
|
5440
|
+
);
|
|
5441
|
+
if (others.length) {
|
|
5442
|
+
warnings.push(
|
|
5443
|
+
`apple-stringsdict: ${dir} has other .stringsdict tables (${others.join(", ")}); only ${TABLE2} is imported`
|
|
5444
|
+
);
|
|
5445
|
+
}
|
|
5446
|
+
let root;
|
|
5447
|
+
try {
|
|
5448
|
+
root = parsePlistDict(text);
|
|
5449
|
+
} catch (e) {
|
|
5450
|
+
warnings.push(`apple-stringsdict: failed to parse ${file}: ${e.message}`);
|
|
5451
|
+
continue;
|
|
5452
|
+
}
|
|
5453
|
+
for (const key of Object.keys(root).sort()) {
|
|
5454
|
+
const icu = entryToIcu(key, root[key], file, warnings);
|
|
5455
|
+
if (icu === null) continue;
|
|
5456
|
+
(keys[key] ??= { values: {} }).values[locale] = icu;
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
return { locales, keys, warnings };
|
|
5460
|
+
}
|
|
5461
|
+
};
|
|
5462
|
+
}
|
|
5463
|
+
});
|
|
5464
|
+
|
|
4505
5465
|
// src/server/import/parsers/index.ts
|
|
4506
5466
|
function getParser(name) {
|
|
4507
5467
|
const p = REGISTRY[name];
|
|
@@ -4516,11 +5476,21 @@ var init_parsers = __esm({
|
|
|
4516
5476
|
init_laravel_php2();
|
|
4517
5477
|
init_flutter_arb2();
|
|
4518
5478
|
init_apple_strings2();
|
|
5479
|
+
init_angular_xliff2();
|
|
5480
|
+
init_gettext_po2();
|
|
5481
|
+
init_i18next_json2();
|
|
5482
|
+
init_rails_yaml2();
|
|
5483
|
+
init_apple_stringsdict2();
|
|
4519
5484
|
REGISTRY = {
|
|
4520
5485
|
[vueI18nJson2.name]: vueI18nJson2,
|
|
4521
5486
|
[laravelPhp2.name]: laravelPhp2,
|
|
4522
5487
|
[flutterArb2.name]: flutterArb2,
|
|
4523
|
-
[appleStrings2.name]: appleStrings2
|
|
5488
|
+
[appleStrings2.name]: appleStrings2,
|
|
5489
|
+
[angularXliff2.name]: angularXliff2,
|
|
5490
|
+
[gettextPo2.name]: gettextPo2,
|
|
5491
|
+
[i18nextJson2.name]: i18nextJson2,
|
|
5492
|
+
[railsYaml2.name]: railsYaml2,
|
|
5493
|
+
[appleStringsdict2.name]: appleStringsdict2
|
|
4524
5494
|
};
|
|
4525
5495
|
}
|
|
4526
5496
|
});
|
|
@@ -4606,7 +5576,14 @@ var init_assemble = __esm({
|
|
|
4606
5576
|
"laravel-php": { adapter: "laravel-php", path: "lang/{locale}/{namespace}.php" },
|
|
4607
5577
|
"vue-i18n-json": { adapter: "vue-i18n-json", path: "src/locale/{locale}.json" },
|
|
4608
5578
|
"flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" },
|
|
4609
|
-
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true }
|
|
5579
|
+
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true },
|
|
5580
|
+
// skipSourceLocale: ng extract-i18n owns messages.xlf (the source file); glotfile
|
|
5581
|
+
// only writes the translation files back next to it.
|
|
5582
|
+
"angular-xliff": { adapter: "angular-xliff", path: "messages.{locale}.xlf", rootRelative: true, skipSourceLocale: true },
|
|
5583
|
+
"gettext-po": { adapter: "gettext-po", path: "{locale}.po", rootRelative: true },
|
|
5584
|
+
"i18next-json": { adapter: "i18next-json", path: "{locale}/translation.json", rootRelative: true },
|
|
5585
|
+
"rails-yaml": { adapter: "rails-yaml", path: "config/locales/{locale}.yml" },
|
|
5586
|
+
"apple-stringsdict": { adapter: "apple-stringsdict", path: "{locale}.lproj/Localizable.stringsdict", rootRelative: true }
|
|
4610
5587
|
};
|
|
4611
5588
|
}
|
|
4612
5589
|
});
|
|
@@ -4917,12 +5894,12 @@ var init_checks = __esm({
|
|
|
4917
5894
|
});
|
|
4918
5895
|
|
|
4919
5896
|
// src/server/ui-prefs.ts
|
|
4920
|
-
import { readFileSync as
|
|
5897
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
4921
5898
|
import { homedir } from "os";
|
|
4922
|
-
import { join as
|
|
5899
|
+
import { join as join14 } from "path";
|
|
4923
5900
|
function readJson2(path) {
|
|
4924
5901
|
try {
|
|
4925
|
-
const parsed = JSON.parse(
|
|
5902
|
+
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
4926
5903
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4927
5904
|
} catch {
|
|
4928
5905
|
return {};
|
|
@@ -4947,7 +5924,7 @@ var init_ui_prefs = __esm({
|
|
|
4947
5924
|
THEMES = ["system", "light", "dark"];
|
|
4948
5925
|
isThemeMode = (v) => THEMES.includes(v);
|
|
4949
5926
|
isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
4950
|
-
defaultUiPrefsPath = () =>
|
|
5927
|
+
defaultUiPrefsPath = () => join14(homedir(), ".glotfile", "ui.json");
|
|
4951
5928
|
DEFAULTS = { theme: "system" };
|
|
4952
5929
|
}
|
|
4953
5930
|
});
|
|
@@ -4955,13 +5932,13 @@ var init_ui_prefs = __esm({
|
|
|
4955
5932
|
// src/server/api.ts
|
|
4956
5933
|
import { Hono } from "hono";
|
|
4957
5934
|
import { streamSSE } from "hono/streaming";
|
|
4958
|
-
import { readFileSync as
|
|
5935
|
+
import { readFileSync as readFileSync21, existsSync as existsSync11, readdirSync as readdirSync14, statSync as statSync8, rmSync as rmSync4 } from "fs";
|
|
4959
5936
|
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
4960
5937
|
function projectName(root) {
|
|
4961
5938
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
4962
5939
|
if (existsSync11(nameFile)) {
|
|
4963
5940
|
try {
|
|
4964
|
-
const name =
|
|
5941
|
+
const name = readFileSync21(nameFile, "utf8").trim();
|
|
4965
5942
|
if (name) return name;
|
|
4966
5943
|
} catch {
|
|
4967
5944
|
}
|
|
@@ -5086,7 +6063,7 @@ function createApi(deps) {
|
|
|
5086
6063
|
if (depth > 4) return;
|
|
5087
6064
|
let entries = [];
|
|
5088
6065
|
try {
|
|
5089
|
-
entries =
|
|
6066
|
+
entries = readdirSync14(dir);
|
|
5090
6067
|
} catch {
|
|
5091
6068
|
return;
|
|
5092
6069
|
}
|
|
@@ -5100,7 +6077,7 @@ function createApi(deps) {
|
|
|
5100
6077
|
filePath = abs;
|
|
5101
6078
|
} else {
|
|
5102
6079
|
try {
|
|
5103
|
-
if (
|
|
6080
|
+
if (statSync8(abs).isDirectory()) walk(abs, depth + 1);
|
|
5104
6081
|
} catch {
|
|
5105
6082
|
}
|
|
5106
6083
|
continue;
|
|
@@ -5918,7 +6895,7 @@ __export(server_exports, {
|
|
|
5918
6895
|
import { Hono as Hono2 } from "hono";
|
|
5919
6896
|
import { serve } from "@hono/node-server";
|
|
5920
6897
|
import { fileURLToPath } from "url";
|
|
5921
|
-
import { dirname as dirname4, join as
|
|
6898
|
+
import { dirname as dirname4, join as join15, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
|
|
5922
6899
|
import { readFile, stat } from "fs/promises";
|
|
5923
6900
|
import { createServer } from "net";
|
|
5924
6901
|
import open from "open";
|
|
@@ -5961,7 +6938,7 @@ function buildApp(opts) {
|
|
|
5961
6938
|
const file = await readFileResponse(target);
|
|
5962
6939
|
if (file) return file;
|
|
5963
6940
|
}
|
|
5964
|
-
const index = await readFileResponse(
|
|
6941
|
+
const index = await readFileResponse(join15(root, "index.html"));
|
|
5965
6942
|
if (index) return index;
|
|
5966
6943
|
return c.notFound();
|
|
5967
6944
|
});
|
|
@@ -6019,7 +6996,7 @@ var init_server = __esm({
|
|
|
6019
6996
|
init_scan();
|
|
6020
6997
|
init_scanner();
|
|
6021
6998
|
here = dirname4(fileURLToPath(import.meta.url));
|
|
6022
|
-
DEFAULT_UI_DIR =
|
|
6999
|
+
DEFAULT_UI_DIR = join15(here, "..", "ui");
|
|
6023
7000
|
MIME = {
|
|
6024
7001
|
".html": "text/html; charset=utf-8",
|
|
6025
7002
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -6063,8 +7040,8 @@ init_scanner();
|
|
|
6063
7040
|
init_context();
|
|
6064
7041
|
init_run2();
|
|
6065
7042
|
init_outputs();
|
|
6066
|
-
import { resolve as resolve11, dirname as dirname5, join as
|
|
6067
|
-
import { readFileSync as
|
|
7043
|
+
import { resolve as resolve11, dirname as dirname5, join as join16 } from "path";
|
|
7044
|
+
import { readFileSync as readFileSync22, existsSync as existsSync12, mkdirSync as mkdirSync4, cpSync } from "fs";
|
|
6068
7045
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6069
7046
|
|
|
6070
7047
|
// src/server/lint/locate.ts
|
|
@@ -6139,6 +7116,9 @@ function parseArgs(argv) {
|
|
|
6139
7116
|
if (first === "help" || first === "--help" || first === "-h") {
|
|
6140
7117
|
return isCommand(argv[1]) ? { command: argv[1], statePath, help: true } : { command: "help", statePath };
|
|
6141
7118
|
}
|
|
7119
|
+
if (first === "version" || first === "--version" || first === "-v") {
|
|
7120
|
+
return { command: "version", statePath };
|
|
7121
|
+
}
|
|
6142
7122
|
if (first !== void 0 && !first.startsWith("-") && !isCommand(first)) {
|
|
6143
7123
|
return { command: "serve", statePath, unknownCommand: first };
|
|
6144
7124
|
}
|
|
@@ -6372,7 +7352,7 @@ async function runLintCmd(args) {
|
|
|
6372
7352
|
}
|
|
6373
7353
|
return;
|
|
6374
7354
|
}
|
|
6375
|
-
const rawText = existsSync12(args.statePath) ?
|
|
7355
|
+
const rawText = existsSync12(args.statePath) ? readFileSync22(args.statePath, "utf8") : "";
|
|
6376
7356
|
const report = await runLint(state, {
|
|
6377
7357
|
locales: args.locales,
|
|
6378
7358
|
ruleIds: args.ruleIds,
|
|
@@ -6396,7 +7376,7 @@ async function runCheck(args) {
|
|
|
6396
7376
|
process.exitCode = 1;
|
|
6397
7377
|
return;
|
|
6398
7378
|
}
|
|
6399
|
-
const rawText = existsSync12(args.statePath) ?
|
|
7379
|
+
const rawText = existsSync12(args.statePath) ? readFileSync22(args.statePath, "utf8") : "";
|
|
6400
7380
|
const root = dirname5(resolve11(args.statePath));
|
|
6401
7381
|
const lint = await runLint(state, {});
|
|
6402
7382
|
const findings = sortFindings([...lint.findings, ...checkOutputs(state, root)]);
|
|
@@ -6557,10 +7537,10 @@ function runSplit(args) {
|
|
|
6557
7537
|
`Split catalog into ${splitDirFor(args.statePath)}/ (config.json, keys.json, locales/ \u2014 up to ${state.config.locales.length} locale files). Removed ${args.statePath}.`
|
|
6558
7538
|
);
|
|
6559
7539
|
}
|
|
6560
|
-
var SKILL_SRC =
|
|
7540
|
+
var SKILL_SRC = join16(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
|
|
6561
7541
|
function runSkill(args) {
|
|
6562
7542
|
if (args.print) {
|
|
6563
|
-
console.log(
|
|
7543
|
+
console.log(readFileSync22(join16(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
|
|
6564
7544
|
return;
|
|
6565
7545
|
}
|
|
6566
7546
|
const dest = resolve11(process.cwd(), ".claude", "skills", "glotfile");
|
|
@@ -6691,12 +7671,16 @@ ${formatOpts([...options, ...GLOBAL_OPTS])}`);
|
|
|
6691
7671
|
formatOpts(commands),
|
|
6692
7672
|
"",
|
|
6693
7673
|
"Global options:",
|
|
6694
|
-
formatOpts(GLOBAL_OPTS),
|
|
7674
|
+
formatOpts([...GLOBAL_OPTS, ["-v, --version", "Print the glotfile version"]]),
|
|
6695
7675
|
"",
|
|
6696
7676
|
"Run `glotfile <command> --help` for a command's options."
|
|
6697
7677
|
].join("\n")
|
|
6698
7678
|
);
|
|
6699
7679
|
}
|
|
7680
|
+
function printVersion() {
|
|
7681
|
+
const pkgPath = join16(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
|
7682
|
+
console.log(JSON.parse(readFileSync22(pkgPath, "utf8")).version);
|
|
7683
|
+
}
|
|
6700
7684
|
async function main(argv) {
|
|
6701
7685
|
const args = parseArgs(argv);
|
|
6702
7686
|
if (args.unknownCommand) {
|
|
@@ -6705,6 +7689,7 @@ async function main(argv) {
|
|
|
6705
7689
|
return;
|
|
6706
7690
|
}
|
|
6707
7691
|
if (args.command === "help") return printHelp();
|
|
7692
|
+
if (args.command === "version") return printVersion();
|
|
6708
7693
|
if (args.help) return printHelp(args.command);
|
|
6709
7694
|
loadDotEnv();
|
|
6710
7695
|
if (args.command === "export") return runExport(args);
|