glotfile 0.8.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/cli.js +553 -149
- package/dist/server/server.js +19 -5
- package/dist/ui/assets/{index-BgJsS1zp.js → index-B7j9yFsz.js} +319 -6
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +28 -3
- package/skill/references/cli-reference.md +84 -1
- package/skill/references/conventions.md +17 -0
- package/skill/references/schema.md +21 -2
- package/skill/references/workflows.md +61 -16
package/dist/server/cli.js
CHANGED
|
@@ -852,6 +852,153 @@ var init_state = __esm({
|
|
|
852
852
|
}
|
|
853
853
|
});
|
|
854
854
|
|
|
855
|
+
// src/server/stats.ts
|
|
856
|
+
function countWords(text) {
|
|
857
|
+
const t = text.trim();
|
|
858
|
+
return t === "" ? 0 : t.split(/\s+/).length;
|
|
859
|
+
}
|
|
860
|
+
function namespaceOf(key) {
|
|
861
|
+
const i = key.indexOf(".");
|
|
862
|
+
return i === -1 ? "(root)" : key.slice(0, i);
|
|
863
|
+
}
|
|
864
|
+
function pct(n, d) {
|
|
865
|
+
return d === 0 ? 0 : Math.round(n / d * 1e3) / 10;
|
|
866
|
+
}
|
|
867
|
+
function sourceText(entry, sourceLocale) {
|
|
868
|
+
const lv = entry.values[sourceLocale];
|
|
869
|
+
if (!lv) return "";
|
|
870
|
+
return entry.plural ? lv.forms?.other ?? "" : lv.value ?? "";
|
|
871
|
+
}
|
|
872
|
+
function isPresent(entry, locale) {
|
|
873
|
+
const lv = entry.values[locale];
|
|
874
|
+
if (!lv) return false;
|
|
875
|
+
return entry.plural ? (lv.forms?.other ?? "").trim() !== "" : (lv.value ?? "").trim() !== "";
|
|
876
|
+
}
|
|
877
|
+
function classify(entry, locale) {
|
|
878
|
+
if (!isPresent(entry, locale)) return "missing";
|
|
879
|
+
const st = entry.values[locale].state;
|
|
880
|
+
if (st === "reviewed") return "reviewed";
|
|
881
|
+
if (st === "needs-review") return "needsReview";
|
|
882
|
+
return "machine";
|
|
883
|
+
}
|
|
884
|
+
function groupCompletion(state, keys, targets, name) {
|
|
885
|
+
let translated = 0;
|
|
886
|
+
let reviewed = 0;
|
|
887
|
+
for (const k of keys) {
|
|
888
|
+
const entry = state.keys[k];
|
|
889
|
+
for (const locale of targets) {
|
|
890
|
+
const bucket = classify(entry, locale);
|
|
891
|
+
if (bucket !== "missing") translated++;
|
|
892
|
+
if (bucket === "reviewed") reviewed++;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const cells = keys.length * targets.length;
|
|
896
|
+
return { name, total: keys.length, translatedPct: pct(translated, cells), reviewedPct: pct(reviewed, cells) };
|
|
897
|
+
}
|
|
898
|
+
function worstFirst(a, b) {
|
|
899
|
+
return a.translatedPct - b.translatedPct || a.name.localeCompare(b.name);
|
|
900
|
+
}
|
|
901
|
+
function computeStats(state) {
|
|
902
|
+
const { sourceLocale, locales } = state.config;
|
|
903
|
+
const targets = locales.filter((l) => l !== sourceLocale);
|
|
904
|
+
const allKeys = Object.keys(state.keys);
|
|
905
|
+
const expected = allKeys.filter((k) => !state.keys[k].skipTranslate);
|
|
906
|
+
const locales_ = targets.map((locale) => {
|
|
907
|
+
const counts = { reviewed: 0, needsReview: 0, machine: 0, missing: 0 };
|
|
908
|
+
let sourceWords = 0;
|
|
909
|
+
let missingWords = 0;
|
|
910
|
+
for (const k of expected) {
|
|
911
|
+
const entry = state.keys[k];
|
|
912
|
+
const w = countWords(sourceText(entry, sourceLocale));
|
|
913
|
+
sourceWords += w;
|
|
914
|
+
const bucket = classify(entry, locale);
|
|
915
|
+
counts[bucket]++;
|
|
916
|
+
if (bucket === "missing") missingWords += w;
|
|
917
|
+
}
|
|
918
|
+
const total = expected.length;
|
|
919
|
+
const translated = counts.reviewed + counts.needsReview + counts.machine;
|
|
920
|
+
return {
|
|
921
|
+
locale,
|
|
922
|
+
total,
|
|
923
|
+
counts,
|
|
924
|
+
translated,
|
|
925
|
+
reviewed: counts.reviewed,
|
|
926
|
+
translatedPct: pct(translated, total),
|
|
927
|
+
reviewedPct: pct(counts.reviewed, total),
|
|
928
|
+
words: { source: sourceWords, missing: missingWords }
|
|
929
|
+
};
|
|
930
|
+
});
|
|
931
|
+
const cells = expected.length * targets.length;
|
|
932
|
+
let translatedCells = 0;
|
|
933
|
+
let reviewedCells = 0;
|
|
934
|
+
for (const ls of locales_) {
|
|
935
|
+
translatedCells += ls.translated;
|
|
936
|
+
reviewedCells += ls.reviewed;
|
|
937
|
+
}
|
|
938
|
+
const nsMap = /* @__PURE__ */ new Map();
|
|
939
|
+
for (const k of expected) {
|
|
940
|
+
const ns = namespaceOf(k);
|
|
941
|
+
(nsMap.get(ns) ?? nsMap.set(ns, []).get(ns)).push(k);
|
|
942
|
+
}
|
|
943
|
+
const byNamespace = [...nsMap.entries()].map(([name, keys]) => groupCompletion(state, keys, targets, name)).sort(worstFirst);
|
|
944
|
+
const tagMap = /* @__PURE__ */ new Map();
|
|
945
|
+
for (const k of expected) {
|
|
946
|
+
for (const tag of state.keys[k].tags ?? []) {
|
|
947
|
+
(tagMap.get(tag) ?? tagMap.set(tag, []).get(tag)).push(k);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const byTag = [...tagMap.entries()].map(([name, keys]) => groupCompletion(state, keys, targets, name)).sort(worstFirst);
|
|
951
|
+
return {
|
|
952
|
+
totals: {
|
|
953
|
+
keys: allKeys.length,
|
|
954
|
+
locales: targets.length,
|
|
955
|
+
translatedPct: pct(translatedCells, cells),
|
|
956
|
+
reviewedPct: pct(reviewedCells, cells),
|
|
957
|
+
sourceWords: expected.reduce((sum, k) => sum + countWords(sourceText(state.keys[k], sourceLocale)), 0)
|
|
958
|
+
},
|
|
959
|
+
locales: locales_,
|
|
960
|
+
byNamespace,
|
|
961
|
+
byTag
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
var init_stats = __esm({
|
|
965
|
+
"src/server/stats.ts"() {
|
|
966
|
+
"use strict";
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// src/server/cell-state.ts
|
|
971
|
+
function cellState(entry, locale, sourceLocale) {
|
|
972
|
+
const lv = entry.values[locale];
|
|
973
|
+
if (locale === sourceLocale) {
|
|
974
|
+
const has = entry.plural ? !!lv?.forms?.other?.trim() : !!lv?.value?.trim();
|
|
975
|
+
return has ? "source" : "missing";
|
|
976
|
+
}
|
|
977
|
+
const present = entry.plural ? categoriesFor(locale).every((c) => (lv?.forms?.[c] ?? "") !== "") : !!lv?.value;
|
|
978
|
+
if (!present) return "missing";
|
|
979
|
+
const st = lv.state;
|
|
980
|
+
return st === "reviewed" ? "reviewed" : st === "needs-review" ? "needs-review" : "machine";
|
|
981
|
+
}
|
|
982
|
+
var EFFECTIVE_STATES;
|
|
983
|
+
var init_cell_state = __esm({
|
|
984
|
+
"src/server/cell-state.ts"() {
|
|
985
|
+
"use strict";
|
|
986
|
+
init_plurals();
|
|
987
|
+
EFFECTIVE_STATES = ["source", "missing", "machine", "needs-review", "reviewed"];
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
// src/server/glob.ts
|
|
992
|
+
function globToRegExp(glob) {
|
|
993
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
994
|
+
return new RegExp(`^${escaped}$`);
|
|
995
|
+
}
|
|
996
|
+
var init_glob = __esm({
|
|
997
|
+
"src/server/glob.ts"() {
|
|
998
|
+
"use strict";
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
|
|
855
1002
|
// src/server/adapters/options.ts
|
|
856
1003
|
function applyCase(canonical, style) {
|
|
857
1004
|
const sep4 = style === "lower-underscore" || style === "bcp47-underscore" ? "_" : "-";
|
|
@@ -3132,17 +3279,6 @@ var init_glossary = __esm({
|
|
|
3132
3279
|
}
|
|
3133
3280
|
});
|
|
3134
3281
|
|
|
3135
|
-
// src/server/glob.ts
|
|
3136
|
-
function globToRegExp(glob) {
|
|
3137
|
-
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
3138
|
-
return new RegExp(`^${escaped}$`);
|
|
3139
|
-
}
|
|
3140
|
-
var init_glob = __esm({
|
|
3141
|
-
"src/server/glob.ts"() {
|
|
3142
|
-
"use strict";
|
|
3143
|
-
}
|
|
3144
|
-
});
|
|
3145
|
-
|
|
3146
3282
|
// src/server/ai/run.ts
|
|
3147
3283
|
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
3148
3284
|
import { resolve as resolve4, extname } from "path";
|
|
@@ -3150,6 +3286,8 @@ function selectRequests(state, opts) {
|
|
|
3150
3286
|
const targets = (opts.locales ?? state.config.locales).filter((l) => l !== state.config.sourceLocale);
|
|
3151
3287
|
const keyRe = opts.keyGlob ? globToRegExp(opts.keyGlob) : null;
|
|
3152
3288
|
const keySet = opts.keys ? new Set(opts.keys) : null;
|
|
3289
|
+
const stateSet = opts.states ? new Set(opts.states) : null;
|
|
3290
|
+
const skip = (st) => stateSet ? !stateSet.has(st) : !!opts.onlyMissing && st !== "missing";
|
|
3153
3291
|
const reqs = [];
|
|
3154
3292
|
let id = 0;
|
|
3155
3293
|
for (const key of Object.keys(state.keys).sort()) {
|
|
@@ -3163,9 +3301,7 @@ function selectRequests(state, opts) {
|
|
|
3163
3301
|
const other = sourceForms?.other;
|
|
3164
3302
|
if (!sourceForms || !other) continue;
|
|
3165
3303
|
for (const locale of targets) {
|
|
3166
|
-
|
|
3167
|
-
const complete = categoriesFor(locale).every((c) => (have[c] ?? "") !== "");
|
|
3168
|
-
if (opts.onlyMissing && complete) continue;
|
|
3304
|
+
if (skip(cellState(entry, locale, state.config.sourceLocale))) continue;
|
|
3169
3305
|
const glossary = relevantGlossary(other, locale, state.glossary);
|
|
3170
3306
|
const literals = quotedLiterals(other);
|
|
3171
3307
|
reqs.push({
|
|
@@ -3187,8 +3323,7 @@ function selectRequests(state, opts) {
|
|
|
3187
3323
|
const source = sourceLv?.value;
|
|
3188
3324
|
if (!source) continue;
|
|
3189
3325
|
for (const locale of targets) {
|
|
3190
|
-
|
|
3191
|
-
if (opts.onlyMissing && existing) continue;
|
|
3326
|
+
if (skip(cellState(entry, locale, state.config.sourceLocale))) continue;
|
|
3192
3327
|
const glossary = relevantGlossary(source, locale, state.glossary);
|
|
3193
3328
|
const literals = quotedLiterals(source);
|
|
3194
3329
|
reqs.push({
|
|
@@ -3349,6 +3484,7 @@ var init_run = __esm({
|
|
|
3349
3484
|
init_placeholders();
|
|
3350
3485
|
init_plurals();
|
|
3351
3486
|
init_state();
|
|
3487
|
+
init_cell_state();
|
|
3352
3488
|
init_glob();
|
|
3353
3489
|
init_batch();
|
|
3354
3490
|
MEDIA_TYPES = {
|
|
@@ -6601,121 +6737,6 @@ var init_run3 = __esm({
|
|
|
6601
6737
|
}
|
|
6602
6738
|
});
|
|
6603
6739
|
|
|
6604
|
-
// src/server/stats.ts
|
|
6605
|
-
function countWords(text) {
|
|
6606
|
-
const t = text.trim();
|
|
6607
|
-
return t === "" ? 0 : t.split(/\s+/).length;
|
|
6608
|
-
}
|
|
6609
|
-
function namespaceOf(key) {
|
|
6610
|
-
const i = key.indexOf(".");
|
|
6611
|
-
return i === -1 ? "(root)" : key.slice(0, i);
|
|
6612
|
-
}
|
|
6613
|
-
function pct(n, d) {
|
|
6614
|
-
return d === 0 ? 0 : Math.round(n / d * 1e3) / 10;
|
|
6615
|
-
}
|
|
6616
|
-
function sourceText(entry, sourceLocale) {
|
|
6617
|
-
const lv = entry.values[sourceLocale];
|
|
6618
|
-
if (!lv) return "";
|
|
6619
|
-
return entry.plural ? lv.forms?.other ?? "" : lv.value ?? "";
|
|
6620
|
-
}
|
|
6621
|
-
function isPresent(entry, locale) {
|
|
6622
|
-
const lv = entry.values[locale];
|
|
6623
|
-
if (!lv) return false;
|
|
6624
|
-
return entry.plural ? (lv.forms?.other ?? "").trim() !== "" : (lv.value ?? "").trim() !== "";
|
|
6625
|
-
}
|
|
6626
|
-
function classify(entry, locale) {
|
|
6627
|
-
if (!isPresent(entry, locale)) return "missing";
|
|
6628
|
-
const st = entry.values[locale].state;
|
|
6629
|
-
if (st === "reviewed") return "reviewed";
|
|
6630
|
-
if (st === "needs-review") return "needsReview";
|
|
6631
|
-
return "machine";
|
|
6632
|
-
}
|
|
6633
|
-
function groupCompletion(state, keys, targets, name) {
|
|
6634
|
-
let translated = 0;
|
|
6635
|
-
let reviewed = 0;
|
|
6636
|
-
for (const k of keys) {
|
|
6637
|
-
const entry = state.keys[k];
|
|
6638
|
-
for (const locale of targets) {
|
|
6639
|
-
const bucket = classify(entry, locale);
|
|
6640
|
-
if (bucket !== "missing") translated++;
|
|
6641
|
-
if (bucket === "reviewed") reviewed++;
|
|
6642
|
-
}
|
|
6643
|
-
}
|
|
6644
|
-
const cells = keys.length * targets.length;
|
|
6645
|
-
return { name, total: keys.length, translatedPct: pct(translated, cells), reviewedPct: pct(reviewed, cells) };
|
|
6646
|
-
}
|
|
6647
|
-
function worstFirst(a, b) {
|
|
6648
|
-
return a.translatedPct - b.translatedPct || a.name.localeCompare(b.name);
|
|
6649
|
-
}
|
|
6650
|
-
function computeStats(state) {
|
|
6651
|
-
const { sourceLocale, locales } = state.config;
|
|
6652
|
-
const targets = locales.filter((l) => l !== sourceLocale);
|
|
6653
|
-
const allKeys = Object.keys(state.keys);
|
|
6654
|
-
const expected = allKeys.filter((k) => !state.keys[k].skipTranslate);
|
|
6655
|
-
const locales_ = targets.map((locale) => {
|
|
6656
|
-
const counts = { reviewed: 0, needsReview: 0, machine: 0, missing: 0 };
|
|
6657
|
-
let sourceWords = 0;
|
|
6658
|
-
let missingWords = 0;
|
|
6659
|
-
for (const k of expected) {
|
|
6660
|
-
const entry = state.keys[k];
|
|
6661
|
-
const w = countWords(sourceText(entry, sourceLocale));
|
|
6662
|
-
sourceWords += w;
|
|
6663
|
-
const bucket = classify(entry, locale);
|
|
6664
|
-
counts[bucket]++;
|
|
6665
|
-
if (bucket === "missing") missingWords += w;
|
|
6666
|
-
}
|
|
6667
|
-
const total = expected.length;
|
|
6668
|
-
const translated = counts.reviewed + counts.needsReview + counts.machine;
|
|
6669
|
-
return {
|
|
6670
|
-
locale,
|
|
6671
|
-
total,
|
|
6672
|
-
counts,
|
|
6673
|
-
translated,
|
|
6674
|
-
reviewed: counts.reviewed,
|
|
6675
|
-
translatedPct: pct(translated, total),
|
|
6676
|
-
reviewedPct: pct(counts.reviewed, total),
|
|
6677
|
-
words: { source: sourceWords, missing: missingWords }
|
|
6678
|
-
};
|
|
6679
|
-
});
|
|
6680
|
-
const cells = expected.length * targets.length;
|
|
6681
|
-
let translatedCells = 0;
|
|
6682
|
-
let reviewedCells = 0;
|
|
6683
|
-
for (const ls of locales_) {
|
|
6684
|
-
translatedCells += ls.translated;
|
|
6685
|
-
reviewedCells += ls.reviewed;
|
|
6686
|
-
}
|
|
6687
|
-
const nsMap = /* @__PURE__ */ new Map();
|
|
6688
|
-
for (const k of expected) {
|
|
6689
|
-
const ns = namespaceOf(k);
|
|
6690
|
-
(nsMap.get(ns) ?? nsMap.set(ns, []).get(ns)).push(k);
|
|
6691
|
-
}
|
|
6692
|
-
const byNamespace = [...nsMap.entries()].map(([name, keys]) => groupCompletion(state, keys, targets, name)).sort(worstFirst);
|
|
6693
|
-
const tagMap = /* @__PURE__ */ new Map();
|
|
6694
|
-
for (const k of expected) {
|
|
6695
|
-
for (const tag of state.keys[k].tags ?? []) {
|
|
6696
|
-
(tagMap.get(tag) ?? tagMap.set(tag, []).get(tag)).push(k);
|
|
6697
|
-
}
|
|
6698
|
-
}
|
|
6699
|
-
const byTag = [...tagMap.entries()].map(([name, keys]) => groupCompletion(state, keys, targets, name)).sort(worstFirst);
|
|
6700
|
-
return {
|
|
6701
|
-
totals: {
|
|
6702
|
-
keys: allKeys.length,
|
|
6703
|
-
locales: targets.length,
|
|
6704
|
-
translatedPct: pct(translatedCells, cells),
|
|
6705
|
-
reviewedPct: pct(reviewedCells, cells),
|
|
6706
|
-
sourceWords: expected.reduce((sum, k) => sum + countWords(sourceText(state.keys[k], sourceLocale)), 0)
|
|
6707
|
-
},
|
|
6708
|
-
locales: locales_,
|
|
6709
|
-
byNamespace,
|
|
6710
|
-
byTag
|
|
6711
|
-
};
|
|
6712
|
-
}
|
|
6713
|
-
var init_stats = __esm({
|
|
6714
|
-
"src/server/stats.ts"() {
|
|
6715
|
-
"use strict";
|
|
6716
|
-
}
|
|
6717
|
-
});
|
|
6718
|
-
|
|
6719
6740
|
// src/server/checks.ts
|
|
6720
6741
|
function runChecks(state, opts = {}) {
|
|
6721
6742
|
const ruleOff = (id) => state.config.lint?.rules?.[CHECK_RULE[id]] === "off";
|
|
@@ -8406,6 +8427,129 @@ var init_server = __esm({
|
|
|
8406
8427
|
|
|
8407
8428
|
// src/server/cli.ts
|
|
8408
8429
|
init_state();
|
|
8430
|
+
init_stats();
|
|
8431
|
+
import { resolve as resolve11, dirname as dirname5, join as join19, basename as basename2 } from "path";
|
|
8432
|
+
import { readFileSync as readFileSync24, existsSync as existsSync14, mkdirSync as mkdirSync6, cpSync } from "fs";
|
|
8433
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8434
|
+
|
|
8435
|
+
// src/server/agent-cli.ts
|
|
8436
|
+
init_state();
|
|
8437
|
+
init_cell_state();
|
|
8438
|
+
init_glob();
|
|
8439
|
+
function projectCell(cell, fields) {
|
|
8440
|
+
const out = {};
|
|
8441
|
+
for (const f of fields) {
|
|
8442
|
+
if (f === "value" && cell.value !== void 0) out.value = cell.value;
|
|
8443
|
+
else if (f === "value" && cell.forms !== void 0) out.forms = cell.forms;
|
|
8444
|
+
else if (f === "state") out.state = cell.state;
|
|
8445
|
+
else if (f === "updatedAt" && cell.updatedAt !== void 0) out.updatedAt = cell.updatedAt;
|
|
8446
|
+
}
|
|
8447
|
+
return out;
|
|
8448
|
+
}
|
|
8449
|
+
function runGet(state, opts) {
|
|
8450
|
+
const { sourceLocale } = state.config;
|
|
8451
|
+
const shown = (opts.locales?.length ? opts.locales : state.config.locales).map(canonLocale);
|
|
8452
|
+
const targetsShown = shown.filter((l) => l !== sourceLocale);
|
|
8453
|
+
const res = opts.keyGlobs?.length ? opts.keyGlobs.map(globToRegExp) : null;
|
|
8454
|
+
const stateSet = opts.states?.length ? new Set(opts.states) : null;
|
|
8455
|
+
const fields = opts.fields?.length ? opts.fields : ["value", "state"];
|
|
8456
|
+
const fullEntry = fields.includes("all");
|
|
8457
|
+
const keys = [];
|
|
8458
|
+
const json = {};
|
|
8459
|
+
const ndjson = [];
|
|
8460
|
+
for (const key of Object.keys(state.keys).sort()) {
|
|
8461
|
+
if (res && !res.some((re) => re.test(key))) continue;
|
|
8462
|
+
const entry = state.keys[key];
|
|
8463
|
+
if (stateSet && !targetsShown.some((l) => stateSet.has(cellState(entry, l, sourceLocale)))) continue;
|
|
8464
|
+
keys.push(key);
|
|
8465
|
+
if (fullEntry) {
|
|
8466
|
+
const values = {};
|
|
8467
|
+
for (const l of shown) if (entry.values[l]) values[l] = entry.values[l];
|
|
8468
|
+
json[key] = { ...entry, values };
|
|
8469
|
+
ndjson.push({ key, ...entry, values });
|
|
8470
|
+
continue;
|
|
8471
|
+
}
|
|
8472
|
+
const cells = {};
|
|
8473
|
+
for (const locale of shown) {
|
|
8474
|
+
const st = cellState(entry, locale, sourceLocale);
|
|
8475
|
+
if (stateSet && locale !== sourceLocale && !stateSet.has(st)) continue;
|
|
8476
|
+
const lv = entry.values[locale];
|
|
8477
|
+
const cell = entry.plural ? { forms: lv?.forms ?? {}, state: st, updatedAt: lv?.updatedAt } : { value: lv?.value ?? "", state: st, updatedAt: lv?.updatedAt };
|
|
8478
|
+
const projected = projectCell(cell, fields);
|
|
8479
|
+
cells[locale] = projected;
|
|
8480
|
+
ndjson.push({ key, locale, ...projected });
|
|
8481
|
+
}
|
|
8482
|
+
json[key] = cells;
|
|
8483
|
+
}
|
|
8484
|
+
return { keys, json, ndjson };
|
|
8485
|
+
}
|
|
8486
|
+
function applyOne(state, op, clock) {
|
|
8487
|
+
switch (op.op) {
|
|
8488
|
+
case "create":
|
|
8489
|
+
createKey(state, op.key, op.value, clock);
|
|
8490
|
+
return;
|
|
8491
|
+
case "set-source":
|
|
8492
|
+
setSourceValue(state, op.key, op.value);
|
|
8493
|
+
return;
|
|
8494
|
+
case "set-target":
|
|
8495
|
+
setTargetValue(state, op.key, op.locale, op.value, clock);
|
|
8496
|
+
if (op.state && op.state !== "reviewed") setKeyState(state, op.key, op.locale, op.state);
|
|
8497
|
+
return;
|
|
8498
|
+
case "set-source-forms":
|
|
8499
|
+
setSourcePluralForms(state, op.key, op.forms);
|
|
8500
|
+
return;
|
|
8501
|
+
case "set-forms":
|
|
8502
|
+
setPluralForms(state, op.key, op.locale, op.forms, clock);
|
|
8503
|
+
if (op.state && op.state !== "reviewed") setKeyState(state, op.key, op.locale, op.state);
|
|
8504
|
+
return;
|
|
8505
|
+
case "set-state":
|
|
8506
|
+
setKeyState(state, op.key, op.locale, op.state);
|
|
8507
|
+
return;
|
|
8508
|
+
case "clear":
|
|
8509
|
+
clearValue(state, op.key, op.locale);
|
|
8510
|
+
return;
|
|
8511
|
+
default:
|
|
8512
|
+
throw new Error(`Unknown op: ${op.op ?? "(missing)"}`);
|
|
8513
|
+
}
|
|
8514
|
+
}
|
|
8515
|
+
function parseOps(raw) {
|
|
8516
|
+
let data;
|
|
8517
|
+
try {
|
|
8518
|
+
data = JSON.parse(raw);
|
|
8519
|
+
} catch (e) {
|
|
8520
|
+
throw new Error(`apply expects a JSON array of operations on stdin: ${e.message}`);
|
|
8521
|
+
}
|
|
8522
|
+
if (!Array.isArray(data)) throw new Error("apply expects a JSON array of operations on stdin");
|
|
8523
|
+
return data.map((o, i) => {
|
|
8524
|
+
if (!o || typeof o !== "object" || typeof o.op !== "string") {
|
|
8525
|
+
throw new Error(`operation ${i} is not an { "op": ... } object`);
|
|
8526
|
+
}
|
|
8527
|
+
return o;
|
|
8528
|
+
});
|
|
8529
|
+
}
|
|
8530
|
+
function applyOps(state, ops, opts = {}) {
|
|
8531
|
+
const clock = opts.clock ?? systemClock;
|
|
8532
|
+
const touched = /* @__PURE__ */ new Set();
|
|
8533
|
+
const errors = [];
|
|
8534
|
+
let applied = 0;
|
|
8535
|
+
for (let i = 0; i < ops.length; i++) {
|
|
8536
|
+
const op = ops[i];
|
|
8537
|
+
try {
|
|
8538
|
+
applyOne(state, op, clock);
|
|
8539
|
+
applied++;
|
|
8540
|
+
if (op.key) touched.add(op.key);
|
|
8541
|
+
} catch (e) {
|
|
8542
|
+
errors.push({ index: i, op: op.op, key: op.key, error: e instanceof Error ? e.message : String(e) });
|
|
8543
|
+
if (!opts.continueOnError) break;
|
|
8544
|
+
}
|
|
8545
|
+
}
|
|
8546
|
+
return { applied, keysTouched: [...touched].sort(), errors };
|
|
8547
|
+
}
|
|
8548
|
+
|
|
8549
|
+
// src/server/cli.ts
|
|
8550
|
+
init_cell_state();
|
|
8551
|
+
init_glob();
|
|
8552
|
+
init_schema();
|
|
8409
8553
|
init_export_run();
|
|
8410
8554
|
init_storage();
|
|
8411
8555
|
init_ai();
|
|
@@ -8425,9 +8569,6 @@ init_usage();
|
|
|
8425
8569
|
init_context();
|
|
8426
8570
|
init_run2();
|
|
8427
8571
|
init_outputs();
|
|
8428
|
-
import { resolve as resolve11, dirname as dirname5, join as join19, basename as basename2 } from "path";
|
|
8429
|
-
import { readFileSync as readFileSync24, existsSync as existsSync14, mkdirSync as mkdirSync6, cpSync } from "fs";
|
|
8430
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8431
8572
|
|
|
8432
8573
|
// src/server/lint/locate.ts
|
|
8433
8574
|
function locate(rawText, key) {
|
|
@@ -8503,7 +8644,7 @@ function formatSarif(report, ctx) {
|
|
|
8503
8644
|
}
|
|
8504
8645
|
|
|
8505
8646
|
// src/server/cli.ts
|
|
8506
|
-
var COMMANDS = ["serve", "export", "translate", "lint", "check", "import", "sync", "build-context", "scan", "prune", "split", "skill", "batch"];
|
|
8647
|
+
var COMMANDS = ["serve", "export", "translate", "lint", "check", "import", "sync", "build-context", "scan", "prune", "split", "skill", "batch", "get", "stats", "set", "set-state", "clear", "apply"];
|
|
8507
8648
|
var isCommand = (s) => s != null && COMMANDS.includes(s);
|
|
8508
8649
|
function parseArgs(argv) {
|
|
8509
8650
|
const statePath = resolve11(process.cwd(), "glotfile.json");
|
|
@@ -8579,9 +8720,21 @@ function parseArgs(argv) {
|
|
|
8579
8720
|
else if (flag === "--batch") args.batch = true;
|
|
8580
8721
|
else if (flag === "--wait") args.wait = true;
|
|
8581
8722
|
else if (flag === "--print") args.print = true;
|
|
8723
|
+
else if (flag === "--state" && next) {
|
|
8724
|
+
args.states = next.split(",");
|
|
8725
|
+
i++;
|
|
8726
|
+
} else if (flag === "--fields" && next) {
|
|
8727
|
+
args.fields = next.split(",");
|
|
8728
|
+
i++;
|
|
8729
|
+
} else if (flag === "--keys-only") args.keysOnly = true;
|
|
8730
|
+
else if (flag === "--value" && next) {
|
|
8731
|
+
args.value = next;
|
|
8732
|
+
i++;
|
|
8733
|
+
} else if (flag === "--create") args.create = true;
|
|
8734
|
+
else if (flag === "--continue-on-error") args.continueOnError = true;
|
|
8582
8735
|
else if (args.command === "batch" && (flag === "status" || flag === "apply" || flag === "cancel")) {
|
|
8583
8736
|
args.batchAction = flag;
|
|
8584
|
-
}
|
|
8737
|
+
} else if (!flag.startsWith("-")) (args.positionals ??= []).push(flag);
|
|
8585
8738
|
}
|
|
8586
8739
|
return args;
|
|
8587
8740
|
}
|
|
@@ -8641,16 +8794,42 @@ function makeProviderOrExit(ai) {
|
|
|
8641
8794
|
return null;
|
|
8642
8795
|
}
|
|
8643
8796
|
}
|
|
8797
|
+
function parseStates(args, allowSource) {
|
|
8798
|
+
if (!args.states?.length) return void 0;
|
|
8799
|
+
const allowed = allowSource ? EFFECTIVE_STATES : EFFECTIVE_STATES.filter((s) => s !== "source");
|
|
8800
|
+
for (const s of args.states) {
|
|
8801
|
+
if (!allowed.includes(s)) {
|
|
8802
|
+
console.error(`Unknown --state '${s}'. Expected one of: ${allowed.join(", ")}.`);
|
|
8803
|
+
process.exit(1);
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
return args.states;
|
|
8807
|
+
}
|
|
8808
|
+
function translateSelection(args) {
|
|
8809
|
+
const states = parseStates(args, false);
|
|
8810
|
+
return {
|
|
8811
|
+
locales: args.locales,
|
|
8812
|
+
keyGlob: args.keyGlob,
|
|
8813
|
+
...states ? { states } : { onlyMissing: args.all ? false : args.onlyMissing ?? true }
|
|
8814
|
+
};
|
|
8815
|
+
}
|
|
8816
|
+
function readStdin() {
|
|
8817
|
+
try {
|
|
8818
|
+
return readFileSync24(0, "utf8");
|
|
8819
|
+
} catch {
|
|
8820
|
+
return "";
|
|
8821
|
+
}
|
|
8822
|
+
}
|
|
8823
|
+
function matchKeys(state, glob) {
|
|
8824
|
+
const re = globToRegExp(glob);
|
|
8825
|
+
return Object.keys(state.keys).filter((k) => re.test(k)).sort();
|
|
8826
|
+
}
|
|
8644
8827
|
async function runTranslate(args) {
|
|
8645
8828
|
const state = loadState(args.statePath);
|
|
8646
8829
|
const projectRoot = dirname5(resolve11(args.statePath));
|
|
8647
8830
|
if (args.estimate) {
|
|
8648
8831
|
const ai = loadLocalSettings(projectRoot).ai;
|
|
8649
|
-
const est = estimateTranslation(state, ai,
|
|
8650
|
-
onlyMissing: args.all ? false : args.onlyMissing ?? true,
|
|
8651
|
-
locales: args.locales,
|
|
8652
|
-
keyGlob: args.keyGlob
|
|
8653
|
-
});
|
|
8832
|
+
const est = estimateTranslation(state, ai, translateSelection(args));
|
|
8654
8833
|
if (!est.requests) {
|
|
8655
8834
|
console.log("Nothing to translate.");
|
|
8656
8835
|
return;
|
|
@@ -8669,13 +8848,7 @@ async function runTranslate(args) {
|
|
|
8669
8848
|
}
|
|
8670
8849
|
return;
|
|
8671
8850
|
}
|
|
8672
|
-
const reqs = selectRequests(state,
|
|
8673
|
-
// Default to translating only empty values; --all forces a full re-translate
|
|
8674
|
-
// (overwriting existing translations). --only missing stays as a no-op alias.
|
|
8675
|
-
onlyMissing: args.all ? false : args.onlyMissing ?? true,
|
|
8676
|
-
locales: args.locales,
|
|
8677
|
-
keyGlob: args.keyGlob
|
|
8678
|
-
});
|
|
8851
|
+
const reqs = selectRequests(state, translateSelection(args));
|
|
8679
8852
|
const toTranslate = [...reqs];
|
|
8680
8853
|
if (args.batch) {
|
|
8681
8854
|
if (!toTranslate.length) {
|
|
@@ -9235,6 +9408,172 @@ function runSkill(args) {
|
|
|
9235
9408
|
cpSync(SKILL_SRC, dest, { recursive: true });
|
|
9236
9409
|
console.log(`Installed the glotfile skill to ${dest}. Restart Claude Code to pick it up.`);
|
|
9237
9410
|
}
|
|
9411
|
+
function runGetCmd(args) {
|
|
9412
|
+
const state = loadState(args.statePath);
|
|
9413
|
+
const keyGlobs = [...args.positionals ?? [], ...args.keyGlob ? [args.keyGlob] : []];
|
|
9414
|
+
const out = runGet(state, {
|
|
9415
|
+
keyGlobs: keyGlobs.length ? keyGlobs : void 0,
|
|
9416
|
+
locales: args.locales,
|
|
9417
|
+
states: parseStates(args, true),
|
|
9418
|
+
fields: args.fields
|
|
9419
|
+
});
|
|
9420
|
+
if (args.keysOnly) {
|
|
9421
|
+
for (const k of out.keys) console.log(k);
|
|
9422
|
+
return;
|
|
9423
|
+
}
|
|
9424
|
+
if (args.format === "ndjson") {
|
|
9425
|
+
for (const row of out.ndjson) console.log(JSON.stringify(row));
|
|
9426
|
+
return;
|
|
9427
|
+
}
|
|
9428
|
+
console.log(JSON.stringify(out.json, null, 2));
|
|
9429
|
+
}
|
|
9430
|
+
function runStatsCmd(args) {
|
|
9431
|
+
const state = loadState(args.statePath);
|
|
9432
|
+
const stats = computeStats(state);
|
|
9433
|
+
let locales = stats.locales;
|
|
9434
|
+
if (args.locales?.length) {
|
|
9435
|
+
const want = new Set(args.locales.map(canonLocale));
|
|
9436
|
+
locales = locales.filter((l) => want.has(l.locale));
|
|
9437
|
+
}
|
|
9438
|
+
if (args.format === "text") {
|
|
9439
|
+
console.log(`${stats.totals.keys} key(s) \xB7 ${stats.totals.locales} target locale(s) \xB7 ${stats.totals.translatedPct}% translated, ${stats.totals.reviewedPct}% reviewed`);
|
|
9440
|
+
for (const l of locales) {
|
|
9441
|
+
const c = l.counts;
|
|
9442
|
+
console.log(` ${l.locale.padEnd(8)} ${String(l.translatedPct).padStart(5)}% translated (reviewed ${c.reviewed}, machine ${c.machine}, needs-review ${c.needsReview}, missing ${c.missing})`);
|
|
9443
|
+
}
|
|
9444
|
+
return;
|
|
9445
|
+
}
|
|
9446
|
+
console.log(JSON.stringify({ totals: stats.totals, locales }, null, 2));
|
|
9447
|
+
}
|
|
9448
|
+
function countNeedsReview(state, key) {
|
|
9449
|
+
const entry = state.keys[key];
|
|
9450
|
+
if (!entry) return 0;
|
|
9451
|
+
let n = 0;
|
|
9452
|
+
for (const [loc, lv] of Object.entries(entry.values)) {
|
|
9453
|
+
if (loc !== state.config.sourceLocale && lv.state === "needs-review") n++;
|
|
9454
|
+
}
|
|
9455
|
+
return n;
|
|
9456
|
+
}
|
|
9457
|
+
function runSet(args) {
|
|
9458
|
+
const pos = args.positionals ?? [];
|
|
9459
|
+
const key = pos[0];
|
|
9460
|
+
if (!key) {
|
|
9461
|
+
console.error("Usage: glotfile set <key> [value] [--locale <code>] [--state <state>] [--create]");
|
|
9462
|
+
process.exitCode = 1;
|
|
9463
|
+
return;
|
|
9464
|
+
}
|
|
9465
|
+
let value = args.value ?? pos[1];
|
|
9466
|
+
if (value === void 0) {
|
|
9467
|
+
const piped = readStdin();
|
|
9468
|
+
value = piped.length ? piped.replace(/\r?\n$/, "") : void 0;
|
|
9469
|
+
}
|
|
9470
|
+
if (value === void 0) {
|
|
9471
|
+
console.error('set requires a value (positional, --value, or piped on stdin). Use --value "" to set an empty value.');
|
|
9472
|
+
process.exitCode = 1;
|
|
9473
|
+
return;
|
|
9474
|
+
}
|
|
9475
|
+
const state = loadState(args.statePath);
|
|
9476
|
+
const sl = state.config.sourceLocale;
|
|
9477
|
+
const locale = args.locales?.[0] ? canonLocale(args.locales[0]) : sl;
|
|
9478
|
+
try {
|
|
9479
|
+
if (locale === sl) {
|
|
9480
|
+
if (args.create && !state.keys[key]) createKey(state, key, value);
|
|
9481
|
+
const before = countNeedsReview(state, key);
|
|
9482
|
+
setSourceValue(state, key, value);
|
|
9483
|
+
saveState(args.statePath, state);
|
|
9484
|
+
const flipped = countNeedsReview(state, key) - before;
|
|
9485
|
+
console.log(`set ${key} (${sl})${flipped > 0 ? ` \u2014 ${flipped} translation(s) now need re-translation (run \`glotfile translate --state needs-review\`)` : ""}`);
|
|
9486
|
+
} else {
|
|
9487
|
+
setTargetValue(state, key, locale, value);
|
|
9488
|
+
const override = args.states?.[0];
|
|
9489
|
+
if (override && override !== "reviewed") setKeyState(state, key, locale, override);
|
|
9490
|
+
saveState(args.statePath, state);
|
|
9491
|
+
console.log(`set ${key} (${locale})`);
|
|
9492
|
+
}
|
|
9493
|
+
} catch (e) {
|
|
9494
|
+
console.error(e.message);
|
|
9495
|
+
process.exitCode = 1;
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
9498
|
+
function runSetStateCmd(args) {
|
|
9499
|
+
const pos = args.positionals ?? [];
|
|
9500
|
+
const sel = args.keyGlob ?? pos[0];
|
|
9501
|
+
const stateName = args.keyGlob ? pos[0] : pos[1];
|
|
9502
|
+
if (!sel || !stateName) {
|
|
9503
|
+
console.error("Usage: glotfile set-state <key|glob> <state> [--locale <list>] (state: machine | needs-review | reviewed)");
|
|
9504
|
+
process.exitCode = 1;
|
|
9505
|
+
return;
|
|
9506
|
+
}
|
|
9507
|
+
if (!STATES.includes(stateName)) {
|
|
9508
|
+
console.error(`Unknown state '${stateName}'. Expected one of: ${STATES.join(", ")}.`);
|
|
9509
|
+
process.exitCode = 1;
|
|
9510
|
+
return;
|
|
9511
|
+
}
|
|
9512
|
+
const state = loadState(args.statePath);
|
|
9513
|
+
const sl = state.config.sourceLocale;
|
|
9514
|
+
const locales = (args.locales?.length ? args.locales : state.config.locales.filter((l) => l !== sl)).map(canonLocale);
|
|
9515
|
+
const keys = matchKeys(state, sel);
|
|
9516
|
+
let n = 0;
|
|
9517
|
+
for (const key of keys) {
|
|
9518
|
+
for (const loc of locales) {
|
|
9519
|
+
if (state.keys[key].values[loc]) {
|
|
9520
|
+
setKeyState(state, key, loc, stateName);
|
|
9521
|
+
n++;
|
|
9522
|
+
}
|
|
9523
|
+
}
|
|
9524
|
+
}
|
|
9525
|
+
saveState(args.statePath, state);
|
|
9526
|
+
console.log(`Set ${n} cell(s) to ${stateName} across ${keys.length} key(s).`);
|
|
9527
|
+
}
|
|
9528
|
+
function runClearCmd(args) {
|
|
9529
|
+
const sel = args.keyGlob ?? args.positionals?.[0];
|
|
9530
|
+
if (!sel) {
|
|
9531
|
+
console.error("Usage: glotfile clear <key|glob> --locale <list>");
|
|
9532
|
+
process.exitCode = 1;
|
|
9533
|
+
return;
|
|
9534
|
+
}
|
|
9535
|
+
if (!args.locales?.length) {
|
|
9536
|
+
console.error("clear requires --locale <list> (the locale(s) to empty).");
|
|
9537
|
+
process.exitCode = 1;
|
|
9538
|
+
return;
|
|
9539
|
+
}
|
|
9540
|
+
const state = loadState(args.statePath);
|
|
9541
|
+
const sl = state.config.sourceLocale;
|
|
9542
|
+
const locales = args.locales.map(canonLocale);
|
|
9543
|
+
if (locales.includes(sl)) {
|
|
9544
|
+
console.error(`Cannot clear the source locale (${sl}); edit it with \`glotfile set\` instead.`);
|
|
9545
|
+
process.exitCode = 1;
|
|
9546
|
+
return;
|
|
9547
|
+
}
|
|
9548
|
+
const keys = matchKeys(state, sel);
|
|
9549
|
+
let n = 0;
|
|
9550
|
+
for (const key of keys) {
|
|
9551
|
+
for (const loc of locales) {
|
|
9552
|
+
if (state.keys[key].values[loc]) {
|
|
9553
|
+
clearValue(state, key, loc);
|
|
9554
|
+
n++;
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9557
|
+
}
|
|
9558
|
+
saveState(args.statePath, state);
|
|
9559
|
+
console.log(`Cleared ${n} value(s) \u2192 untranslated.`);
|
|
9560
|
+
}
|
|
9561
|
+
function runApply(args) {
|
|
9562
|
+
let ops;
|
|
9563
|
+
try {
|
|
9564
|
+
ops = parseOps(readStdin());
|
|
9565
|
+
} catch (e) {
|
|
9566
|
+
console.error(e.message);
|
|
9567
|
+
process.exitCode = 1;
|
|
9568
|
+
return;
|
|
9569
|
+
}
|
|
9570
|
+
const state = loadState(args.statePath);
|
|
9571
|
+
const r = applyOps(state, ops, { continueOnError: args.continueOnError });
|
|
9572
|
+
const saved = (args.continueOnError || r.errors.length === 0) && !args.dryRun;
|
|
9573
|
+
if (saved) saveState(args.statePath, state);
|
|
9574
|
+
console.log(JSON.stringify({ applied: r.applied, keysTouched: r.keysTouched, saved, dryRun: !!args.dryRun, errors: r.errors }, null, 2));
|
|
9575
|
+
if (r.errors.length) process.exitCode = 1;
|
|
9576
|
+
}
|
|
9238
9577
|
var GLOBAL_OPTS = [
|
|
9239
9578
|
["-f, --file <path>", "State file to use (default: ./glotfile.json)"],
|
|
9240
9579
|
["-h, --help", "Show this help"]
|
|
@@ -9258,9 +9597,10 @@ var COMMAND_HELP = {
|
|
|
9258
9597
|
},
|
|
9259
9598
|
translate: {
|
|
9260
9599
|
summary: "AI-translate missing strings into your target locales (writes back to the state file).",
|
|
9261
|
-
usage: "glotfile translate [--all] [--estimate] [--locale <list>] [--key <glob>]",
|
|
9600
|
+
usage: "glotfile translate [--all] [--state <list>] [--estimate] [--locale <list>] [--key <glob>]",
|
|
9262
9601
|
options: [
|
|
9263
9602
|
["--all", "Re-translate every string, not just empty values"],
|
|
9603
|
+
["--state <list>", "Re-translate only targets in these states: missing|machine|needs-review|reviewed (e.g. needs-review = strings a source edit invalidated)"],
|
|
9264
9604
|
["--estimate", "Print batches, tokens and estimated cost without translating"],
|
|
9265
9605
|
["--locale <list>", "Comma-separated target locales (alias: --locales)"],
|
|
9266
9606
|
["--key <glob>", "Only keys matching this glob"],
|
|
@@ -9355,6 +9695,64 @@ var COMMAND_HELP = {
|
|
|
9355
9695
|
["apply", "Fetch results and write translations (auto-runs when finished)"],
|
|
9356
9696
|
["cancel", "Cancel the pending batch and discard the handle"]
|
|
9357
9697
|
]
|
|
9698
|
+
},
|
|
9699
|
+
get: {
|
|
9700
|
+
summary: "Extract values from the catalog (filtered) without loading the whole file. Prints JSON.",
|
|
9701
|
+
usage: "glotfile get [<key-glob>\u2026] [--key <glob>] [--locale <list>] [--state <list>] [--fields <list>] [--keys-only] [--format json|ndjson]",
|
|
9702
|
+
options: [
|
|
9703
|
+
["<key-glob>\u2026", "Key globs to include (e.g. auth.*); positional, repeatable. Default: all keys"],
|
|
9704
|
+
["--key <glob>", "Additional key glob (merged with positionals)"],
|
|
9705
|
+
["--locale <list>", "Locales to show (default: all configured locales, source included)"],
|
|
9706
|
+
["--state <list>", "Only keys whose shown target locales are in these states: source|missing|machine|needs-review|reviewed"],
|
|
9707
|
+
["--fields <list>", "Cell fields to project: value,state,updatedAt (default value,state); 'all' = the full key entry"],
|
|
9708
|
+
["--keys-only", "Print just the matched key names, one per line"],
|
|
9709
|
+
["--format <fmt>", "json (default, nested) or ndjson (one row per cell)"]
|
|
9710
|
+
]
|
|
9711
|
+
},
|
|
9712
|
+
stats: {
|
|
9713
|
+
summary: "Per-locale progress counts (translated / reviewed / machine / needs-review / missing).",
|
|
9714
|
+
usage: "glotfile stats [--locale <list>] [--format json|text]",
|
|
9715
|
+
options: [
|
|
9716
|
+
["--locale <list>", "Restrict to these comma-separated locales"],
|
|
9717
|
+
["--format <fmt>", "json (default) or text"]
|
|
9718
|
+
]
|
|
9719
|
+
},
|
|
9720
|
+
set: {
|
|
9721
|
+
summary: "Set one value: the source string (default \u2014 flips downstream translations to needs-review) or a target (--locale).",
|
|
9722
|
+
usage: "glotfile set <key> [value] [--locale <code>] [--state <state>] [--create]",
|
|
9723
|
+
options: [
|
|
9724
|
+
["<key> [value]", "Key, then the value (or pass --value, or pipe it on stdin)"],
|
|
9725
|
+
["--locale <code>", "Set this target locale instead of the source"],
|
|
9726
|
+
["--value <v>", "The value (alternative to the positional / stdin)"],
|
|
9727
|
+
["--state <state>", "Resulting state for a target write (default reviewed): machine|needs-review|reviewed"],
|
|
9728
|
+
["--create", "Create the key (scalar) if it does not exist yet"]
|
|
9729
|
+
]
|
|
9730
|
+
},
|
|
9731
|
+
"set-state": {
|
|
9732
|
+
summary: "Flip the review state of one key \u2014 or many via a glob \u2014 across locales.",
|
|
9733
|
+
usage: "glotfile set-state <key|glob> <state> [--locale <list>]",
|
|
9734
|
+
options: [
|
|
9735
|
+
["<key|glob> <state>", "Key/glob, then machine | needs-review | reviewed"],
|
|
9736
|
+
["--key <glob>", "Glob selecting keys (alternative to the positional key)"],
|
|
9737
|
+
["--locale <list>", "Locales to affect (default: every target locale)"]
|
|
9738
|
+
]
|
|
9739
|
+
},
|
|
9740
|
+
clear: {
|
|
9741
|
+
summary: "Empty target value(s) so they read as untranslated (and get refilled by a plain translate).",
|
|
9742
|
+
usage: "glotfile clear <key|glob> --locale <list>",
|
|
9743
|
+
options: [
|
|
9744
|
+
["<key|glob>", "Key or glob to clear"],
|
|
9745
|
+
["--key <glob>", "Glob selecting keys (alternative to the positional key)"],
|
|
9746
|
+
["--locale <list>", "Required: the locale(s) to empty (cannot be the source)"]
|
|
9747
|
+
]
|
|
9748
|
+
},
|
|
9749
|
+
apply: {
|
|
9750
|
+
summary: "Apply a JSON batch of write operations from stdin in one load \u2192 save (atomic by default).",
|
|
9751
|
+
usage: "glotfile apply [--dry-run] [--continue-on-error] < ops.json",
|
|
9752
|
+
options: [
|
|
9753
|
+
["--dry-run", "Report what would change without writing"],
|
|
9754
|
+
["--continue-on-error", "Apply the survivors past a failing op instead of stopping (and saving nothing)"]
|
|
9755
|
+
]
|
|
9358
9756
|
}
|
|
9359
9757
|
};
|
|
9360
9758
|
function formatOpts(opts) {
|
|
@@ -9413,6 +9811,12 @@ async function main(argv) {
|
|
|
9413
9811
|
if (args.command === "split") return runSplit(args);
|
|
9414
9812
|
if (args.command === "skill") return runSkill(args);
|
|
9415
9813
|
if (args.command === "batch") return runBatch(args);
|
|
9814
|
+
if (args.command === "get") return runGetCmd(args);
|
|
9815
|
+
if (args.command === "stats") return runStatsCmd(args);
|
|
9816
|
+
if (args.command === "set") return runSet(args);
|
|
9817
|
+
if (args.command === "set-state") return runSetStateCmd(args);
|
|
9818
|
+
if (args.command === "clear") return runClearCmd(args);
|
|
9819
|
+
if (args.command === "apply") return runApply(args);
|
|
9416
9820
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
9417
9821
|
const { url } = await startServer2({ statePath: args.statePath, dev: args.dev, open: !args.noOpen });
|
|
9418
9822
|
if (args.dev) console.log(`Glotfile dev API on ${url} \u2014 open the UI at the Vite "Local:" URL above`);
|