glotfile 0.7.5 → 0.8.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 +1517 -1216
- package/dist/server/server.js +210 -9
- package/dist/ui/assets/index-8D4-Hok6.js +2299 -0
- package/dist/ui/assets/index-Cgnutw-J.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/skill/SKILL.md +4 -2
- package/skill/references/cli-reference.md +21 -1
- package/skill/references/workflows.md +25 -5
- package/dist/ui/assets/index-BvrhsGHu.js +0 -2124
- package/dist/ui/assets/index-dSBo_QMR.css +0 -1
package/dist/server/server.js
CHANGED
|
@@ -4788,6 +4788,23 @@ function parseAttrs(s) {
|
|
|
4788
4788
|
for (const m of s.matchAll(/([\w-]+)="([^"]*)"/g)) out[m[1]] = decodeEntities(m[2]);
|
|
4789
4789
|
return out;
|
|
4790
4790
|
}
|
|
4791
|
+
function decodeLocations(body) {
|
|
4792
|
+
const out = [];
|
|
4793
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4794
|
+
for (const g of body.matchAll(/<context-group\b[^>]*\bpurpose="location"[^>]*>([\s\S]*?)<\/context-group>/g)) {
|
|
4795
|
+
const inner = g[1];
|
|
4796
|
+
const file = inner.match(/<context\b[^>]*context-type="sourcefile"[^>]*>([\s\S]*?)<\/context>/)?.[1];
|
|
4797
|
+
if (file === void 0) continue;
|
|
4798
|
+
const lineRaw = inner.match(/<context\b[^>]*context-type="linenumber"[^>]*>([\s\S]*?)<\/context>/)?.[1];
|
|
4799
|
+
const decodedFile = decodeEntities(file.trim());
|
|
4800
|
+
const line = lineRaw ? parseInt(lineRaw.trim(), 10) || 1 : 1;
|
|
4801
|
+
const dedup = `${decodedFile}:${line}`;
|
|
4802
|
+
if (seen.has(dedup)) continue;
|
|
4803
|
+
seen.add(dedup);
|
|
4804
|
+
out.push({ file: decodedFile, line });
|
|
4805
|
+
}
|
|
4806
|
+
return out;
|
|
4807
|
+
}
|
|
4791
4808
|
function decodeInline(raw, addMeta) {
|
|
4792
4809
|
let out = "";
|
|
4793
4810
|
let last = 0;
|
|
@@ -4859,6 +4876,10 @@ var angularXliff2 = {
|
|
|
4859
4876
|
entry.values[targetLocale] = decodeInline(tgt[2], addMeta);
|
|
4860
4877
|
seen(targetLocale);
|
|
4861
4878
|
}
|
|
4879
|
+
if (entry.locations === void 0) {
|
|
4880
|
+
const locs = decodeLocations(body);
|
|
4881
|
+
if (locs.length) entry.locations = locs;
|
|
4882
|
+
}
|
|
4862
4883
|
}
|
|
4863
4884
|
}
|
|
4864
4885
|
return { locales, keys, warnings };
|
|
@@ -5605,6 +5626,96 @@ function assemble2(parsed, opts) {
|
|
|
5605
5626
|
};
|
|
5606
5627
|
}
|
|
5607
5628
|
|
|
5629
|
+
// src/server/import/merge.ts
|
|
5630
|
+
function hasContent(lv) {
|
|
5631
|
+
if (!lv) return false;
|
|
5632
|
+
return !!(lv.forms ? lv.forms.other?.trim() : lv.value?.trim());
|
|
5633
|
+
}
|
|
5634
|
+
function formsEqual(a, b) {
|
|
5635
|
+
const ak = Object.keys(a ?? {}).sort();
|
|
5636
|
+
const bk = Object.keys(b ?? {}).sort();
|
|
5637
|
+
if (ak.length !== bk.length || ak.some((k, i) => k !== bk[i])) return false;
|
|
5638
|
+
return ak.every((k) => a[k] === b[k]);
|
|
5639
|
+
}
|
|
5640
|
+
function sameSource(cur, inc, src) {
|
|
5641
|
+
if (!!cur.plural !== !!inc.plural) return false;
|
|
5642
|
+
const c = cur.values[src];
|
|
5643
|
+
const i = inc.values[src];
|
|
5644
|
+
return cur.plural ? formsEqual(c?.forms, i?.forms) : (c?.value ?? "") === (i?.value ?? "");
|
|
5645
|
+
}
|
|
5646
|
+
function applyIncomingSource(cur, inc, src, shapeChanged) {
|
|
5647
|
+
const incSrc = inc.values[src];
|
|
5648
|
+
if (shapeChanged) {
|
|
5649
|
+
if (inc.plural) cur.plural = { arg: inc.plural.arg };
|
|
5650
|
+
else delete cur.plural;
|
|
5651
|
+
cur.values = { [src]: { ...incSrc, state: "source" } };
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
const existing = cur.values[src];
|
|
5655
|
+
if (cur.plural) cur.values[src] = { ...existing, forms: incSrc?.forms, state: "source" };
|
|
5656
|
+
else cur.values[src] = { ...existing, value: incSrc?.value, state: "source" };
|
|
5657
|
+
}
|
|
5658
|
+
function cloneForAdd(inc, allowed) {
|
|
5659
|
+
const entry = structuredClone(inc);
|
|
5660
|
+
for (const loc of Object.keys(entry.values)) {
|
|
5661
|
+
if (!allowed.has(loc)) delete entry.values[loc];
|
|
5662
|
+
}
|
|
5663
|
+
return entry;
|
|
5664
|
+
}
|
|
5665
|
+
function mergeStates(existing, incoming, opts = {}) {
|
|
5666
|
+
const state = structuredClone(existing);
|
|
5667
|
+
const src = state.config.sourceLocale;
|
|
5668
|
+
const targets = state.config.locales.filter((l) => l !== src);
|
|
5669
|
+
const allowed = new Set(state.config.locales);
|
|
5670
|
+
const live = opts.liveKeys ?? new Set(Object.keys(incoming.keys));
|
|
5671
|
+
const plan = { added: [], sourceChanged: [], adopted: [], removed: [], unchanged: 0 };
|
|
5672
|
+
for (const [key, inc] of Object.entries(incoming.keys)) {
|
|
5673
|
+
if (!live.has(key)) continue;
|
|
5674
|
+
const cur = state.keys[key];
|
|
5675
|
+
if (!cur) {
|
|
5676
|
+
const entry = cloneForAdd(inc, allowed);
|
|
5677
|
+
if (!entry.createdAt) entry.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5678
|
+
state.keys[key] = entry;
|
|
5679
|
+
plan.added.push(key);
|
|
5680
|
+
continue;
|
|
5681
|
+
}
|
|
5682
|
+
const shapeChanged = !!cur.plural !== !!inc.plural;
|
|
5683
|
+
const srcChanged = !sameSource(cur, inc, src);
|
|
5684
|
+
if (srcChanged) {
|
|
5685
|
+
applyIncomingSource(cur, inc, src, shapeChanged);
|
|
5686
|
+
plan.sourceChanged.push(key);
|
|
5687
|
+
for (const loc of targets) {
|
|
5688
|
+
const lv = cur.values[loc];
|
|
5689
|
+
if (lv && hasContent(lv)) lv.state = "needs-review";
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
if (inc.placeholders) cur.placeholders = inc.placeholders;
|
|
5693
|
+
else delete cur.placeholders;
|
|
5694
|
+
if (!cur.description && inc.description) cur.description = inc.description;
|
|
5695
|
+
let adoptedHere = false;
|
|
5696
|
+
for (const loc of targets) {
|
|
5697
|
+
const incLv = inc.values[loc];
|
|
5698
|
+
if (!hasContent(incLv)) continue;
|
|
5699
|
+
if (hasContent(cur.values[loc])) continue;
|
|
5700
|
+
cur.values[loc] = { ...structuredClone(incLv), state: "reviewed" };
|
|
5701
|
+
plan.adopted.push({ key, locale: loc });
|
|
5702
|
+
adoptedHere = true;
|
|
5703
|
+
}
|
|
5704
|
+
if (!srcChanged && !adoptedHere) plan.unchanged++;
|
|
5705
|
+
}
|
|
5706
|
+
for (const key of Object.keys(state.keys)) {
|
|
5707
|
+
if (!live.has(key)) {
|
|
5708
|
+
plan.removed.push(key);
|
|
5709
|
+
if (opts.prune) delete state.keys[key];
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
plan.added.sort();
|
|
5713
|
+
plan.sourceChanged.sort();
|
|
5714
|
+
plan.removed.sort();
|
|
5715
|
+
plan.adopted.sort((a, b) => a.key.localeCompare(b.key) || a.locale.localeCompare(b.locale));
|
|
5716
|
+
return { state, plan };
|
|
5717
|
+
}
|
|
5718
|
+
|
|
5608
5719
|
// src/server/import/run.ts
|
|
5609
5720
|
function previewImport(projectRoot, format) {
|
|
5610
5721
|
const det = detect(projectRoot, format);
|
|
@@ -5628,6 +5739,29 @@ function previewImport(projectRoot, format) {
|
|
|
5628
5739
|
sampleKeys
|
|
5629
5740
|
};
|
|
5630
5741
|
}
|
|
5742
|
+
function runSync(opts) {
|
|
5743
|
+
const det = detect(opts.projectRoot, opts.format);
|
|
5744
|
+
if (!det) throw new Error(`No recognized locale files found in ${opts.projectRoot}`);
|
|
5745
|
+
const parser = getParser(det.format);
|
|
5746
|
+
const sourceLocale = opts.sourceLocale ?? det.sourceLocale;
|
|
5747
|
+
const parsed = parser.parse(
|
|
5748
|
+
det.localeRoot,
|
|
5749
|
+
opts.locales ? { locales: opts.locales } : void 0
|
|
5750
|
+
);
|
|
5751
|
+
const sourceParse = parser.parse(det.localeRoot, { locales: [sourceLocale] });
|
|
5752
|
+
const liveKeys = new Set(Object.keys(sourceParse.keys));
|
|
5753
|
+
const assembled = assemble2(parsed, {
|
|
5754
|
+
sourceLocale,
|
|
5755
|
+
format: det.format,
|
|
5756
|
+
cldr: opts.cldr,
|
|
5757
|
+
localeRootRel: relative3(opts.projectRoot, det.localeRoot)
|
|
5758
|
+
});
|
|
5759
|
+
const { warnings, ...rest } = assembled;
|
|
5760
|
+
const incoming = validate(rest);
|
|
5761
|
+
const existing = loadState(opts.statePath);
|
|
5762
|
+
const { state, plan } = mergeStates(existing, incoming, { prune: opts.prune, liveKeys });
|
|
5763
|
+
return { state, plan, warnings, keyCount: Object.keys(state.keys).length };
|
|
5764
|
+
}
|
|
5631
5765
|
function runImport(opts) {
|
|
5632
5766
|
const det = detect(opts.projectRoot, opts.format);
|
|
5633
5767
|
if (!det) throw new Error(`No recognized locale files found in ${opts.projectRoot}`);
|
|
@@ -5652,6 +5786,36 @@ function runImport(opts) {
|
|
|
5652
5786
|
};
|
|
5653
5787
|
}
|
|
5654
5788
|
|
|
5789
|
+
// src/server/import/usage.ts
|
|
5790
|
+
var LOCATION_SCANNED_ADAPTERS = /* @__PURE__ */ new Set(["angular-xliff"]);
|
|
5791
|
+
function isLocationScannedState(state) {
|
|
5792
|
+
return state.config.outputs.some((o) => LOCATION_SCANNED_ADAPTERS.has(o.adapter));
|
|
5793
|
+
}
|
|
5794
|
+
function buildLocationUsageCache(parsed) {
|
|
5795
|
+
const files = {};
|
|
5796
|
+
for (const [key, pk] of Object.entries(parsed.keys)) {
|
|
5797
|
+
for (const loc of pk.locations ?? []) {
|
|
5798
|
+
const file = files[loc.file] ??= { mtime: 0, size: 0, refs: [], prefixes: [] };
|
|
5799
|
+
file.refs.push({ key, line: loc.line, col: 1, scanner: "angular-xliff" });
|
|
5800
|
+
}
|
|
5801
|
+
}
|
|
5802
|
+
return { version: CACHE_VERSION, scannedAt: (/* @__PURE__ */ new Date()).toISOString(), files };
|
|
5803
|
+
}
|
|
5804
|
+
function usageCounts(cache2) {
|
|
5805
|
+
return {
|
|
5806
|
+
files: Object.keys(cache2.files).length,
|
|
5807
|
+
refs: Object.values(cache2.files).reduce((n, f) => n + f.refs.length, 0)
|
|
5808
|
+
};
|
|
5809
|
+
}
|
|
5810
|
+
function refreshLocationUsage(projectRoot, format) {
|
|
5811
|
+
const det = detect(projectRoot, format);
|
|
5812
|
+
if (!det) return null;
|
|
5813
|
+
const parsed = getParser(det.format).parse(det.localeRoot, { locales: [det.sourceLocale] });
|
|
5814
|
+
const cache2 = buildLocationUsageCache(parsed);
|
|
5815
|
+
saveUsageCache(projectRoot, cache2);
|
|
5816
|
+
return cache2;
|
|
5817
|
+
}
|
|
5818
|
+
|
|
5655
5819
|
// src/server/export-run.ts
|
|
5656
5820
|
import { existsSync as existsSync12, readFileSync as readFileSync20, readdirSync as readdirSync13, rmdirSync, statSync as statSync7, unlinkSync } from "fs";
|
|
5657
5821
|
import { dirname as dirname2, resolve as resolve7, sep } from "path";
|
|
@@ -6503,6 +6667,39 @@ function createApi(deps) {
|
|
|
6503
6667
|
console.log(`[import] ${result.keyCount} key(s) across ${result.localeCount} locale(s)${result.warnings.length ? `, ${result.warnings.length} warning(s)` : ""}`);
|
|
6504
6668
|
return c.json({ keyCount: result.keyCount, localeCount: result.localeCount, warnings: result.warnings });
|
|
6505
6669
|
});
|
|
6670
|
+
app.post("/sync", async (c) => {
|
|
6671
|
+
if (Object.keys(load().keys).length === 0) {
|
|
6672
|
+
return c.json({ error: "nothing to sync into; import first" }, 400);
|
|
6673
|
+
}
|
|
6674
|
+
const body = await c.req.json().catch(() => ({}));
|
|
6675
|
+
let result;
|
|
6676
|
+
try {
|
|
6677
|
+
result = runSync({
|
|
6678
|
+
projectRoot,
|
|
6679
|
+
statePath: deps.statePath,
|
|
6680
|
+
format: body.format,
|
|
6681
|
+
sourceLocale: body.sourceLocale,
|
|
6682
|
+
locales: body.locales,
|
|
6683
|
+
cldr: body.cldr,
|
|
6684
|
+
prune: body.prune
|
|
6685
|
+
});
|
|
6686
|
+
} catch (e) {
|
|
6687
|
+
return c.json({ error: e.message }, 400);
|
|
6688
|
+
}
|
|
6689
|
+
if (body.apply !== true) {
|
|
6690
|
+
return c.json({ plan: result.plan, warnings: result.warnings });
|
|
6691
|
+
}
|
|
6692
|
+
persist(result.state);
|
|
6693
|
+
const usageCache = isLocationScannedState(result.state) ? refreshLocationUsage(projectRoot, body.format) : null;
|
|
6694
|
+
const usageRefs = usageCache ? usageCounts(usageCache).refs : void 0;
|
|
6695
|
+
const p = result.plan;
|
|
6696
|
+
logChange({
|
|
6697
|
+
kind: "import",
|
|
6698
|
+
summary: `Synced: +${p.added.length} added, ~${p.sourceChanged.length} changed, -${p.removed.length} removed${body.prune ? " (pruned)" : ""}`
|
|
6699
|
+
});
|
|
6700
|
+
console.log(`[sync] +${p.added.length} ~${p.sourceChanged.length} -${p.removed.length}${body.prune ? " pruned" : ""}`);
|
|
6701
|
+
return c.json({ applied: true, plan: result.plan, warnings: result.warnings, usageRefs });
|
|
6702
|
+
});
|
|
6506
6703
|
app.post("/export", (c) => {
|
|
6507
6704
|
const root = dirname3(resolve9(deps.statePath));
|
|
6508
6705
|
const { written, skipped, deleted, warnings } = exportToDisk(load(), root);
|
|
@@ -6766,12 +6963,11 @@ function createApi(deps) {
|
|
|
6766
6963
|
app.get("/log", (c) => c.json(readLog(projectRoot, 100)));
|
|
6767
6964
|
app.post("/scan", async (c) => {
|
|
6768
6965
|
const s = load();
|
|
6769
|
-
const
|
|
6770
|
-
|
|
6771
|
-
const
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
return c.json({ files: fileCount2, refs: refCount, scannedAt: result.scannedAt });
|
|
6966
|
+
const result = isLocationScannedState(s) ? refreshLocationUsage(projectRoot) : runScan(projectRoot, s.config.scan ?? {}, loadUsageCache(projectRoot));
|
|
6967
|
+
if (!result) return c.json({ files: 0, refs: 0, scannedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6968
|
+
const { files, refs } = usageCounts(result);
|
|
6969
|
+
console.log(`[scan] ${files} file(s), ${refs} reference(s)`);
|
|
6970
|
+
return c.json({ files, refs, scannedAt: result.scannedAt });
|
|
6775
6971
|
});
|
|
6776
6972
|
app.get("/scan", (c) => {
|
|
6777
6973
|
const cache2 = loadUsageCache(projectRoot);
|
|
@@ -7118,11 +7314,16 @@ function backgroundScan(statePath) {
|
|
|
7118
7314
|
const projectRoot = dirname4(resolve10(statePath));
|
|
7119
7315
|
Promise.resolve().then(() => {
|
|
7120
7316
|
const state = loadState(statePath);
|
|
7317
|
+
if (isLocationScannedState(state)) {
|
|
7318
|
+
const cache2 = refreshLocationUsage(projectRoot);
|
|
7319
|
+
const { files: files2, refs: refs2 } = cache2 ? usageCounts(cache2) : { files: 0, refs: 0 };
|
|
7320
|
+
console.log(`[scan] ${files2} file(s), ${refs2} reference(s) (from catalog locations)`);
|
|
7321
|
+
return;
|
|
7322
|
+
}
|
|
7121
7323
|
const existing = loadUsageCache(projectRoot);
|
|
7122
7324
|
const result = runScan(projectRoot, state.config.scan ?? {}, existing);
|
|
7123
|
-
const
|
|
7124
|
-
|
|
7125
|
-
console.log(`[scan] ${fileCount2} file(s), ${refCount} reference(s)`);
|
|
7325
|
+
const { files, refs } = usageCounts(result);
|
|
7326
|
+
console.log(`[scan] ${files} file(s), ${refs} reference(s)`);
|
|
7126
7327
|
}).catch((err) => {
|
|
7127
7328
|
console.warn("[scan] failed:", err instanceof Error ? err.message : String(err));
|
|
7128
7329
|
});
|