glotfile 0.5.1 → 0.5.2
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 +296 -45
- package/dist/server/server.js +252 -42
- package/dist/ui/assets/{index-DC89onXX.js → index-B1UwtMSs.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/package.json +3 -2
- package/skill/SKILL.md +66 -0
- package/skill/references/cli-reference.md +84 -0
- package/skill/references/conventions.md +50 -0
- package/skill/references/schema.md +99 -0
- package/skill/references/workflows.md +84 -0
package/dist/server/cli.js
CHANGED
|
@@ -1445,8 +1445,58 @@ var init_apple_stringsdict = __esm({
|
|
|
1445
1445
|
}
|
|
1446
1446
|
});
|
|
1447
1447
|
|
|
1448
|
+
// src/server/adapters/apple-strings.ts
|
|
1449
|
+
function escape(s) {
|
|
1450
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r");
|
|
1451
|
+
}
|
|
1452
|
+
var DEFAULT_LOCALE_CASE6, appleStrings;
|
|
1453
|
+
var init_apple_strings = __esm({
|
|
1454
|
+
"src/server/adapters/apple-strings.ts"() {
|
|
1455
|
+
"use strict";
|
|
1456
|
+
init_adapters();
|
|
1457
|
+
init_options();
|
|
1458
|
+
DEFAULT_LOCALE_CASE6 = "bcp47-hyphen";
|
|
1459
|
+
appleStrings = {
|
|
1460
|
+
name: "apple-strings",
|
|
1461
|
+
capabilities: {
|
|
1462
|
+
// Plurals belong in .stringsdict (apple-stringsdict), not the scalar table.
|
|
1463
|
+
plural: "none",
|
|
1464
|
+
select: "none",
|
|
1465
|
+
nesting: "flat",
|
|
1466
|
+
metadata: false,
|
|
1467
|
+
placeholderStyle: "printf",
|
|
1468
|
+
fileGrouping: "per-locale"
|
|
1469
|
+
},
|
|
1470
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE6,
|
|
1471
|
+
export(state, output) {
|
|
1472
|
+
const files = [];
|
|
1473
|
+
const warnings = [];
|
|
1474
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE6));
|
|
1475
|
+
const emptyAs = resolveEmptyAs(output, "source");
|
|
1476
|
+
const keys = Object.keys(state.keys).sort();
|
|
1477
|
+
for (const locale of state.config.locales) {
|
|
1478
|
+
const lines = [];
|
|
1479
|
+
for (const key of keys) {
|
|
1480
|
+
const entry = state.keys[key];
|
|
1481
|
+
if (entry.plural) continue;
|
|
1482
|
+
const value = resolveScalar(entry, locale, state.config.sourceLocale, emptyAs);
|
|
1483
|
+
if (value === null) continue;
|
|
1484
|
+
lines.push(`"${escape(key)}" = "${escape(value)}";`);
|
|
1485
|
+
}
|
|
1486
|
+
const contents = lines.length ? lines.join("\n") + "\n" : "";
|
|
1487
|
+
files.push({
|
|
1488
|
+
path: resolvePath(output.path, resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE6)),
|
|
1489
|
+
contents
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
return { files, warnings };
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1448
1498
|
// src/server/adapters/vue-i18n-json.ts
|
|
1449
|
-
var
|
|
1499
|
+
var DEFAULT_LOCALE_CASE7, vueI18nJson;
|
|
1450
1500
|
var init_vue_i18n_json = __esm({
|
|
1451
1501
|
"src/server/adapters/vue-i18n-json.ts"() {
|
|
1452
1502
|
"use strict";
|
|
@@ -1456,7 +1506,7 @@ var init_vue_i18n_json = __esm({
|
|
|
1456
1506
|
init_format();
|
|
1457
1507
|
init_placeholders();
|
|
1458
1508
|
init_schema();
|
|
1459
|
-
|
|
1509
|
+
DEFAULT_LOCALE_CASE7 = "lower-hyphen";
|
|
1460
1510
|
vueI18nJson = {
|
|
1461
1511
|
name: "vue-i18n-json",
|
|
1462
1512
|
capabilities: {
|
|
@@ -1467,11 +1517,11 @@ var init_vue_i18n_json = __esm({
|
|
|
1467
1517
|
placeholderStyle: "named",
|
|
1468
1518
|
fileGrouping: "per-locale"
|
|
1469
1519
|
},
|
|
1470
|
-
defaultLocaleCase:
|
|
1520
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE7,
|
|
1471
1521
|
export(state, output) {
|
|
1472
1522
|
const files = [];
|
|
1473
1523
|
const warnings = [];
|
|
1474
|
-
warnings.push(...localeCollisionWarnings(output, state.config.locales,
|
|
1524
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE7));
|
|
1475
1525
|
const { indent, finalNewline } = resolveFormat(state, output);
|
|
1476
1526
|
const fmt = { indent, sortKeys: true, finalNewline };
|
|
1477
1527
|
const emptyAs = resolveEmptyAs(output, "omit");
|
|
@@ -1511,7 +1561,7 @@ var init_vue_i18n_json = __esm({
|
|
|
1511
1561
|
}
|
|
1512
1562
|
payload = tree;
|
|
1513
1563
|
}
|
|
1514
|
-
files.push({ path: resolvePath(output.path, resolveLocaleToken(output, locale,
|
|
1564
|
+
files.push({ path: resolvePath(output.path, resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE7)), contents: serializeJson(payload, fmt) });
|
|
1515
1565
|
}
|
|
1516
1566
|
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
1517
1567
|
return { files, warnings };
|
|
@@ -1557,7 +1607,7 @@ function renderEmbeddedIcu(value) {
|
|
|
1557
1607
|
function renderScalar(value, ids) {
|
|
1558
1608
|
return isIcuPluralOrSelect(value) ? renderEmbeddedIcu(value) : renderInterpolations(value, ids);
|
|
1559
1609
|
}
|
|
1560
|
-
var
|
|
1610
|
+
var DEFAULT_LOCALE_CASE8, angularXliff;
|
|
1561
1611
|
var init_angular_xliff = __esm({
|
|
1562
1612
|
"src/server/adapters/angular-xliff.ts"() {
|
|
1563
1613
|
"use strict";
|
|
@@ -1565,7 +1615,7 @@ var init_angular_xliff = __esm({
|
|
|
1565
1615
|
init_options();
|
|
1566
1616
|
init_placeholders();
|
|
1567
1617
|
init_schema();
|
|
1568
|
-
|
|
1618
|
+
DEFAULT_LOCALE_CASE8 = "bcp47-hyphen";
|
|
1569
1619
|
angularXliff = {
|
|
1570
1620
|
name: "angular-xliff",
|
|
1571
1621
|
capabilities: {
|
|
@@ -1576,17 +1626,17 @@ var init_angular_xliff = __esm({
|
|
|
1576
1626
|
placeholderStyle: "icu",
|
|
1577
1627
|
fileGrouping: "per-locale"
|
|
1578
1628
|
},
|
|
1579
|
-
defaultLocaleCase:
|
|
1629
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE8,
|
|
1580
1630
|
export(state, output) {
|
|
1581
1631
|
const files = [];
|
|
1582
1632
|
const warnings = [];
|
|
1583
|
-
warnings.push(...localeCollisionWarnings(output, state.config.locales,
|
|
1633
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE8));
|
|
1584
1634
|
const sourceLocale = state.config.sourceLocale;
|
|
1585
|
-
const sourceToken = resolveLocaleToken(output, sourceLocale,
|
|
1635
|
+
const sourceToken = resolveLocaleToken(output, sourceLocale, DEFAULT_LOCALE_CASE8);
|
|
1586
1636
|
const emptyAs = resolveEmptyAs(output, "source");
|
|
1587
1637
|
const keys = Object.keys(state.keys).sort();
|
|
1588
1638
|
for (const locale of state.config.locales) {
|
|
1589
|
-
const token = resolveLocaleToken(output, locale,
|
|
1639
|
+
const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE8);
|
|
1590
1640
|
const units = [];
|
|
1591
1641
|
for (const key of keys) {
|
|
1592
1642
|
const entry = state.keys[key];
|
|
@@ -1650,7 +1700,7 @@ function yamlMap(node, indent, level) {
|
|
|
1650
1700
|
}
|
|
1651
1701
|
return lines;
|
|
1652
1702
|
}
|
|
1653
|
-
var RESERVED_KEYS,
|
|
1703
|
+
var RESERVED_KEYS, DEFAULT_LOCALE_CASE9, railsYaml;
|
|
1654
1704
|
var init_rails_yaml = __esm({
|
|
1655
1705
|
"src/server/adapters/rails-yaml.ts"() {
|
|
1656
1706
|
"use strict";
|
|
@@ -1660,7 +1710,7 @@ var init_rails_yaml = __esm({
|
|
|
1660
1710
|
init_placeholders();
|
|
1661
1711
|
init_schema();
|
|
1662
1712
|
RESERVED_KEYS = /* @__PURE__ */ new Set(["true", "false", "yes", "no", "on", "off", "null", "y", "n"]);
|
|
1663
|
-
|
|
1713
|
+
DEFAULT_LOCALE_CASE9 = "bcp47-hyphen";
|
|
1664
1714
|
railsYaml = {
|
|
1665
1715
|
name: "rails-yaml",
|
|
1666
1716
|
capabilities: {
|
|
@@ -1671,10 +1721,10 @@ var init_rails_yaml = __esm({
|
|
|
1671
1721
|
placeholderStyle: "named",
|
|
1672
1722
|
fileGrouping: "per-locale"
|
|
1673
1723
|
},
|
|
1674
|
-
defaultLocaleCase:
|
|
1724
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE9,
|
|
1675
1725
|
export(state, output) {
|
|
1676
1726
|
const warnings = [];
|
|
1677
|
-
warnings.push(...localeCollisionWarnings(output, state.config.locales,
|
|
1727
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE9));
|
|
1678
1728
|
const { indent, finalNewline } = resolveFormat(state, output);
|
|
1679
1729
|
const emptyAs = resolveEmptyAs(output, "omit");
|
|
1680
1730
|
const files = [];
|
|
@@ -1706,7 +1756,7 @@ var init_rails_yaml = __esm({
|
|
|
1706
1756
|
for (const c of collisions) {
|
|
1707
1757
|
warnings.push({ code: "key-collision", key: c, locale, message: "key is both a leaf and a parent; dropped from nested output" });
|
|
1708
1758
|
}
|
|
1709
|
-
const token = resolveLocaleToken(output, locale,
|
|
1759
|
+
const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE9);
|
|
1710
1760
|
const body = [`${yamlKey(token)}:`, ...yamlMap(nested, indent, 1)].join("\n");
|
|
1711
1761
|
files.push({ path: resolvePath(output.path, token), contents: finalNewline ? body + "\n" : body });
|
|
1712
1762
|
}
|
|
@@ -1748,6 +1798,7 @@ function getRegistry() {
|
|
|
1748
1798
|
[i18nextJson.name]: i18nextJson,
|
|
1749
1799
|
[gettextPo.name]: gettextPo,
|
|
1750
1800
|
[appleStringsdict.name]: appleStringsdict,
|
|
1801
|
+
[appleStrings.name]: appleStrings,
|
|
1751
1802
|
[vueI18nJson.name]: vueI18nJson,
|
|
1752
1803
|
[angularXliff.name]: angularXliff,
|
|
1753
1804
|
[railsYaml.name]: railsYaml
|
|
@@ -1768,6 +1819,7 @@ var init_adapters = __esm({
|
|
|
1768
1819
|
init_i18next_json();
|
|
1769
1820
|
init_gettext_po();
|
|
1770
1821
|
init_apple_stringsdict();
|
|
1822
|
+
init_apple_strings();
|
|
1771
1823
|
init_vue_i18n_json();
|
|
1772
1824
|
init_angular_xliff();
|
|
1773
1825
|
init_rails_yaml();
|
|
@@ -3309,7 +3361,11 @@ var init_scanner = __esm({
|
|
|
3309
3361
|
apple: [
|
|
3310
3362
|
/NSLocalizedString\s*\(\s*@?"([^"]+)"/g,
|
|
3311
3363
|
/String\s*\(\s*localized:\s*"([^"]+)"/g,
|
|
3312
|
-
/localizedString\s*\(\s*forKey:\s*"([^"]+)"/g
|
|
3364
|
+
/localizedString\s*\(\s*forKey:\s*"([^"]+)"/g,
|
|
3365
|
+
// The "key".localized / "key".localised String-extension idiom, where the
|
|
3366
|
+
// literal IS the key (common when keys are natural-language source text).
|
|
3367
|
+
/"([^"]+)"\s*\.\s*localized\b/g,
|
|
3368
|
+
/"([^"]+)"\s*\.\s*localised\b/g
|
|
3313
3369
|
]
|
|
3314
3370
|
};
|
|
3315
3371
|
PREFIX_PATTERNS = {
|
|
@@ -3327,7 +3383,7 @@ var init_scanner = __esm({
|
|
|
3327
3383
|
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$]*)\$\{/g
|
|
3328
3384
|
]
|
|
3329
3385
|
};
|
|
3330
|
-
CACHE_VERSION =
|
|
3386
|
+
CACHE_VERSION = 5;
|
|
3331
3387
|
EXT_SCANNER = {
|
|
3332
3388
|
".php": "laravel",
|
|
3333
3389
|
".vue": "js-i18n",
|
|
@@ -3967,6 +4023,32 @@ function detectArb(root) {
|
|
|
3967
4023
|
}
|
|
3968
4024
|
return null;
|
|
3969
4025
|
}
|
|
4026
|
+
function lprojLocales(dir) {
|
|
4027
|
+
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync10(join4(dir, `${l}.lproj`, "Localizable.strings")));
|
|
4028
|
+
}
|
|
4029
|
+
function detectApple(root) {
|
|
4030
|
+
const candidates = [root, ...listDirs(root).map((d) => join4(root, d))];
|
|
4031
|
+
let best = null;
|
|
4032
|
+
for (const dir of candidates) {
|
|
4033
|
+
const locales = lprojLocales(dir);
|
|
4034
|
+
if (locales.length === 0) continue;
|
|
4035
|
+
if (!best || locales.length > best.locales.length) {
|
|
4036
|
+
best = {
|
|
4037
|
+
format: "apple-strings",
|
|
4038
|
+
localeRoot: dir,
|
|
4039
|
+
locales,
|
|
4040
|
+
sourceLocale: pickSource(locales, (loc) => {
|
|
4041
|
+
try {
|
|
4042
|
+
return statSync3(join4(dir, `${loc}.lproj`, "Localizable.strings")).size;
|
|
4043
|
+
} catch {
|
|
4044
|
+
return 0;
|
|
4045
|
+
}
|
|
4046
|
+
})
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
return best;
|
|
4051
|
+
}
|
|
3970
4052
|
function detect(root, formatOverride) {
|
|
3971
4053
|
if (!existsSync10(root)) return null;
|
|
3972
4054
|
if (formatOverride) {
|
|
@@ -3986,11 +4068,12 @@ var init_detect = __esm({
|
|
|
3986
4068
|
"use strict";
|
|
3987
4069
|
LOCALE_RE = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
3988
4070
|
VUE_DIR_CANDIDATES = ["src/locale", "src/locales", "src/i18n/locales", "locales", "lang"];
|
|
3989
|
-
DETECTORS = [detectLaravel, detectVue, detectArb];
|
|
4071
|
+
DETECTORS = [detectLaravel, detectVue, detectArb, detectApple];
|
|
3990
4072
|
BY_FORMAT = {
|
|
3991
4073
|
"laravel-php": detectLaravel,
|
|
3992
4074
|
"vue-i18n-json": (root) => detectVue(root, true),
|
|
3993
|
-
"flutter-arb": detectArb
|
|
4075
|
+
"flutter-arb": detectArb,
|
|
4076
|
+
"apple-strings": detectApple
|
|
3994
4077
|
};
|
|
3995
4078
|
}
|
|
3996
4079
|
});
|
|
@@ -4216,6 +4299,139 @@ var init_flutter_arb2 = __esm({
|
|
|
4216
4299
|
}
|
|
4217
4300
|
});
|
|
4218
4301
|
|
|
4302
|
+
// src/server/import/parsers/apple-strings.ts
|
|
4303
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync5 } from "fs";
|
|
4304
|
+
import { join as join8 } from "path";
|
|
4305
|
+
function localeFromLproj(dir) {
|
|
4306
|
+
const m = dir.match(/^(.+)\.lproj$/);
|
|
4307
|
+
if (!m) return null;
|
|
4308
|
+
return LOCALE_RE4.test(m[1]) ? m[1] : null;
|
|
4309
|
+
}
|
|
4310
|
+
function unescape(body) {
|
|
4311
|
+
return body.replace(/\\(U[0-9a-fA-F]{4}|u[0-9a-fA-F]{4}|.)/g, (_m, esc) => {
|
|
4312
|
+
const c = esc[0];
|
|
4313
|
+
if (c === "U" || c === "u") return String.fromCharCode(parseInt(esc.slice(1), 16));
|
|
4314
|
+
if (c === "n") return "\n";
|
|
4315
|
+
if (c === "t") return " ";
|
|
4316
|
+
if (c === "r") return "\r";
|
|
4317
|
+
return esc;
|
|
4318
|
+
});
|
|
4319
|
+
}
|
|
4320
|
+
function parseStrings(text, file, warnings) {
|
|
4321
|
+
const pairs = [];
|
|
4322
|
+
let i = 0;
|
|
4323
|
+
const n = text.length;
|
|
4324
|
+
const skipTrivia = () => {
|
|
4325
|
+
while (i < n) {
|
|
4326
|
+
const c = text[i];
|
|
4327
|
+
if (c === " " || c === " " || c === "\n" || c === "\r") {
|
|
4328
|
+
i++;
|
|
4329
|
+
continue;
|
|
4330
|
+
}
|
|
4331
|
+
if (c === "/" && text[i + 1] === "/") {
|
|
4332
|
+
i += 2;
|
|
4333
|
+
while (i < n && text[i] !== "\n") i++;
|
|
4334
|
+
continue;
|
|
4335
|
+
}
|
|
4336
|
+
if (c === "/" && text[i + 1] === "*") {
|
|
4337
|
+
i += 2;
|
|
4338
|
+
while (i < n && !(text[i] === "*" && text[i + 1] === "/")) i++;
|
|
4339
|
+
i += 2;
|
|
4340
|
+
continue;
|
|
4341
|
+
}
|
|
4342
|
+
break;
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4345
|
+
const readToken = () => {
|
|
4346
|
+
if (i >= n) return null;
|
|
4347
|
+
if (text[i] === '"') {
|
|
4348
|
+
i++;
|
|
4349
|
+
let raw2 = "";
|
|
4350
|
+
while (i < n) {
|
|
4351
|
+
const c = text[i];
|
|
4352
|
+
if (c === "\\") {
|
|
4353
|
+
raw2 += c + (text[i + 1] ?? "");
|
|
4354
|
+
i += 2;
|
|
4355
|
+
continue;
|
|
4356
|
+
}
|
|
4357
|
+
if (c === '"') {
|
|
4358
|
+
i++;
|
|
4359
|
+
return unescape(raw2);
|
|
4360
|
+
}
|
|
4361
|
+
raw2 += c;
|
|
4362
|
+
i++;
|
|
4363
|
+
}
|
|
4364
|
+
return null;
|
|
4365
|
+
}
|
|
4366
|
+
let raw = "";
|
|
4367
|
+
while (i < n && !/[\s=;]/.test(text[i])) raw += text[i++];
|
|
4368
|
+
return raw.length ? raw : null;
|
|
4369
|
+
};
|
|
4370
|
+
while (true) {
|
|
4371
|
+
skipTrivia();
|
|
4372
|
+
if (i >= n) break;
|
|
4373
|
+
const key = readToken();
|
|
4374
|
+
if (key === null) {
|
|
4375
|
+
warnings.push(`apple-strings: malformed entry in ${file} near offset ${i}`);
|
|
4376
|
+
break;
|
|
4377
|
+
}
|
|
4378
|
+
skipTrivia();
|
|
4379
|
+
if (text[i] !== "=") {
|
|
4380
|
+
warnings.push(`apple-strings: expected '=' after key "${key}" in ${file}`);
|
|
4381
|
+
break;
|
|
4382
|
+
}
|
|
4383
|
+
i++;
|
|
4384
|
+
skipTrivia();
|
|
4385
|
+
const value = readToken();
|
|
4386
|
+
if (value === null) {
|
|
4387
|
+
warnings.push(`apple-strings: missing value for key "${key}" in ${file}`);
|
|
4388
|
+
break;
|
|
4389
|
+
}
|
|
4390
|
+
skipTrivia();
|
|
4391
|
+
if (text[i] === ";") i++;
|
|
4392
|
+
pairs.push({ key, value });
|
|
4393
|
+
}
|
|
4394
|
+
return pairs;
|
|
4395
|
+
}
|
|
4396
|
+
var LOCALE_RE4, TABLE, appleStrings2;
|
|
4397
|
+
var init_apple_strings2 = __esm({
|
|
4398
|
+
"src/server/import/parsers/apple-strings.ts"() {
|
|
4399
|
+
"use strict";
|
|
4400
|
+
LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4401
|
+
TABLE = "Localizable.strings";
|
|
4402
|
+
appleStrings2 = {
|
|
4403
|
+
name: "apple-strings",
|
|
4404
|
+
parse(localeRoot, opts) {
|
|
4405
|
+
const warnings = [];
|
|
4406
|
+
const keys = {};
|
|
4407
|
+
const locales = [];
|
|
4408
|
+
for (const dir of readdirSync8(localeRoot).sort()) {
|
|
4409
|
+
const locale = localeFromLproj(dir);
|
|
4410
|
+
if (!locale) continue;
|
|
4411
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4412
|
+
const file = join8(localeRoot, dir, TABLE);
|
|
4413
|
+
let text;
|
|
4414
|
+
try {
|
|
4415
|
+
if (!statSync5(file).isFile()) continue;
|
|
4416
|
+
text = readFileSync13(file, "utf8");
|
|
4417
|
+
} catch {
|
|
4418
|
+
continue;
|
|
4419
|
+
}
|
|
4420
|
+
locales.push(locale);
|
|
4421
|
+
const others = readdirSync8(join8(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
|
|
4422
|
+
if (others.length) {
|
|
4423
|
+
warnings.push(`apple-strings: ${dir} has other .strings tables (${others.join(", ")}); only ${TABLE} is imported`);
|
|
4424
|
+
}
|
|
4425
|
+
for (const { key, value } of parseStrings(text, file, warnings)) {
|
|
4426
|
+
(keys[key] ??= { values: {} }).values[locale] = value;
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
return { locales, keys, warnings };
|
|
4430
|
+
}
|
|
4431
|
+
};
|
|
4432
|
+
}
|
|
4433
|
+
});
|
|
4434
|
+
|
|
4219
4435
|
// src/server/import/parsers/index.ts
|
|
4220
4436
|
function getParser(name) {
|
|
4221
4437
|
const p = REGISTRY[name];
|
|
@@ -4229,10 +4445,12 @@ var init_parsers = __esm({
|
|
|
4229
4445
|
init_vue_i18n_json2();
|
|
4230
4446
|
init_laravel_php2();
|
|
4231
4447
|
init_flutter_arb2();
|
|
4448
|
+
init_apple_strings2();
|
|
4232
4449
|
REGISTRY = {
|
|
4233
4450
|
[vueI18nJson2.name]: vueI18nJson2,
|
|
4234
4451
|
[laravelPhp2.name]: laravelPhp2,
|
|
4235
|
-
[flutterArb2.name]: flutterArb2
|
|
4452
|
+
[flutterArb2.name]: flutterArb2,
|
|
4453
|
+
[appleStrings2.name]: appleStrings2
|
|
4236
4454
|
};
|
|
4237
4455
|
}
|
|
4238
4456
|
});
|
|
@@ -4242,10 +4460,13 @@ function assemble2(parsed, opts) {
|
|
|
4242
4460
|
const warnings = [...parsed.warnings];
|
|
4243
4461
|
const base = OUTPUT_BY_FORMAT[opts.format];
|
|
4244
4462
|
if (!base) throw new Error(`No output mapping for format "${opts.format}"`);
|
|
4463
|
+
const prefix = (opts.localeRootRel ?? "").replace(/\\/g, "/").replace(/\/+$/, "");
|
|
4464
|
+
const path = base.rootRelative && prefix ? `${prefix}/${base.path}` : base.path;
|
|
4245
4465
|
const rawLocales = [.../* @__PURE__ */ new Set([opts.sourceLocale, ...parsed.locales])];
|
|
4246
4466
|
const pairs = rawLocales.map((obs) => [canonLocale(obs), obs]);
|
|
4247
4467
|
const inferred = inferLocaleStyle(pairs, getAdapter(base.adapter).defaultLocaleCase);
|
|
4248
|
-
const
|
|
4468
|
+
const { rootRelative: _rootRelative, ...baseOutput } = base;
|
|
4469
|
+
const output = { ...baseOutput, path, ...inferred };
|
|
4249
4470
|
const sourceLocale = canonLocale(opts.sourceLocale);
|
|
4250
4471
|
const locales = [...new Set(rawLocales.map(canonLocale))].sort();
|
|
4251
4472
|
const keys = {};
|
|
@@ -4314,7 +4535,8 @@ var init_assemble = __esm({
|
|
|
4314
4535
|
OUTPUT_BY_FORMAT = {
|
|
4315
4536
|
"laravel-php": { adapter: "laravel-php", path: "lang/{locale}/{namespace}.php" },
|
|
4316
4537
|
"vue-i18n-json": { adapter: "vue-i18n-json", path: "src/locale/{locale}.json" },
|
|
4317
|
-
"flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" }
|
|
4538
|
+
"flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" },
|
|
4539
|
+
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true }
|
|
4318
4540
|
};
|
|
4319
4541
|
}
|
|
4320
4542
|
});
|
|
@@ -4325,6 +4547,7 @@ __export(run_exports, {
|
|
|
4325
4547
|
previewImport: () => previewImport,
|
|
4326
4548
|
runImport: () => runImport
|
|
4327
4549
|
});
|
|
4550
|
+
import { relative as relative3 } from "path";
|
|
4328
4551
|
function previewImport(projectRoot, format) {
|
|
4329
4552
|
const det = detect(projectRoot, format);
|
|
4330
4553
|
if (!det) return null;
|
|
@@ -4358,7 +4581,8 @@ function runImport(opts) {
|
|
|
4358
4581
|
const assembled = assemble2(parsed, {
|
|
4359
4582
|
sourceLocale: opts.sourceLocale ?? det.sourceLocale,
|
|
4360
4583
|
format: det.format,
|
|
4361
|
-
cldr: opts.cldr
|
|
4584
|
+
cldr: opts.cldr,
|
|
4585
|
+
localeRootRel: relative3(opts.projectRoot, det.localeRoot)
|
|
4362
4586
|
});
|
|
4363
4587
|
const { warnings, ...rest } = assembled;
|
|
4364
4588
|
const state = validate(rest);
|
|
@@ -4734,12 +4958,12 @@ var init_checks = __esm({
|
|
|
4734
4958
|
});
|
|
4735
4959
|
|
|
4736
4960
|
// src/server/ui-prefs.ts
|
|
4737
|
-
import { readFileSync as
|
|
4961
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
4738
4962
|
import { homedir } from "os";
|
|
4739
|
-
import { join as
|
|
4963
|
+
import { join as join9 } from "path";
|
|
4740
4964
|
function readJson2(path) {
|
|
4741
4965
|
try {
|
|
4742
|
-
const parsed = JSON.parse(
|
|
4966
|
+
const parsed = JSON.parse(readFileSync14(path, "utf8"));
|
|
4743
4967
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4744
4968
|
} catch {
|
|
4745
4969
|
return {};
|
|
@@ -4764,7 +4988,7 @@ var init_ui_prefs = __esm({
|
|
|
4764
4988
|
THEMES = ["system", "light", "dark"];
|
|
4765
4989
|
isThemeMode = (v) => THEMES.includes(v);
|
|
4766
4990
|
isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
4767
|
-
defaultUiPrefsPath = () =>
|
|
4991
|
+
defaultUiPrefsPath = () => join9(homedir(), ".glotfile", "ui.json");
|
|
4768
4992
|
DEFAULTS = { theme: "system" };
|
|
4769
4993
|
}
|
|
4770
4994
|
});
|
|
@@ -4772,13 +4996,13 @@ var init_ui_prefs = __esm({
|
|
|
4772
4996
|
// src/server/api.ts
|
|
4773
4997
|
import { Hono } from "hono";
|
|
4774
4998
|
import { streamSSE } from "hono/streaming";
|
|
4775
|
-
import { readFileSync as
|
|
4776
|
-
import { dirname as dirname3, resolve as resolve9, basename, relative as
|
|
4999
|
+
import { readFileSync as readFileSync15, existsSync as existsSync11, readdirSync as readdirSync9, statSync as statSync6, rmSync as rmSync4 } from "fs";
|
|
5000
|
+
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
4777
5001
|
function projectName(root) {
|
|
4778
5002
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
4779
5003
|
if (existsSync11(nameFile)) {
|
|
4780
5004
|
try {
|
|
4781
|
-
const name =
|
|
5005
|
+
const name = readFileSync15(nameFile, "utf8").trim();
|
|
4782
5006
|
if (name) return name;
|
|
4783
5007
|
} catch {
|
|
4784
5008
|
}
|
|
@@ -4893,7 +5117,7 @@ function createApi(deps) {
|
|
|
4893
5117
|
app.get("/file", (c) => c.json({ path: deps.statePath, name: basename(deps.statePath), dir: projectRoot, project: basename(projectRoot) }));
|
|
4894
5118
|
app.get("/files", (c) => {
|
|
4895
5119
|
const found = /* @__PURE__ */ new Map();
|
|
4896
|
-
const activeRel =
|
|
5120
|
+
const activeRel = relative4(projectRoot, deps.statePath);
|
|
4897
5121
|
found.set(deps.statePath, {
|
|
4898
5122
|
name: basename(deps.statePath),
|
|
4899
5123
|
path: deps.statePath,
|
|
@@ -4903,7 +5127,7 @@ function createApi(deps) {
|
|
|
4903
5127
|
if (depth > 4) return;
|
|
4904
5128
|
let entries = [];
|
|
4905
5129
|
try {
|
|
4906
|
-
entries =
|
|
5130
|
+
entries = readdirSync9(dir);
|
|
4907
5131
|
} catch {
|
|
4908
5132
|
return;
|
|
4909
5133
|
}
|
|
@@ -4917,7 +5141,7 @@ function createApi(deps) {
|
|
|
4917
5141
|
filePath = abs;
|
|
4918
5142
|
} else {
|
|
4919
5143
|
try {
|
|
4920
|
-
if (
|
|
5144
|
+
if (statSync6(abs).isDirectory()) walk(abs, depth + 1);
|
|
4921
5145
|
} catch {
|
|
4922
5146
|
}
|
|
4923
5147
|
continue;
|
|
@@ -4925,7 +5149,7 @@ function createApi(deps) {
|
|
|
4925
5149
|
if (found.has(filePath)) continue;
|
|
4926
5150
|
try {
|
|
4927
5151
|
loadState(filePath);
|
|
4928
|
-
const rel =
|
|
5152
|
+
const rel = relative4(projectRoot, filePath);
|
|
4929
5153
|
found.set(filePath, { name: basename(filePath), path: filePath, relDir: rel !== basename(filePath) ? dirname3(rel) : void 0 });
|
|
4930
5154
|
} catch {
|
|
4931
5155
|
}
|
|
@@ -5004,7 +5228,7 @@ function createApi(deps) {
|
|
|
5004
5228
|
for (const e of Object.values(s.keys)) if (e.screenshot === screenshot) return;
|
|
5005
5229
|
const root = dirname3(resolve9(deps.statePath));
|
|
5006
5230
|
const abs = resolve9(root, screenshot);
|
|
5007
|
-
const rel =
|
|
5231
|
+
const rel = relative4(root, abs);
|
|
5008
5232
|
const seg0 = rel.split(sep2)[0] ?? "";
|
|
5009
5233
|
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") && existsSync11(abs)) {
|
|
5010
5234
|
try {
|
|
@@ -5735,7 +5959,7 @@ __export(server_exports, {
|
|
|
5735
5959
|
import { Hono as Hono2 } from "hono";
|
|
5736
5960
|
import { serve } from "@hono/node-server";
|
|
5737
5961
|
import { fileURLToPath } from "url";
|
|
5738
|
-
import { dirname as dirname4, join as
|
|
5962
|
+
import { dirname as dirname4, join as join10, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
|
|
5739
5963
|
import { readFile, stat } from "fs/promises";
|
|
5740
5964
|
import { createServer } from "net";
|
|
5741
5965
|
import open from "open";
|
|
@@ -5778,7 +6002,7 @@ function buildApp(opts) {
|
|
|
5778
6002
|
const file = await readFileResponse(target);
|
|
5779
6003
|
if (file) return file;
|
|
5780
6004
|
}
|
|
5781
|
-
const index = await readFileResponse(
|
|
6005
|
+
const index = await readFileResponse(join10(root, "index.html"));
|
|
5782
6006
|
if (index) return index;
|
|
5783
6007
|
return c.notFound();
|
|
5784
6008
|
});
|
|
@@ -5836,7 +6060,7 @@ var init_server = __esm({
|
|
|
5836
6060
|
init_scan();
|
|
5837
6061
|
init_scanner();
|
|
5838
6062
|
here = dirname4(fileURLToPath(import.meta.url));
|
|
5839
|
-
DEFAULT_UI_DIR =
|
|
6063
|
+
DEFAULT_UI_DIR = join10(here, "..", "ui");
|
|
5840
6064
|
MIME = {
|
|
5841
6065
|
".html": "text/html; charset=utf-8",
|
|
5842
6066
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -5880,8 +6104,9 @@ init_scanner();
|
|
|
5880
6104
|
init_context();
|
|
5881
6105
|
init_run2();
|
|
5882
6106
|
init_outputs();
|
|
5883
|
-
import { resolve as resolve11, dirname as dirname5 } from "path";
|
|
5884
|
-
import { readFileSync as
|
|
6107
|
+
import { resolve as resolve11, dirname as dirname5, join as join11 } from "path";
|
|
6108
|
+
import { readFileSync as readFileSync16, existsSync as existsSync12, mkdirSync as mkdirSync4, cpSync } from "fs";
|
|
6109
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5885
6110
|
|
|
5886
6111
|
// src/server/lint/locate.ts
|
|
5887
6112
|
function locate(rawText, key) {
|
|
@@ -5947,8 +6172,7 @@ function formatSarif(report, rawText) {
|
|
|
5947
6172
|
}
|
|
5948
6173
|
|
|
5949
6174
|
// src/server/cli.ts
|
|
5950
|
-
|
|
5951
|
-
var COMMANDS = ["serve", "export", "translate", "lint", "check", "import", "build-context", "scan", "prune", "split"];
|
|
6175
|
+
var COMMANDS = ["serve", "export", "translate", "lint", "check", "import", "build-context", "scan", "prune", "split", "skill"];
|
|
5952
6176
|
var isCommand = (s) => s != null && COMMANDS.includes(s);
|
|
5953
6177
|
function parseArgs(argv) {
|
|
5954
6178
|
const statePath = resolve11(process.cwd(), "glotfile.json");
|
|
@@ -6015,6 +6239,7 @@ function parseArgs(argv) {
|
|
|
6015
6239
|
else if (flag === "--unused") args.unused = true;
|
|
6016
6240
|
else if (flag === "--write") args.write = true;
|
|
6017
6241
|
else if (flag === "--estimate") args.estimate = true;
|
|
6242
|
+
else if (flag === "--print") args.print = true;
|
|
6018
6243
|
}
|
|
6019
6244
|
return args;
|
|
6020
6245
|
}
|
|
@@ -6188,7 +6413,7 @@ async function runLintCmd(args) {
|
|
|
6188
6413
|
}
|
|
6189
6414
|
return;
|
|
6190
6415
|
}
|
|
6191
|
-
const rawText = existsSync12(args.statePath) ?
|
|
6416
|
+
const rawText = existsSync12(args.statePath) ? readFileSync16(args.statePath, "utf8") : "";
|
|
6192
6417
|
const report = await runLint(state, {
|
|
6193
6418
|
locales: args.locales,
|
|
6194
6419
|
ruleIds: args.ruleIds,
|
|
@@ -6212,7 +6437,7 @@ async function runCheck(args) {
|
|
|
6212
6437
|
process.exitCode = 1;
|
|
6213
6438
|
return;
|
|
6214
6439
|
}
|
|
6215
|
-
const rawText = existsSync12(args.statePath) ?
|
|
6440
|
+
const rawText = existsSync12(args.statePath) ? readFileSync16(args.statePath, "utf8") : "";
|
|
6216
6441
|
const root = dirname5(resolve11(args.statePath));
|
|
6217
6442
|
const lint = await runLint(state, {});
|
|
6218
6443
|
const findings = sortFindings([...lint.findings, ...checkOutputs(state, root)]);
|
|
@@ -6373,6 +6598,22 @@ function runSplit(args) {
|
|
|
6373
6598
|
`Split catalog into ${splitDirFor(args.statePath)}/ (config.json, keys.json, locales/ \u2014 up to ${state.config.locales.length} locale files). Removed ${args.statePath}.`
|
|
6374
6599
|
);
|
|
6375
6600
|
}
|
|
6601
|
+
var SKILL_SRC = join11(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
|
|
6602
|
+
function runSkill(args) {
|
|
6603
|
+
if (args.print) {
|
|
6604
|
+
console.log(readFileSync16(join11(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
|
|
6605
|
+
return;
|
|
6606
|
+
}
|
|
6607
|
+
const dest = resolve11(process.cwd(), ".claude", "skills", "glotfile");
|
|
6608
|
+
if (existsSync12(dest) && !args.importForce) {
|
|
6609
|
+
console.error(`${dest} already exists; pass --force to overwrite`);
|
|
6610
|
+
process.exitCode = 1;
|
|
6611
|
+
return;
|
|
6612
|
+
}
|
|
6613
|
+
mkdirSync4(dirname5(dest), { recursive: true });
|
|
6614
|
+
cpSync(SKILL_SRC, dest, { recursive: true });
|
|
6615
|
+
console.log(`Installed the glotfile skill to ${dest}. Restart Claude Code to pick it up.`);
|
|
6616
|
+
}
|
|
6376
6617
|
var GLOBAL_OPTS = [
|
|
6377
6618
|
["-f, --file <path>", "State file to use (default: ./glotfile.json)"],
|
|
6378
6619
|
["-h, --help", "Show this help"]
|
|
@@ -6458,6 +6699,14 @@ var COMMAND_HELP = {
|
|
|
6458
6699
|
summary: "Convert glotfile.json into a glotfile/ directory of per-locale files (faster, reviewable git diffs).",
|
|
6459
6700
|
usage: "glotfile split",
|
|
6460
6701
|
options: []
|
|
6702
|
+
},
|
|
6703
|
+
skill: {
|
|
6704
|
+
summary: "Install the Claude Code skill for managing glotfile into ./.claude/skills/glotfile/.",
|
|
6705
|
+
usage: "glotfile skill [--print] [--force]",
|
|
6706
|
+
options: [
|
|
6707
|
+
["--print", "Write SKILL.md to stdout instead of installing"],
|
|
6708
|
+
["--force", "Overwrite an existing installed skill"]
|
|
6709
|
+
]
|
|
6461
6710
|
}
|
|
6462
6711
|
};
|
|
6463
6712
|
function formatOpts(opts) {
|
|
@@ -6508,6 +6757,7 @@ async function main(argv) {
|
|
|
6508
6757
|
if (args.command === "scan") return runScanCmd(args);
|
|
6509
6758
|
if (args.command === "prune") return runPrune(args);
|
|
6510
6759
|
if (args.command === "split") return runSplit(args);
|
|
6760
|
+
if (args.command === "skill") return runSkill(args);
|
|
6511
6761
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
6512
6762
|
const { url } = await startServer2({ statePath: args.statePath, dev: args.dev });
|
|
6513
6763
|
if (args.dev) console.log(`Glotfile dev API on ${url} \u2014 open the UI at the Vite "Local:" URL above`);
|
|
@@ -6523,5 +6773,6 @@ export {
|
|
|
6523
6773
|
main,
|
|
6524
6774
|
parseArgs,
|
|
6525
6775
|
runPrune,
|
|
6776
|
+
runSkill,
|
|
6526
6777
|
watchTargetFor
|
|
6527
6778
|
};
|