glotfile 0.5.0 → 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 +331 -63
- package/dist/server/server.js +367 -141
- 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/server.js
CHANGED
|
@@ -29,7 +29,7 @@ var init_dictionary_en = __esm({
|
|
|
29
29
|
import { Hono as Hono2 } from "hono";
|
|
30
30
|
import { serve } from "@hono/node-server";
|
|
31
31
|
import { fileURLToPath } from "url";
|
|
32
|
-
import { dirname as dirname4, join as
|
|
32
|
+
import { dirname as dirname4, join as join10, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
|
|
33
33
|
import { readFile, stat } from "fs/promises";
|
|
34
34
|
import { createServer } from "net";
|
|
35
35
|
import open from "open";
|
|
@@ -916,7 +916,11 @@ var PATTERNS = {
|
|
|
916
916
|
apple: [
|
|
917
917
|
/NSLocalizedString\s*\(\s*@?"([^"]+)"/g,
|
|
918
918
|
/String\s*\(\s*localized:\s*"([^"]+)"/g,
|
|
919
|
-
/localizedString\s*\(\s*forKey:\s*"([^"]+)"/g
|
|
919
|
+
/localizedString\s*\(\s*forKey:\s*"([^"]+)"/g,
|
|
920
|
+
// The "key".localized / "key".localised String-extension idiom, where the
|
|
921
|
+
// literal IS the key (common when keys are natural-language source text).
|
|
922
|
+
/"([^"]+)"\s*\.\s*localized\b/g,
|
|
923
|
+
/"([^"]+)"\s*\.\s*localised\b/g
|
|
920
924
|
]
|
|
921
925
|
};
|
|
922
926
|
var PREFIX_PATTERNS = {
|
|
@@ -934,7 +938,7 @@ var PREFIX_PATTERNS = {
|
|
|
934
938
|
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$]*)\$\{/g
|
|
935
939
|
]
|
|
936
940
|
};
|
|
937
|
-
var CACHE_VERSION =
|
|
941
|
+
var CACHE_VERSION = 5;
|
|
938
942
|
var EXT_SCANNER = {
|
|
939
943
|
".php": "laravel",
|
|
940
944
|
".vue": "js-i18n",
|
|
@@ -1493,6 +1497,88 @@ function globToRegExp2(glob) {
|
|
|
1493
1497
|
return new RegExp(`^${escaped}$`);
|
|
1494
1498
|
}
|
|
1495
1499
|
|
|
1500
|
+
// src/server/ai/batch.ts
|
|
1501
|
+
var MalformedReplyError = class extends Error {
|
|
1502
|
+
constructor(raw) {
|
|
1503
|
+
super("Model reply was not valid translation JSON.");
|
|
1504
|
+
this.raw = raw;
|
|
1505
|
+
this.name = "MalformedReplyError";
|
|
1506
|
+
}
|
|
1507
|
+
raw;
|
|
1508
|
+
};
|
|
1509
|
+
function parseReplyItems(text) {
|
|
1510
|
+
let parsed;
|
|
1511
|
+
try {
|
|
1512
|
+
parsed = JSON.parse(text);
|
|
1513
|
+
} catch {
|
|
1514
|
+
throw new MalformedReplyError(text);
|
|
1515
|
+
}
|
|
1516
|
+
if (!Array.isArray(parsed.items)) throw new MalformedReplyError(text);
|
|
1517
|
+
return parsed.items;
|
|
1518
|
+
}
|
|
1519
|
+
function chunk(items, size) {
|
|
1520
|
+
const out = [];
|
|
1521
|
+
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
1522
|
+
return out;
|
|
1523
|
+
}
|
|
1524
|
+
function validateTranslation(req, translation) {
|
|
1525
|
+
if (translation === void 0) return { id: req.id, error: "No translation returned." };
|
|
1526
|
+
if (!placeholdersMatch(req.source, translation)) {
|
|
1527
|
+
return { id: req.id, error: "Placeholder mismatch between source and translation." };
|
|
1528
|
+
}
|
|
1529
|
+
if (req.maxLength !== void 0 && translation.length > req.maxLength) {
|
|
1530
|
+
return { id: req.id, translation, error: `Exceeds maxLength (${translation.length} > ${req.maxLength}).` };
|
|
1531
|
+
}
|
|
1532
|
+
return { id: req.id, translation };
|
|
1533
|
+
}
|
|
1534
|
+
function validatePlural(req, forms) {
|
|
1535
|
+
if (!forms) return { id: req.id, error: "No translation returned." };
|
|
1536
|
+
const plural = req.plural;
|
|
1537
|
+
if (!plural) return { id: req.id, error: "validatePlural called on a non-plural request." };
|
|
1538
|
+
const cats = plural.categories;
|
|
1539
|
+
const missing = cats.filter((c) => typeof forms[c] !== "string");
|
|
1540
|
+
if (missing.length) return { id: req.id, error: `Missing plural categories: ${missing.join(", ")}.` };
|
|
1541
|
+
const badPh = cats.find((c) => !pluralFormPlaceholdersMatch(c, req.source, forms[c]));
|
|
1542
|
+
if (badPh) return { id: req.id, error: `Placeholder mismatch in plural form "${badPh}".` };
|
|
1543
|
+
if (req.maxLength !== void 0) {
|
|
1544
|
+
const over = cats.find((c) => forms[c].length > req.maxLength);
|
|
1545
|
+
if (over) return { id: req.id, error: `Plural form "${over}" exceeds maxLength (${forms[over].length} > ${req.maxLength}).` };
|
|
1546
|
+
}
|
|
1547
|
+
const out = {};
|
|
1548
|
+
for (const c of cats) out[c] = forms[c];
|
|
1549
|
+
return { id: req.id, forms: out };
|
|
1550
|
+
}
|
|
1551
|
+
function validateReply(req, item) {
|
|
1552
|
+
return req.plural ? validatePlural(req, item?.forms) : validateTranslation(req, item?.translation);
|
|
1553
|
+
}
|
|
1554
|
+
async function runBatched(reqs, batchSize, callBatch, onBatchComplete, signal, onMalformedReply) {
|
|
1555
|
+
const failBatch = (batch) => batch.map((req) => ({ id: req.id, error: "Model returned malformed JSON for this string." }));
|
|
1556
|
+
async function resolveBatch(batch, isRetry = false) {
|
|
1557
|
+
let reply;
|
|
1558
|
+
try {
|
|
1559
|
+
reply = await callBatch(batch, signal);
|
|
1560
|
+
} catch (err) {
|
|
1561
|
+
if (!(err instanceof MalformedReplyError)) throw err;
|
|
1562
|
+
onMalformedReply?.(err.raw, batch.length);
|
|
1563
|
+
if (signal?.aborted) return failBatch(batch);
|
|
1564
|
+
if (batch.length === 1) return isRetry ? failBatch(batch) : resolveBatch(batch, true);
|
|
1565
|
+
const mid = Math.ceil(batch.length / 2);
|
|
1566
|
+
return [...await resolveBatch(batch.slice(0, mid)), ...await resolveBatch(batch.slice(mid))];
|
|
1567
|
+
}
|
|
1568
|
+
const byId = new Map(reply.map((r) => [r.id, r]));
|
|
1569
|
+
return batch.map((req) => validateReply(req, byId.get(req.id)));
|
|
1570
|
+
}
|
|
1571
|
+
const results = [];
|
|
1572
|
+
const total = reqs.length;
|
|
1573
|
+
for (const batch of chunk(reqs, Math.max(1, batchSize))) {
|
|
1574
|
+
if (signal?.aborted) break;
|
|
1575
|
+
const batchResults = await resolveBatch(batch);
|
|
1576
|
+
results.push(...batchResults);
|
|
1577
|
+
onBatchComplete?.(results.length, total, batchResults);
|
|
1578
|
+
}
|
|
1579
|
+
return results;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1496
1582
|
// src/server/ai/run.ts
|
|
1497
1583
|
function selectRequests(state, opts) {
|
|
1498
1584
|
const targets = (opts.locales ?? state.config.locales).filter((l) => l !== state.config.sourceLocale);
|
|
@@ -1603,7 +1689,7 @@ function attachScreenshotsForProvider(reqs, state, projectRoot, supportsVision)
|
|
|
1603
1689
|
return { skipped: keys.size };
|
|
1604
1690
|
}
|
|
1605
1691
|
var DEFAULT_LOCALE_CONCURRENCY = 3;
|
|
1606
|
-
async function runLocaleParallel(reqs, provider, hooks = {}, concurrency = DEFAULT_LOCALE_CONCURRENCY, signal) {
|
|
1692
|
+
async function runLocaleParallel(reqs, provider, hooks = {}, concurrency = DEFAULT_LOCALE_CONCURRENCY, signal, batchSize = Infinity) {
|
|
1607
1693
|
if (!reqs.length) return [];
|
|
1608
1694
|
const byLocale = /* @__PURE__ */ new Map();
|
|
1609
1695
|
for (const req of reqs) {
|
|
@@ -1614,26 +1700,42 @@ async function runLocaleParallel(reqs, provider, hooks = {}, concurrency = DEFAU
|
|
|
1614
1700
|
}
|
|
1615
1701
|
group.push(req);
|
|
1616
1702
|
}
|
|
1703
|
+
const localeBatches = [...byLocale.entries()].map(([locale, group]) => ({
|
|
1704
|
+
locale,
|
|
1705
|
+
batches: chunk(group, Math.max(1, batchSize))
|
|
1706
|
+
}));
|
|
1707
|
+
const jobs = [];
|
|
1708
|
+
const maxBatches = Math.max(...localeBatches.map((g) => g.batches.length));
|
|
1709
|
+
for (let i = 0; i < maxBatches; i++) {
|
|
1710
|
+
for (const g of localeBatches) {
|
|
1711
|
+
if (i < g.batches.length) jobs.push({ locale: g.locale, batch: g.batches[i] });
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
const remaining = new Map(localeBatches.map((g) => [g.locale, g.batches.length]));
|
|
1715
|
+
const started = /* @__PURE__ */ new Set();
|
|
1617
1716
|
const total = reqs.length;
|
|
1618
1717
|
let done = 0;
|
|
1619
1718
|
const allResults = [];
|
|
1620
|
-
const groups = [...byLocale.values()];
|
|
1621
1719
|
let next = 0;
|
|
1622
1720
|
async function worker() {
|
|
1623
|
-
while (next <
|
|
1721
|
+
while (next < jobs.length) {
|
|
1624
1722
|
if (signal?.aborted) break;
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1723
|
+
const { locale, batch } = jobs[next++];
|
|
1724
|
+
if (!started.has(locale)) {
|
|
1725
|
+
started.add(locale);
|
|
1726
|
+
hooks.onLocaleStart?.(locale);
|
|
1727
|
+
}
|
|
1728
|
+
const batchResults = await provider.translate(batch, (_localeDone, _localeTotal, results) => {
|
|
1729
|
+
done += results.length;
|
|
1730
|
+
hooks.onBatchComplete?.(done, total, results, locale);
|
|
1731
|
+
}, signal, (raw, size) => hooks.onMalformedReply?.(raw, size, locale));
|
|
1732
|
+
allResults.push(...batchResults);
|
|
1733
|
+
const left = remaining.get(locale) - 1;
|
|
1734
|
+
remaining.set(locale, left);
|
|
1735
|
+
if (left === 0 && !signal?.aborted) hooks.onLocaleDone?.(locale);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
const workers = Array.from({ length: Math.min(concurrency, jobs.length) }, worker);
|
|
1637
1739
|
await Promise.all(workers);
|
|
1638
1740
|
return allResults;
|
|
1639
1741
|
}
|
|
@@ -2639,8 +2741,50 @@ var appleStringsdict = {
|
|
|
2639
2741
|
}
|
|
2640
2742
|
};
|
|
2641
2743
|
|
|
2744
|
+
// src/server/adapters/apple-strings.ts
|
|
2745
|
+
var DEFAULT_LOCALE_CASE6 = "bcp47-hyphen";
|
|
2746
|
+
function escape(s) {
|
|
2747
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r");
|
|
2748
|
+
}
|
|
2749
|
+
var appleStrings = {
|
|
2750
|
+
name: "apple-strings",
|
|
2751
|
+
capabilities: {
|
|
2752
|
+
// Plurals belong in .stringsdict (apple-stringsdict), not the scalar table.
|
|
2753
|
+
plural: "none",
|
|
2754
|
+
select: "none",
|
|
2755
|
+
nesting: "flat",
|
|
2756
|
+
metadata: false,
|
|
2757
|
+
placeholderStyle: "printf",
|
|
2758
|
+
fileGrouping: "per-locale"
|
|
2759
|
+
},
|
|
2760
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE6,
|
|
2761
|
+
export(state, output) {
|
|
2762
|
+
const files = [];
|
|
2763
|
+
const warnings = [];
|
|
2764
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE6));
|
|
2765
|
+
const emptyAs = resolveEmptyAs(output, "source");
|
|
2766
|
+
const keys = Object.keys(state.keys).sort();
|
|
2767
|
+
for (const locale of state.config.locales) {
|
|
2768
|
+
const lines = [];
|
|
2769
|
+
for (const key of keys) {
|
|
2770
|
+
const entry = state.keys[key];
|
|
2771
|
+
if (entry.plural) continue;
|
|
2772
|
+
const value = resolveScalar(entry, locale, state.config.sourceLocale, emptyAs);
|
|
2773
|
+
if (value === null) continue;
|
|
2774
|
+
lines.push(`"${escape(key)}" = "${escape(value)}";`);
|
|
2775
|
+
}
|
|
2776
|
+
const contents = lines.length ? lines.join("\n") + "\n" : "";
|
|
2777
|
+
files.push({
|
|
2778
|
+
path: resolvePath(output.path, resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE6)),
|
|
2779
|
+
contents
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
return { files, warnings };
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
|
|
2642
2786
|
// src/server/adapters/vue-i18n-json.ts
|
|
2643
|
-
var
|
|
2787
|
+
var DEFAULT_LOCALE_CASE7 = "lower-hyphen";
|
|
2644
2788
|
var vueI18nJson = {
|
|
2645
2789
|
name: "vue-i18n-json",
|
|
2646
2790
|
capabilities: {
|
|
@@ -2651,11 +2795,11 @@ var vueI18nJson = {
|
|
|
2651
2795
|
placeholderStyle: "named",
|
|
2652
2796
|
fileGrouping: "per-locale"
|
|
2653
2797
|
},
|
|
2654
|
-
defaultLocaleCase:
|
|
2798
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE7,
|
|
2655
2799
|
export(state, output) {
|
|
2656
2800
|
const files = [];
|
|
2657
2801
|
const warnings = [];
|
|
2658
|
-
warnings.push(...localeCollisionWarnings(output, state.config.locales,
|
|
2802
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE7));
|
|
2659
2803
|
const { indent, finalNewline } = resolveFormat(state, output);
|
|
2660
2804
|
const fmt = { indent, sortKeys: true, finalNewline };
|
|
2661
2805
|
const emptyAs = resolveEmptyAs(output, "omit");
|
|
@@ -2695,7 +2839,7 @@ var vueI18nJson = {
|
|
|
2695
2839
|
}
|
|
2696
2840
|
payload = tree;
|
|
2697
2841
|
}
|
|
2698
|
-
files.push({ path: resolvePath(output.path, resolveLocaleToken(output, locale,
|
|
2842
|
+
files.push({ path: resolvePath(output.path, resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE7)), contents: serializeJson(payload, fmt) });
|
|
2699
2843
|
}
|
|
2700
2844
|
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
2701
2845
|
return { files, warnings };
|
|
@@ -2739,7 +2883,7 @@ function renderEmbeddedIcu(value) {
|
|
|
2739
2883
|
function renderScalar(value, ids) {
|
|
2740
2884
|
return isIcuPluralOrSelect(value) ? renderEmbeddedIcu(value) : renderInterpolations(value, ids);
|
|
2741
2885
|
}
|
|
2742
|
-
var
|
|
2886
|
+
var DEFAULT_LOCALE_CASE8 = "bcp47-hyphen";
|
|
2743
2887
|
var angularXliff = {
|
|
2744
2888
|
name: "angular-xliff",
|
|
2745
2889
|
capabilities: {
|
|
@@ -2750,17 +2894,17 @@ var angularXliff = {
|
|
|
2750
2894
|
placeholderStyle: "icu",
|
|
2751
2895
|
fileGrouping: "per-locale"
|
|
2752
2896
|
},
|
|
2753
|
-
defaultLocaleCase:
|
|
2897
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE8,
|
|
2754
2898
|
export(state, output) {
|
|
2755
2899
|
const files = [];
|
|
2756
2900
|
const warnings = [];
|
|
2757
|
-
warnings.push(...localeCollisionWarnings(output, state.config.locales,
|
|
2901
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE8));
|
|
2758
2902
|
const sourceLocale = state.config.sourceLocale;
|
|
2759
|
-
const sourceToken = resolveLocaleToken(output, sourceLocale,
|
|
2903
|
+
const sourceToken = resolveLocaleToken(output, sourceLocale, DEFAULT_LOCALE_CASE8);
|
|
2760
2904
|
const emptyAs = resolveEmptyAs(output, "source");
|
|
2761
2905
|
const keys = Object.keys(state.keys).sort();
|
|
2762
2906
|
for (const locale of state.config.locales) {
|
|
2763
|
-
const token = resolveLocaleToken(output, locale,
|
|
2907
|
+
const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE8);
|
|
2764
2908
|
const units = [];
|
|
2765
2909
|
for (const key of keys) {
|
|
2766
2910
|
const entry = state.keys[key];
|
|
@@ -2823,7 +2967,7 @@ function yamlMap(node, indent, level) {
|
|
|
2823
2967
|
}
|
|
2824
2968
|
return lines;
|
|
2825
2969
|
}
|
|
2826
|
-
var
|
|
2970
|
+
var DEFAULT_LOCALE_CASE9 = "bcp47-hyphen";
|
|
2827
2971
|
var railsYaml = {
|
|
2828
2972
|
name: "rails-yaml",
|
|
2829
2973
|
capabilities: {
|
|
@@ -2834,10 +2978,10 @@ var railsYaml = {
|
|
|
2834
2978
|
placeholderStyle: "named",
|
|
2835
2979
|
fileGrouping: "per-locale"
|
|
2836
2980
|
},
|
|
2837
|
-
defaultLocaleCase:
|
|
2981
|
+
defaultLocaleCase: DEFAULT_LOCALE_CASE9,
|
|
2838
2982
|
export(state, output) {
|
|
2839
2983
|
const warnings = [];
|
|
2840
|
-
warnings.push(...localeCollisionWarnings(output, state.config.locales,
|
|
2984
|
+
warnings.push(...localeCollisionWarnings(output, state.config.locales, DEFAULT_LOCALE_CASE9));
|
|
2841
2985
|
const { indent, finalNewline } = resolveFormat(state, output);
|
|
2842
2986
|
const emptyAs = resolveEmptyAs(output, "omit");
|
|
2843
2987
|
const files = [];
|
|
@@ -2869,7 +3013,7 @@ var railsYaml = {
|
|
|
2869
3013
|
for (const c of collisions) {
|
|
2870
3014
|
warnings.push({ code: "key-collision", key: c, locale, message: "key is both a leaf and a parent; dropped from nested output" });
|
|
2871
3015
|
}
|
|
2872
|
-
const token = resolveLocaleToken(output, locale,
|
|
3016
|
+
const token = resolveLocaleToken(output, locale, DEFAULT_LOCALE_CASE9);
|
|
2873
3017
|
const body = [`${yamlKey(token)}:`, ...yamlMap(nested, indent, 1)].join("\n");
|
|
2874
3018
|
files.push({ path: resolvePath(output.path, token), contents: finalNewline ? body + "\n" : body });
|
|
2875
3019
|
}
|
|
@@ -2910,6 +3054,7 @@ function getRegistry() {
|
|
|
2910
3054
|
[i18nextJson.name]: i18nextJson,
|
|
2911
3055
|
[gettextPo.name]: gettextPo,
|
|
2912
3056
|
[appleStringsdict.name]: appleStringsdict,
|
|
3057
|
+
[appleStrings.name]: appleStrings,
|
|
2913
3058
|
[vueI18nJson.name]: vueI18nJson,
|
|
2914
3059
|
[angularXliff.name]: angularXliff,
|
|
2915
3060
|
[railsYaml.name]: railsYaml
|
|
@@ -2940,8 +3085,8 @@ function checkOutputs(state, root) {
|
|
|
2940
3085
|
}
|
|
2941
3086
|
|
|
2942
3087
|
// src/server/api.ts
|
|
2943
|
-
import { readFileSync as
|
|
2944
|
-
import { dirname as dirname3, resolve as resolve9, basename, relative as
|
|
3088
|
+
import { readFileSync as readFileSync15, existsSync as existsSync11, readdirSync as readdirSync9, statSync as statSync6, rmSync as rmSync4 } from "fs";
|
|
3089
|
+
import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
|
|
2945
3090
|
|
|
2946
3091
|
// src/server/ai/anthropic.ts
|
|
2947
3092
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -3042,88 +3187,6 @@ var BATCH_SCHEMA = {
|
|
|
3042
3187
|
additionalProperties: false
|
|
3043
3188
|
};
|
|
3044
3189
|
|
|
3045
|
-
// src/server/ai/batch.ts
|
|
3046
|
-
var MalformedReplyError = class extends Error {
|
|
3047
|
-
constructor(raw) {
|
|
3048
|
-
super("Model reply was not valid translation JSON.");
|
|
3049
|
-
this.raw = raw;
|
|
3050
|
-
this.name = "MalformedReplyError";
|
|
3051
|
-
}
|
|
3052
|
-
raw;
|
|
3053
|
-
};
|
|
3054
|
-
function parseReplyItems(text) {
|
|
3055
|
-
let parsed;
|
|
3056
|
-
try {
|
|
3057
|
-
parsed = JSON.parse(text);
|
|
3058
|
-
} catch {
|
|
3059
|
-
throw new MalformedReplyError(text);
|
|
3060
|
-
}
|
|
3061
|
-
if (!Array.isArray(parsed.items)) throw new MalformedReplyError(text);
|
|
3062
|
-
return parsed.items;
|
|
3063
|
-
}
|
|
3064
|
-
function chunk(items, size) {
|
|
3065
|
-
const out = [];
|
|
3066
|
-
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
3067
|
-
return out;
|
|
3068
|
-
}
|
|
3069
|
-
function validateTranslation(req, translation) {
|
|
3070
|
-
if (translation === void 0) return { id: req.id, error: "No translation returned." };
|
|
3071
|
-
if (!placeholdersMatch(req.source, translation)) {
|
|
3072
|
-
return { id: req.id, error: "Placeholder mismatch between source and translation." };
|
|
3073
|
-
}
|
|
3074
|
-
if (req.maxLength !== void 0 && translation.length > req.maxLength) {
|
|
3075
|
-
return { id: req.id, translation, error: `Exceeds maxLength (${translation.length} > ${req.maxLength}).` };
|
|
3076
|
-
}
|
|
3077
|
-
return { id: req.id, translation };
|
|
3078
|
-
}
|
|
3079
|
-
function validatePlural(req, forms) {
|
|
3080
|
-
if (!forms) return { id: req.id, error: "No translation returned." };
|
|
3081
|
-
const plural = req.plural;
|
|
3082
|
-
if (!plural) return { id: req.id, error: "validatePlural called on a non-plural request." };
|
|
3083
|
-
const cats = plural.categories;
|
|
3084
|
-
const missing = cats.filter((c) => typeof forms[c] !== "string");
|
|
3085
|
-
if (missing.length) return { id: req.id, error: `Missing plural categories: ${missing.join(", ")}.` };
|
|
3086
|
-
const badPh = cats.find((c) => !pluralFormPlaceholdersMatch(c, req.source, forms[c]));
|
|
3087
|
-
if (badPh) return { id: req.id, error: `Placeholder mismatch in plural form "${badPh}".` };
|
|
3088
|
-
if (req.maxLength !== void 0) {
|
|
3089
|
-
const over = cats.find((c) => forms[c].length > req.maxLength);
|
|
3090
|
-
if (over) return { id: req.id, error: `Plural form "${over}" exceeds maxLength (${forms[over].length} > ${req.maxLength}).` };
|
|
3091
|
-
}
|
|
3092
|
-
const out = {};
|
|
3093
|
-
for (const c of cats) out[c] = forms[c];
|
|
3094
|
-
return { id: req.id, forms: out };
|
|
3095
|
-
}
|
|
3096
|
-
function validateReply(req, item) {
|
|
3097
|
-
return req.plural ? validatePlural(req, item?.forms) : validateTranslation(req, item?.translation);
|
|
3098
|
-
}
|
|
3099
|
-
async function runBatched(reqs, batchSize, callBatch, onBatchComplete, signal, onMalformedReply) {
|
|
3100
|
-
const failBatch = (batch) => batch.map((req) => ({ id: req.id, error: "Model returned malformed JSON for this string." }));
|
|
3101
|
-
async function resolveBatch(batch, isRetry = false) {
|
|
3102
|
-
let reply;
|
|
3103
|
-
try {
|
|
3104
|
-
reply = await callBatch(batch, signal);
|
|
3105
|
-
} catch (err) {
|
|
3106
|
-
if (!(err instanceof MalformedReplyError)) throw err;
|
|
3107
|
-
onMalformedReply?.(err.raw, batch.length);
|
|
3108
|
-
if (signal?.aborted) return failBatch(batch);
|
|
3109
|
-
if (batch.length === 1) return isRetry ? failBatch(batch) : resolveBatch(batch, true);
|
|
3110
|
-
const mid = Math.ceil(batch.length / 2);
|
|
3111
|
-
return [...await resolveBatch(batch.slice(0, mid)), ...await resolveBatch(batch.slice(mid))];
|
|
3112
|
-
}
|
|
3113
|
-
const byId = new Map(reply.map((r) => [r.id, r]));
|
|
3114
|
-
return batch.map((req) => validateReply(req, byId.get(req.id)));
|
|
3115
|
-
}
|
|
3116
|
-
const results = [];
|
|
3117
|
-
const total = reqs.length;
|
|
3118
|
-
for (const batch of chunk(reqs, Math.max(1, batchSize))) {
|
|
3119
|
-
if (signal?.aborted) break;
|
|
3120
|
-
const batchResults = await resolveBatch(batch);
|
|
3121
|
-
results.push(...batchResults);
|
|
3122
|
-
onBatchComplete?.(results.length, total, batchResults);
|
|
3123
|
-
}
|
|
3124
|
-
return results;
|
|
3125
|
-
}
|
|
3126
|
-
|
|
3127
3190
|
// src/server/ai/anthropic.ts
|
|
3128
3191
|
var AnthropicProvider = class {
|
|
3129
3192
|
constructor(config, client) {
|
|
@@ -3669,6 +3732,9 @@ function readLog(projectRoot, limit = 100) {
|
|
|
3669
3732
|
return entries.reverse().slice(0, limit);
|
|
3670
3733
|
}
|
|
3671
3734
|
|
|
3735
|
+
// src/server/import/run.ts
|
|
3736
|
+
import { relative as relative3 } from "path";
|
|
3737
|
+
|
|
3672
3738
|
// src/server/import/detect.ts
|
|
3673
3739
|
import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
3674
3740
|
import { join as join4 } from "path";
|
|
@@ -3733,11 +3799,38 @@ function detectArb(root) {
|
|
|
3733
3799
|
}
|
|
3734
3800
|
return null;
|
|
3735
3801
|
}
|
|
3736
|
-
|
|
3802
|
+
function lprojLocales(dir) {
|
|
3803
|
+
return listDirs(dir).map((d) => d.match(/^(.+)\.lproj$/)?.[1]).filter((l) => !!l && LOCALE_RE.test(l) && existsSync9(join4(dir, `${l}.lproj`, "Localizable.strings")));
|
|
3804
|
+
}
|
|
3805
|
+
function detectApple(root) {
|
|
3806
|
+
const candidates = [root, ...listDirs(root).map((d) => join4(root, d))];
|
|
3807
|
+
let best = null;
|
|
3808
|
+
for (const dir of candidates) {
|
|
3809
|
+
const locales = lprojLocales(dir);
|
|
3810
|
+
if (locales.length === 0) continue;
|
|
3811
|
+
if (!best || locales.length > best.locales.length) {
|
|
3812
|
+
best = {
|
|
3813
|
+
format: "apple-strings",
|
|
3814
|
+
localeRoot: dir,
|
|
3815
|
+
locales,
|
|
3816
|
+
sourceLocale: pickSource(locales, (loc) => {
|
|
3817
|
+
try {
|
|
3818
|
+
return statSync2(join4(dir, `${loc}.lproj`, "Localizable.strings")).size;
|
|
3819
|
+
} catch {
|
|
3820
|
+
return 0;
|
|
3821
|
+
}
|
|
3822
|
+
})
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
return best;
|
|
3827
|
+
}
|
|
3828
|
+
var DETECTORS = [detectLaravel, detectVue, detectArb, detectApple];
|
|
3737
3829
|
var BY_FORMAT = {
|
|
3738
3830
|
"laravel-php": detectLaravel,
|
|
3739
3831
|
"vue-i18n-json": (root) => detectVue(root, true),
|
|
3740
|
-
"flutter-arb": detectArb
|
|
3832
|
+
"flutter-arb": detectArb,
|
|
3833
|
+
"apple-strings": detectApple
|
|
3741
3834
|
};
|
|
3742
3835
|
function detect(root, formatOverride) {
|
|
3743
3836
|
if (!existsSync9(root)) return null;
|
|
@@ -3947,11 +4040,139 @@ var flutterArb2 = {
|
|
|
3947
4040
|
}
|
|
3948
4041
|
};
|
|
3949
4042
|
|
|
4043
|
+
// src/server/import/parsers/apple-strings.ts
|
|
4044
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
|
|
4045
|
+
import { join as join8 } from "path";
|
|
4046
|
+
var LOCALE_RE4 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
|
|
4047
|
+
var TABLE = "Localizable.strings";
|
|
4048
|
+
function localeFromLproj(dir) {
|
|
4049
|
+
const m = dir.match(/^(.+)\.lproj$/);
|
|
4050
|
+
if (!m) return null;
|
|
4051
|
+
return LOCALE_RE4.test(m[1]) ? m[1] : null;
|
|
4052
|
+
}
|
|
4053
|
+
function unescape(body) {
|
|
4054
|
+
return body.replace(/\\(U[0-9a-fA-F]{4}|u[0-9a-fA-F]{4}|.)/g, (_m, esc) => {
|
|
4055
|
+
const c = esc[0];
|
|
4056
|
+
if (c === "U" || c === "u") return String.fromCharCode(parseInt(esc.slice(1), 16));
|
|
4057
|
+
if (c === "n") return "\n";
|
|
4058
|
+
if (c === "t") return " ";
|
|
4059
|
+
if (c === "r") return "\r";
|
|
4060
|
+
return esc;
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
function parseStrings(text, file, warnings) {
|
|
4064
|
+
const pairs = [];
|
|
4065
|
+
let i = 0;
|
|
4066
|
+
const n = text.length;
|
|
4067
|
+
const skipTrivia = () => {
|
|
4068
|
+
while (i < n) {
|
|
4069
|
+
const c = text[i];
|
|
4070
|
+
if (c === " " || c === " " || c === "\n" || c === "\r") {
|
|
4071
|
+
i++;
|
|
4072
|
+
continue;
|
|
4073
|
+
}
|
|
4074
|
+
if (c === "/" && text[i + 1] === "/") {
|
|
4075
|
+
i += 2;
|
|
4076
|
+
while (i < n && text[i] !== "\n") i++;
|
|
4077
|
+
continue;
|
|
4078
|
+
}
|
|
4079
|
+
if (c === "/" && text[i + 1] === "*") {
|
|
4080
|
+
i += 2;
|
|
4081
|
+
while (i < n && !(text[i] === "*" && text[i + 1] === "/")) i++;
|
|
4082
|
+
i += 2;
|
|
4083
|
+
continue;
|
|
4084
|
+
}
|
|
4085
|
+
break;
|
|
4086
|
+
}
|
|
4087
|
+
};
|
|
4088
|
+
const readToken = () => {
|
|
4089
|
+
if (i >= n) return null;
|
|
4090
|
+
if (text[i] === '"') {
|
|
4091
|
+
i++;
|
|
4092
|
+
let raw2 = "";
|
|
4093
|
+
while (i < n) {
|
|
4094
|
+
const c = text[i];
|
|
4095
|
+
if (c === "\\") {
|
|
4096
|
+
raw2 += c + (text[i + 1] ?? "");
|
|
4097
|
+
i += 2;
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
4100
|
+
if (c === '"') {
|
|
4101
|
+
i++;
|
|
4102
|
+
return unescape(raw2);
|
|
4103
|
+
}
|
|
4104
|
+
raw2 += c;
|
|
4105
|
+
i++;
|
|
4106
|
+
}
|
|
4107
|
+
return null;
|
|
4108
|
+
}
|
|
4109
|
+
let raw = "";
|
|
4110
|
+
while (i < n && !/[\s=;]/.test(text[i])) raw += text[i++];
|
|
4111
|
+
return raw.length ? raw : null;
|
|
4112
|
+
};
|
|
4113
|
+
while (true) {
|
|
4114
|
+
skipTrivia();
|
|
4115
|
+
if (i >= n) break;
|
|
4116
|
+
const key = readToken();
|
|
4117
|
+
if (key === null) {
|
|
4118
|
+
warnings.push(`apple-strings: malformed entry in ${file} near offset ${i}`);
|
|
4119
|
+
break;
|
|
4120
|
+
}
|
|
4121
|
+
skipTrivia();
|
|
4122
|
+
if (text[i] !== "=") {
|
|
4123
|
+
warnings.push(`apple-strings: expected '=' after key "${key}" in ${file}`);
|
|
4124
|
+
break;
|
|
4125
|
+
}
|
|
4126
|
+
i++;
|
|
4127
|
+
skipTrivia();
|
|
4128
|
+
const value = readToken();
|
|
4129
|
+
if (value === null) {
|
|
4130
|
+
warnings.push(`apple-strings: missing value for key "${key}" in ${file}`);
|
|
4131
|
+
break;
|
|
4132
|
+
}
|
|
4133
|
+
skipTrivia();
|
|
4134
|
+
if (text[i] === ";") i++;
|
|
4135
|
+
pairs.push({ key, value });
|
|
4136
|
+
}
|
|
4137
|
+
return pairs;
|
|
4138
|
+
}
|
|
4139
|
+
var appleStrings2 = {
|
|
4140
|
+
name: "apple-strings",
|
|
4141
|
+
parse(localeRoot, opts) {
|
|
4142
|
+
const warnings = [];
|
|
4143
|
+
const keys = {};
|
|
4144
|
+
const locales = [];
|
|
4145
|
+
for (const dir of readdirSync7(localeRoot).sort()) {
|
|
4146
|
+
const locale = localeFromLproj(dir);
|
|
4147
|
+
if (!locale) continue;
|
|
4148
|
+
if (opts?.locales && !opts.locales.includes(locale)) continue;
|
|
4149
|
+
const file = join8(localeRoot, dir, TABLE);
|
|
4150
|
+
let text;
|
|
4151
|
+
try {
|
|
4152
|
+
if (!statSync4(file).isFile()) continue;
|
|
4153
|
+
text = readFileSync11(file, "utf8");
|
|
4154
|
+
} catch {
|
|
4155
|
+
continue;
|
|
4156
|
+
}
|
|
4157
|
+
locales.push(locale);
|
|
4158
|
+
const others = readdirSync7(join8(localeRoot, dir)).filter((f) => f.endsWith(".strings") && f !== TABLE);
|
|
4159
|
+
if (others.length) {
|
|
4160
|
+
warnings.push(`apple-strings: ${dir} has other .strings tables (${others.join(", ")}); only ${TABLE} is imported`);
|
|
4161
|
+
}
|
|
4162
|
+
for (const { key, value } of parseStrings(text, file, warnings)) {
|
|
4163
|
+
(keys[key] ??= { values: {} }).values[locale] = value;
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
return { locales, keys, warnings };
|
|
4167
|
+
}
|
|
4168
|
+
};
|
|
4169
|
+
|
|
3950
4170
|
// src/server/import/parsers/index.ts
|
|
3951
4171
|
var REGISTRY = {
|
|
3952
4172
|
[vueI18nJson2.name]: vueI18nJson2,
|
|
3953
4173
|
[laravelPhp2.name]: laravelPhp2,
|
|
3954
|
-
[flutterArb2.name]: flutterArb2
|
|
4174
|
+
[flutterArb2.name]: flutterArb2,
|
|
4175
|
+
[appleStrings2.name]: appleStrings2
|
|
3955
4176
|
};
|
|
3956
4177
|
function getParser(name) {
|
|
3957
4178
|
const p = REGISTRY[name];
|
|
@@ -3963,16 +4184,20 @@ function getParser(name) {
|
|
|
3963
4184
|
var OUTPUT_BY_FORMAT = {
|
|
3964
4185
|
"laravel-php": { adapter: "laravel-php", path: "lang/{locale}/{namespace}.php" },
|
|
3965
4186
|
"vue-i18n-json": { adapter: "vue-i18n-json", path: "src/locale/{locale}.json" },
|
|
3966
|
-
"flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" }
|
|
4187
|
+
"flutter-arb": { adapter: "flutter-arb", path: "lib/l10n/app_{locale}.arb" },
|
|
4188
|
+
"apple-strings": { adapter: "apple-strings", path: "{locale}.lproj/Localizable.strings", rootRelative: true }
|
|
3967
4189
|
};
|
|
3968
4190
|
function assemble2(parsed, opts) {
|
|
3969
4191
|
const warnings = [...parsed.warnings];
|
|
3970
4192
|
const base = OUTPUT_BY_FORMAT[opts.format];
|
|
3971
4193
|
if (!base) throw new Error(`No output mapping for format "${opts.format}"`);
|
|
4194
|
+
const prefix = (opts.localeRootRel ?? "").replace(/\\/g, "/").replace(/\/+$/, "");
|
|
4195
|
+
const path = base.rootRelative && prefix ? `${prefix}/${base.path}` : base.path;
|
|
3972
4196
|
const rawLocales = [.../* @__PURE__ */ new Set([opts.sourceLocale, ...parsed.locales])];
|
|
3973
4197
|
const pairs = rawLocales.map((obs) => [canonLocale(obs), obs]);
|
|
3974
4198
|
const inferred = inferLocaleStyle(pairs, getAdapter(base.adapter).defaultLocaleCase);
|
|
3975
|
-
const
|
|
4199
|
+
const { rootRelative: _rootRelative, ...baseOutput } = base;
|
|
4200
|
+
const output = { ...baseOutput, path, ...inferred };
|
|
3976
4201
|
const sourceLocale = canonLocale(opts.sourceLocale);
|
|
3977
4202
|
const locales = [...new Set(rawLocales.map(canonLocale))].sort();
|
|
3978
4203
|
const keys = {};
|
|
@@ -4064,7 +4289,8 @@ function runImport(opts) {
|
|
|
4064
4289
|
const assembled = assemble2(parsed, {
|
|
4065
4290
|
sourceLocale: opts.sourceLocale ?? det.sourceLocale,
|
|
4066
4291
|
format: det.format,
|
|
4067
|
-
cldr: opts.cldr
|
|
4292
|
+
cldr: opts.cldr,
|
|
4293
|
+
localeRootRel: relative3(opts.projectRoot, det.localeRoot)
|
|
4068
4294
|
});
|
|
4069
4295
|
const { warnings, ...rest } = assembled;
|
|
4070
4296
|
const state = validate(rest);
|
|
@@ -4077,7 +4303,7 @@ function runImport(opts) {
|
|
|
4077
4303
|
}
|
|
4078
4304
|
|
|
4079
4305
|
// src/server/export-run.ts
|
|
4080
|
-
import { existsSync as existsSync10, readFileSync as
|
|
4306
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12, readdirSync as readdirSync8, rmdirSync, statSync as statSync5, unlinkSync } from "fs";
|
|
4081
4307
|
import { dirname as dirname2, resolve as resolve7, sep } from "path";
|
|
4082
4308
|
function effectiveLocales(config) {
|
|
4083
4309
|
const limit = config.exportLocales;
|
|
@@ -4120,7 +4346,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
4120
4346
|
if (!segment.includes("{locale}") && !segment.includes("{namespace}")) {
|
|
4121
4347
|
const next = resolve7(dir, segment);
|
|
4122
4348
|
if (isLast) {
|
|
4123
|
-
if (stale(locale) && existsSync10(next) &&
|
|
4349
|
+
if (stale(locale) && existsSync10(next) && statSync5(next).isFile()) {
|
|
4124
4350
|
unlinkSync(next);
|
|
4125
4351
|
deleted++;
|
|
4126
4352
|
removeEmptyDirs(dir, root);
|
|
@@ -4133,7 +4359,7 @@ function pruneStaleLocaleFiles(output, validTokens, projectRoot) {
|
|
|
4133
4359
|
const re = segmentRegExp(segment);
|
|
4134
4360
|
let entries;
|
|
4135
4361
|
try {
|
|
4136
|
-
entries =
|
|
4362
|
+
entries = readdirSync8(dir, { withFileTypes: true });
|
|
4137
4363
|
} catch {
|
|
4138
4364
|
return;
|
|
4139
4365
|
}
|
|
@@ -4176,7 +4402,7 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
4176
4402
|
writtenPaths.add(abs);
|
|
4177
4403
|
let current = null;
|
|
4178
4404
|
try {
|
|
4179
|
-
current =
|
|
4405
|
+
current = readFileSync12(abs, "utf8");
|
|
4180
4406
|
} catch {
|
|
4181
4407
|
}
|
|
4182
4408
|
if (current === f.contents) {
|
|
@@ -4193,17 +4419,17 @@ function exportToDisk(state, projectRoot, opts) {
|
|
|
4193
4419
|
}
|
|
4194
4420
|
|
|
4195
4421
|
// src/server/ui-prefs.ts
|
|
4196
|
-
import { readFileSync as
|
|
4422
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4197
4423
|
import { homedir } from "os";
|
|
4198
|
-
import { join as
|
|
4424
|
+
import { join as join9 } from "path";
|
|
4199
4425
|
var THEMES = ["system", "light", "dark"];
|
|
4200
4426
|
var isThemeMode = (v) => THEMES.includes(v);
|
|
4201
4427
|
var isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
4202
|
-
var defaultUiPrefsPath = () =>
|
|
4428
|
+
var defaultUiPrefsPath = () => join9(homedir(), ".glotfile", "ui.json");
|
|
4203
4429
|
var DEFAULTS = { theme: "system" };
|
|
4204
4430
|
function readJson(path) {
|
|
4205
4431
|
try {
|
|
4206
|
-
const parsed = JSON.parse(
|
|
4432
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
4207
4433
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4208
4434
|
} catch {
|
|
4209
4435
|
return {};
|
|
@@ -4222,7 +4448,7 @@ function saveUiPrefs(path, prefs) {
|
|
|
4222
4448
|
}
|
|
4223
4449
|
|
|
4224
4450
|
// src/server/local-settings.ts
|
|
4225
|
-
import { readFileSync as
|
|
4451
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
4226
4452
|
import { resolve as resolve8 } from "path";
|
|
4227
4453
|
var EDITOR_IDS = ["vscode", "zed", "phpstorm"];
|
|
4228
4454
|
var isEditorId = (v) => EDITOR_IDS.includes(v);
|
|
@@ -4237,7 +4463,7 @@ var DEFAULT_EDITOR = "vscode";
|
|
|
4237
4463
|
var settingsPath = (projectRoot) => resolve8(projectRoot, ".glotfile", "settings.json");
|
|
4238
4464
|
function readJson2(path) {
|
|
4239
4465
|
try {
|
|
4240
|
-
const parsed = JSON.parse(
|
|
4466
|
+
const parsed = JSON.parse(readFileSync14(path, "utf8"));
|
|
4241
4467
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4242
4468
|
} catch {
|
|
4243
4469
|
return {};
|
|
@@ -4310,7 +4536,7 @@ function projectName(root) {
|
|
|
4310
4536
|
const nameFile = resolve9(root, ".idea", ".name");
|
|
4311
4537
|
if (existsSync11(nameFile)) {
|
|
4312
4538
|
try {
|
|
4313
|
-
const name =
|
|
4539
|
+
const name = readFileSync15(nameFile, "utf8").trim();
|
|
4314
4540
|
if (name) return name;
|
|
4315
4541
|
} catch {
|
|
4316
4542
|
}
|
|
@@ -4425,7 +4651,7 @@ function createApi(deps) {
|
|
|
4425
4651
|
app.get("/file", (c) => c.json({ path: deps.statePath, name: basename(deps.statePath), dir: projectRoot, project: basename(projectRoot) }));
|
|
4426
4652
|
app.get("/files", (c) => {
|
|
4427
4653
|
const found = /* @__PURE__ */ new Map();
|
|
4428
|
-
const activeRel =
|
|
4654
|
+
const activeRel = relative4(projectRoot, deps.statePath);
|
|
4429
4655
|
found.set(deps.statePath, {
|
|
4430
4656
|
name: basename(deps.statePath),
|
|
4431
4657
|
path: deps.statePath,
|
|
@@ -4435,7 +4661,7 @@ function createApi(deps) {
|
|
|
4435
4661
|
if (depth > 4) return;
|
|
4436
4662
|
let entries = [];
|
|
4437
4663
|
try {
|
|
4438
|
-
entries =
|
|
4664
|
+
entries = readdirSync9(dir);
|
|
4439
4665
|
} catch {
|
|
4440
4666
|
return;
|
|
4441
4667
|
}
|
|
@@ -4449,7 +4675,7 @@ function createApi(deps) {
|
|
|
4449
4675
|
filePath = abs;
|
|
4450
4676
|
} else {
|
|
4451
4677
|
try {
|
|
4452
|
-
if (
|
|
4678
|
+
if (statSync6(abs).isDirectory()) walk(abs, depth + 1);
|
|
4453
4679
|
} catch {
|
|
4454
4680
|
}
|
|
4455
4681
|
continue;
|
|
@@ -4457,7 +4683,7 @@ function createApi(deps) {
|
|
|
4457
4683
|
if (found.has(filePath)) continue;
|
|
4458
4684
|
try {
|
|
4459
4685
|
loadState(filePath);
|
|
4460
|
-
const rel =
|
|
4686
|
+
const rel = relative4(projectRoot, filePath);
|
|
4461
4687
|
found.set(filePath, { name: basename(filePath), path: filePath, relDir: rel !== basename(filePath) ? dirname3(rel) : void 0 });
|
|
4462
4688
|
} catch {
|
|
4463
4689
|
}
|
|
@@ -4536,7 +4762,7 @@ function createApi(deps) {
|
|
|
4536
4762
|
for (const e of Object.values(s.keys)) if (e.screenshot === screenshot) return;
|
|
4537
4763
|
const root = dirname3(resolve9(deps.statePath));
|
|
4538
4764
|
const abs = resolve9(root, screenshot);
|
|
4539
|
-
const rel =
|
|
4765
|
+
const rel = relative4(root, abs);
|
|
4540
4766
|
const seg0 = rel.split(sep2)[0] ?? "";
|
|
4541
4767
|
if (!rel.startsWith("..") && seg0.endsWith("-screenshots") && existsSync11(abs)) {
|
|
4542
4768
|
try {
|
|
@@ -5002,7 +5228,7 @@ function createApi(deps) {
|
|
|
5002
5228
|
raw
|
|
5003
5229
|
});
|
|
5004
5230
|
}
|
|
5005
|
-
}, aiCfg.concurrency, signal);
|
|
5231
|
+
}, aiCfg.concurrency, signal, aiCfg.batchSize);
|
|
5006
5232
|
if (!signal?.aborted) {
|
|
5007
5233
|
console.log(`[translate] done \u2014 wrote ${totalWritten}, ${allErrors.length} error(s)`);
|
|
5008
5234
|
await stream.writeSSE({ event: "done", data: JSON.stringify({ written: totalWritten, errors: allErrors }) });
|
|
@@ -5045,7 +5271,7 @@ function createApi(deps) {
|
|
|
5045
5271
|
raw
|
|
5046
5272
|
});
|
|
5047
5273
|
}
|
|
5048
|
-
}, aiCfg.concurrency);
|
|
5274
|
+
}, aiCfg.concurrency, void 0, aiCfg.batchSize);
|
|
5049
5275
|
const latest = load();
|
|
5050
5276
|
({ written, errors } = applyResults(latest, toTranslate, results, void 0, force));
|
|
5051
5277
|
const entry = {
|
|
@@ -5230,7 +5456,7 @@ function createApi(deps) {
|
|
|
5230
5456
|
|
|
5231
5457
|
// src/server/server.ts
|
|
5232
5458
|
var here = dirname4(fileURLToPath(import.meta.url));
|
|
5233
|
-
var DEFAULT_UI_DIR =
|
|
5459
|
+
var DEFAULT_UI_DIR = join10(here, "..", "ui");
|
|
5234
5460
|
var MIME = {
|
|
5235
5461
|
".html": "text/html; charset=utf-8",
|
|
5236
5462
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -5284,7 +5510,7 @@ function buildApp(opts) {
|
|
|
5284
5510
|
const file = await readFileResponse(target);
|
|
5285
5511
|
if (file) return file;
|
|
5286
5512
|
}
|
|
5287
|
-
const index = await readFileResponse(
|
|
5513
|
+
const index = await readFileResponse(join10(root, "index.html"));
|
|
5288
5514
|
if (index) return index;
|
|
5289
5515
|
return c.notFound();
|
|
5290
5516
|
});
|