glotfile 0.8.7 → 0.8.9

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Glotfile</title>
7
- <script type="module" crossorigin src="/assets/index-CVA535xu.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-B64rdzp7.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-BaHu118N.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "glotfile",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "description": "Local-first, git-native translation management.",
5
5
  "license": "MIT",
6
6
  "author": "James Dempster",
7
- "homepage": "https://github.com/jdempster/glotfile#readme",
7
+ "homepage": "https://glotfile.dev",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/jdempster/glotfile.git"
package/skill/SKILL.md CHANGED
@@ -30,6 +30,23 @@ keys cannot be added in glotfile (trans-unit ids are content hashes). After the
30
30
  context) — see "Angular projects" in `references/workflows.md` for the
31
31
  extract → sync → translate → export loop.
32
32
 
33
+ ## Work through the commands, not the raw file
34
+
35
+ On anything but a tiny catalog, **don't `Read` the whole state file or hand-edit its JSON.**
36
+ It can hold thousands of keys across many locales and is re-serialized deterministically,
37
+ so loading it wastes context and hand-edits are slow and easy to corrupt. Instead drive it
38
+ with the CLI:
39
+
40
+ - **Read / extract** with `glotfile get` (filter by key glob, `--locale`, `--state`),
41
+ `glotfile get --keys-only`, and `glotfile stats` (per-locale progress). All emit JSON.
42
+ - **Write** with `glotfile set` (source or `--locale` target), `glotfile set-state`,
43
+ `glotfile clear`, or `glotfile apply` (a JSON batch of edits applied in one atomic write).
44
+
45
+ These are the supported, stable way to inspect and change a glotfile from the CLI — prefer
46
+ them. Reach for a raw-file edit only for something no command covers, and then match the
47
+ deterministic format (`references/schema.md`). (Reading `config` directly is still fine —
48
+ it's small; see below.)
49
+
33
50
  ## Before you touch anything: discover the project's actual config
34
51
 
35
52
  Do not assume the locales, providers, or output formats — read them. They live in the
@@ -46,13 +63,21 @@ unless asked.
46
63
 
47
64
  | You want to… | Do this |
48
65
  | --- | --- |
49
- | Add a new string | Add a `KeyEntry` to `keys` (source-locale value, `state: "source"`), then `glotfile translate` + `glotfile export`. See `references/workflows.md`. |
50
- | Edit an existing source string | Change the source-locale value; downstream translations become stale re-translate. See `references/workflows.md`. |
51
- | Translate missing strings | `glotfile translate` (only fills empties; `--all` re-translates everything). |
66
+ | Size up the catalog / what's left | `glotfile stats` (per-locale translated/reviewed/missing counts). |
67
+ | Extract specific values | `glotfile get [<key-glob>…] [--locale <list>] [--state <list>]` — JSON out; the way to read a large catalog. `--keys-only` for just names. |
68
+ | Add a new string | `glotfile set <key> "<source text>" --create`, then `glotfile translate` + `glotfile export`. (Angular: add it in code, not here.) See `references/workflows.md`. |
69
+ | Edit an existing source string | `glotfile set <key> "<new text>"` — flips downstream translations to `needs-review`. Then `glotfile translate --state needs-review` re-translates just those. See `references/workflows.md`. |
70
+ | Set or fix one translation | `glotfile set <key> "<text>" --locale <code>` (lands `reviewed`; `--state` to override). |
71
+ | Translate missing strings | `glotfile translate` (fills empties only). `--state needs-review` re-translates stale strings; `--all` redoes everything. |
72
+ | Mark a review state | `glotfile set-state <key|glob> <reviewed\|needs-review\|machine> [--locale <list>]`. |
73
+ | Empty a translation (force re-translate) | `glotfile clear <key|glob> --locale <list>`. |
74
+ | Make many edits at once | Pipe a JSON op list to `glotfile apply` — one atomic write; the right tool for bulk edits on a big file. |
52
75
  | Write locale files | `glotfile export` |
53
76
  | Find problems | `glotfile lint` (catalog issues) / `glotfile check` (lint + exports up to date) |
54
77
  | Bootstrap from existing locale files | `glotfile import --format <adapter>` (or bare `glotfile import` to auto-detect) — see `references/workflows.md`. Every export adapter is importable. |
55
78
  | Remove dead keys | `glotfile prune --unused` / `--empty-source` (dry-run unless `--write`). |
79
+ | Stop a live key being pruned | Add its glob to `config.scan.keep` (keys always counted as used). Never delete a `prune --unused` hit you know is live — `keep` it. See `references/schema.md`. |
80
+ | Exclude a key from translation | Set `skipTranslate: true` on the key (brand names, code, copy that must stay in the source language) — `translate` skips it and lint won't flag it as missing. |
56
81
  | Enforce term translations | Glossary — see `references/workflows.md`. |
57
82
 
58
83
  ## Reference files — read the one you need
@@ -27,10 +27,14 @@ Adapter names: `flutter-arb`, `laravel-php`, `i18next-json`, `vue-i18n-json`,
27
27
  `gettext-po`, `apple-strings`, `apple-stringsdict`, `angular-xliff`, `rails-yaml`.
28
28
 
29
29
  ## translate
30
- `glotfile translate [--all] [--estimate] [--locale <list>] [--key <glob>] [--batch [--wait]]`
30
+ `glotfile translate [--all] [--state <list>] [--estimate] [--locale <list>] [--key <glob>] [--batch [--wait]]`
31
31
  — AI-translate strings into the target locales and write the results back into the state file.
32
32
  - By default only **empty** values are translated (existing translations are left alone).
33
33
  - `--all` — re-translate every string, overwriting existing translations.
34
+ - `--state <list>` — re-translate only targets currently in these states (comma list of
35
+ `missing`, `machine`, `needs-review`, `reviewed`). The key one is **`--state needs-review`**:
36
+ it re-translates exactly the strings a source edit invalidated, without touching good
37
+ reviewed translations. (`--all` is the same as listing every state; default is `missing`.)
34
38
  - `--estimate` — print batch/token/cost estimates and translate nothing.
35
39
  - `--locale fr,de` — restrict to these target locales (alias: `--locales`).
36
40
  - `--key <glob>` — only keys matching the glob (e.g. `auth.*`).
@@ -41,6 +45,75 @@ Adapter names: `flutter-arb`, `laravel-php`, `i18next-json`, `vue-i18n-json`,
41
45
 
42
46
  Requires a configured AI provider + API key in per-machine local settings.
43
47
 
48
+ ## get
49
+ `glotfile get [<key-glob>…] [--key <glob>] [--locale <list>] [--state <list>] [--fields <list>] [--keys-only] [--format json|ndjson]`
50
+ — extract values from the catalog **without loading the whole file**. Prints JSON to stdout.
51
+ This is how you read a large catalog.
52
+ - Positional `<key-glob>` args (and/or `--key`) select keys (e.g. `auth.*`); default: all keys.
53
+ - `--locale <list>` — locales to show (default: every configured locale, **source included**
54
+ so you always have the reference text).
55
+ - `--state <list>` — show only keys whose shown target locales are in these effective states:
56
+ `source`, `missing` (empty/untranslated), `machine`, `needs-review`, `reviewed`. The source
57
+ locale is always shown as the reference and doesn't gate the filter. So
58
+ `glotfile get --locale en,de --state missing` is "every key still untranslated in `de`, with
59
+ the English source beside it" — the translation work queue.
60
+ - `--fields <list>` — cell projection: `value,state` (default), add `updatedAt`, or `all` for
61
+ the full key entry (context/notes/tags/placeholders/plural + values).
62
+ - `--keys-only` — print just the matched key names, one per line (the cheapest overview).
63
+ - `--format ndjson` — one flat `{key, locale, value, state}` row per line (stream-friendly for
64
+ huge result sets); default is a nested JSON object `{ key: { locale: { value, state } } }`.
65
+
66
+ ## stats
67
+ `glotfile stats [--locale <list>] [--format json|text]` — per-locale progress counts
68
+ (`reviewed` / `machine` / `needs-review` / `missing`) plus totals. Use it to size up the work
69
+ before a big translate or to report completion. JSON by default; `--format text` for a table.
70
+
71
+ ## set
72
+ `glotfile set <key> [value] [--locale <code>] [--state <state>] [--create]`
73
+ — set a single value. The value is the positional, or `--value`, or piped on stdin (multi-line).
74
+ - **No `--locale` ⇒ the source string.** Editing it flips every downstream `reviewed`/`machine`
75
+ translation to `needs-review` (it tells you how many) — then `glotfile translate --state
76
+ needs-review` re-fills just those. This is the "write back the source language, mark the
77
+ others stale" path.
78
+ - `--locale <code>` — set that target's value. It lands `reviewed` (a deliberate, authoritative
79
+ edit); pass `--state machine|needs-review` to override.
80
+ - `--create` — create the key (scalar) if it doesn't exist yet (source writes only).
81
+ - Plural keys are edited via `apply` (`set-source-forms` / `set-forms`), not `set`.
82
+
83
+ ## set-state
84
+ `glotfile set-state <key|glob> <state> [--locale <list>]` — flip the review state
85
+ (`machine` / `needs-review` / `reviewed`) of one key, or many via a glob, across locales
86
+ (default: every target locale). E.g. `glotfile set-state auth.* reviewed --locale fr` approves
87
+ all of `auth.*`'s French. Only cells that already have a value are touched.
88
+
89
+ ## clear
90
+ `glotfile clear <key|glob> --locale <list>` — empty the given target value(s) so they read as
91
+ **untranslated**, which makes a plain `glotfile translate` refill them. `--locale` is required
92
+ and cannot be the source locale (edit that with `set`).
93
+
94
+ ## apply
95
+ `glotfile apply [--dry-run] [--continue-on-error]` — read a JSON **array of write operations**
96
+ from stdin and apply them all in one load → mutate → save. Use this for bulk edits on a large
97
+ catalog: one file rewrite instead of N. Each op is one object:
98
+
99
+ ```jsonc
100
+ [
101
+ { "op": "set-source", "key": "auth.title", "value": "Sign in" },
102
+ { "op": "set-target", "key": "auth.title", "locale": "fr", "value": "Connexion", "state": "reviewed" },
103
+ { "op": "set-source-forms", "key": "cart.items", "forms": { "one": "{count} item", "other": "{count} items" } },
104
+ { "op": "set-forms", "key": "cart.items", "locale": "pl", "forms": { "one": "…", "few": "…", "many": "…", "other": "…" } },
105
+ { "op": "set-state", "key": "auth.title", "locale": "de", "state": "reviewed" },
106
+ { "op": "clear", "key": "auth.title", "locale": "es" },
107
+ { "op": "create", "key": "home.cta", "value": "Get started" }
108
+ ]
109
+ ```
110
+
111
+ - **Atomic by default:** if any op fails, nothing is written (the file is untouched) and the
112
+ command exits non-zero, reporting which op failed.
113
+ - `--continue-on-error` — apply the ops that succeed, skip the ones that fail, and save anyway.
114
+ - `--dry-run` — report what would change without writing.
115
+ - Prints `{ applied, keysTouched, saved, dryRun, errors }`.
116
+
44
117
  ## lint
45
118
  `glotfile lint [--format text|json|sarif] [--locale <list>] [--rule <list>] [--max-warnings <n>] [--include-suppressed] [--accept]`
46
119
  — check the catalog for problems (placeholder mismatches, length, glossary violations,
@@ -99,6 +172,10 @@ batch API (anthropic only), like `translate --batch`.
99
172
  (`angular-xliff`), the regex scanner can't see content-hash ids, so `scan` instead rebuilds
100
173
  the index from the source locations recorded in `messages.xlf`.
101
174
 
175
+ Tune it via `config.scan` when the heuristic misses (`include`/`exclude` file globs,
176
+ Flutter `accessors`, custom `patterns` regexes, and **`keep`** — see below). See
177
+ `references/schema.md` for the full shape.
178
+
102
179
  ## prune
103
180
  `glotfile prune (--empty-source | --unused) [--write]` — remove keys. **Dry-run (lists
104
181
  only) unless `--write` is given.**
@@ -107,6 +184,12 @@ only) unless `--write` is given.**
107
184
  For Angular, "unused" is computed from a fresh extraction (keys gone from `messages.xlf`),
108
185
  so run `ng extract-i18n` before pruning.
109
186
 
187
+ `--unused` is heuristic. A key referenced only dynamically or through a wrapper the scanner
188
+ doesn't recognise can show up as a false positive. **Don't delete a key you know is live —
189
+ add its glob to `config.scan.keep`** (keys always treated as used) and re-run. `keep` is also
190
+ the right home for keys consumed by code the scanner can never see (framework internals,
191
+ vendored packages, server-driven UIs).
192
+
110
193
  ## split
111
194
  `glotfile split` — convert a single `glotfile.json` into a `glotfile/` directory
112
195
  (`config.json`, `keys.json`, `locales/<code>.json`). Produces smaller, more reviewable
@@ -24,6 +24,23 @@ saved to the state file, exported, and committed.
24
24
  - Commit the state file and the regenerated outputs together, so the catalog and the
25
25
  locale files never drift apart in history. `glotfile check` verifies they're in sync.
26
26
 
27
+ ## Work surgically — don't load or hand-edit a large catalog
28
+
29
+ The state file is the source of truth, but it is not your editing surface. On any
30
+ non-trivial catalog, reading the whole `glotfile.json` into context or hand-editing its
31
+ JSON is wasteful and error-prone (it's re-serialized deterministically, so manual edits
32
+ must match exactly). Use the commands that read and write it precisely:
33
+
34
+ - **Read:** `glotfile get` (filter by key glob, `--locale`, `--state`), `get --keys-only`,
35
+ `glotfile stats`.
36
+ - **Write:** `glotfile set` (source or `--locale` target), `glotfile set-state`,
37
+ `glotfile clear`, and `glotfile apply` for a batch of edits in one atomic write.
38
+
39
+ Editing the **source** value (`glotfile set <key> "<text>"`) automatically flips the other
40
+ locales to `needs-review`; re-fill just those with `glotfile translate --state needs-review`.
41
+ Reading `config` directly is fine — it's small. Hand-edit a key only for something no
42
+ command covers, and then match the deterministic format (`references/schema.md`).
43
+
27
44
  ## Two on-disk layouts — support both
28
45
 
29
46
  A project is either single-file (`glotfile.json`) or split (`glotfile/` directory).
@@ -36,7 +36,12 @@ In **split** layout this is spread across `glotfile/config.json` (holds `version
36
36
  "autoExport": true, // serve re-exports on change (default)
37
37
  "spelling": { "customWords": [] },
38
38
  "lint": { "rules": {}, "ignore": [], "spelling": {} },
39
- "scan": { "include": [], "exclude": [], "accessors": [], "patterns": [] }
39
+ "scan": { // tunes `scan` / `prune --unused`
40
+ "include": [], "exclude": [], // globs limiting which files are scanned
41
+ "accessors": [], // extra Flutter accessor names
42
+ "patterns": [], // custom usage regexes (capture group 1 = key)
43
+ "keep": ["analytics.*"] // key globs ALWAYS treated as used (never pruned)
44
+ }
40
45
  }
41
46
  ```
42
47
 
@@ -44,6 +49,20 @@ In **split** layout this is spread across `glotfile/config.json` (holds `version
44
49
  **Saving Settings from the UI replaces the whole `config` object** — any new `config.*`
45
50
  section must be modeled in the round-trip or it is silently wiped.
46
51
 
52
+ ### `config.scan` — keeping the scanner honest
53
+
54
+ `scan` (and therefore `prune --unused` and the UI usage tree) infers which keys the code
55
+ references. Tune it when the heuristic misses:
56
+
57
+ - `include` / `exclude` — globs narrowing which files are scanned.
58
+ - `accessors` — extra accessor names for Flutter's gen_l10n object (auto-detection covers
59
+ most projects; this is the escape hatch).
60
+ - `patterns` — custom regexes for project-specific i18n call sites (capture group 1 is the key).
61
+ - **`keep`** — key globs **always counted as used**, so `prune --unused` never flags them.
62
+ Use it for keys consumed by code the scanner can't see: framework internals, vendored
63
+ packages, server-driven UIs, or keys built from a fully dynamic name. This is the fix when
64
+ `prune --unused` lists a key you know is live — add the glob to `keep`, don't delete it.
65
+
47
66
  ## KeyEntry
48
67
 
49
68
  ```jsonc
@@ -54,7 +73,7 @@ section must be modeled in the round-trip or it is silently wiped.
54
73
  "tags": ["checkout"],
55
74
  "maxLength": 40, // lint flags overflow
56
75
  "screenshot": "…", // path; used by vision providers
57
- "skipTranslate": false,
76
+ "skipTranslate": false, // true => translate skips it; not "missing"
58
77
  "plural": { "arg": "count" }, // presence => plural message
59
78
  "placeholders": { // typed placeholder metadata
60
79
  "count": { "type": "int", "format": "compact", "example": "1,000" }
@@ -8,33 +8,46 @@ when no command fits (then match the deterministic format — see `references/sc
8
8
 
9
9
  1. Pick a key name that matches the project's existing convention (look at sibling keys —
10
10
  dotted `auth.login.title`, flat, etc.).
11
- 2. Add a `KeyEntry` to `keys` with the source-locale value and `state: "source"`:
12
- ```jsonc
13
- "cart.empty.title": {
14
- "context": "Heading on the empty-cart screen",
15
- "values": { "en": { "value": "Your cart is empty", "state": "source" } }
16
- }
11
+ 2. Create it with its source value:
12
+ ```sh
13
+ glotfile set cart.empty.title "Your cart is empty" --create
17
14
  ```
18
15
  Add `context` when the string is ambiguous out of context — it measurably improves
19
- translation quality.
16
+ translation quality (set it in the UI, or hand-edit the key's `context` field; see
17
+ `references/schema.md`).
20
18
  3. `glotfile translate` to fill the other locales (or `--key "cart.*"` to scope it).
21
19
  4. `glotfile export` to write the locale files.
22
20
  5. Wire the key up in the app code using the project's i18n accessor, then commit.
23
21
 
24
- For a **plural** message, add a `plural: { "arg": "count" }` marker and use `forms`
25
- instead of `value` (see `references/schema.md`). Use ICU placeholders like `{count}` and
26
- keep them identical across locales the lint placeholder rule enforces this.
22
+ Adding many at once? Batch them through `glotfile apply` (a list of `create` ops) one
23
+ write instead of one per key. For a **plural** message, create it then set its forms via
24
+ `apply` (`set-source-forms`), or hand-edit the key with a `plural: { "arg": "count" }`
25
+ marker and `forms` instead of `value` (see `references/schema.md`). Use ICU placeholders
26
+ like `{count}` and keep them identical across locales — the lint placeholder rule enforces
27
+ this.
27
28
 
28
29
  ## Edit an existing source string
29
30
 
30
- 1. Change the **source-locale** value in `keys.<name>.values.<sourceLocale>`.
31
- 2. Existing translations are now stale. Re-translate them: `glotfile translate --all
32
- --key "<name>"` (or let the user review). Glotfile tracks the source a translation was
33
- made from, so stale entries can be flagged `needs-review`.
31
+ 1. Write the new source value:
32
+ ```sh
33
+ glotfile set checkout.title "Review your order"
34
+ ```
35
+ This flips every `reviewed`/`machine` translation of that key to `needs-review` (it
36
+ reports how many) — they're now stale.
37
+ 2. Re-translate just the stale ones:
38
+ ```sh
39
+ glotfile translate --state needs-review
40
+ ```
41
+ (Scope with `--key "checkout.*"` if you only want this key.) Use `--state needs-review`,
42
+ **not** `--all` — a plain `glotfile translate` fills only *empty* values so it would skip
43
+ the stale-but-non-empty ones, while `--all` would also overwrite good `reviewed`
44
+ translations elsewhere. `--state needs-review` re-does exactly what the edit invalidated.
34
45
  3. `glotfile export`, then commit.
35
46
 
36
- Never edit a translation in an exported file to "fix" it fix it in the state file
37
- (`state: "reviewed"` once a human approves) and re-export.
47
+ Editing many sources at once? Batch the `set-source` ops through `glotfile apply`, then run
48
+ the single `glotfile translate --state needs-review`. Never edit a translation in an
49
+ exported file to "fix" it — fix it in the catalog (`glotfile set <key> --locale <code>`,
50
+ which lands `reviewed`) and re-export.
38
51
 
39
52
  ## Onboard a repo that already has locale files
40
53
 
@@ -128,3 +141,35 @@ with the glossary applied.
128
141
  `glotfile scan` (index code references) → `glotfile build-context` (AI writes a short
129
142
  `context` per key from how it's used in code) → `glotfile translate`. Keys with good
130
143
  `context` translate far more accurately, especially short or ambiguous strings.
144
+
145
+ ## Work a large catalog from the CLI
146
+
147
+ A real catalog can be thousands of keys across a dozen-plus locales. Don't read or
148
+ hand-edit the file — query and edit it surgically.
149
+
150
+ 1. **Survey:** `glotfile stats` for per-locale completion; `glotfile stats --format text`
151
+ for a quick table. `glotfile get --keys-only --key "<glob>"` to enumerate a namespace.
152
+ 2. **Pull only what you need:** the source plus the cells still to do —
153
+ ```sh
154
+ glotfile get --locale en,de --state missing,needs-review --key "checkout.*"
155
+ ```
156
+ gives the English source beside every `de` cell that's empty or stale for `checkout.*`.
157
+ Add `--format ndjson` when the result is large and you want to stream/grep it.
158
+ 3. **Make the edits as one batch.** Build a JSON op list and pipe it in — one atomic write,
159
+ no diff churn, no per-edit race:
160
+ ```sh
161
+ cat <<'JSON' | glotfile apply
162
+ [
163
+ { "op": "set-target", "key": "checkout.title", "locale": "de", "value": "Kasse", "state": "reviewed" },
164
+ { "op": "set-target", "key": "checkout.pay", "locale": "de", "value": "Bezahlen", "state": "reviewed" }
165
+ ]
166
+ JSON
167
+ ```
168
+ Preview first with `glotfile apply --dry-run`. On any bad op the whole batch is rejected
169
+ (nothing written) unless you pass `--continue-on-error`.
170
+ 4. **Or let AI do the gaps:** `glotfile translate --locale de --key "checkout.*"` fills the
171
+ empties; after a source edit, `glotfile translate --state needs-review` re-does the stale
172
+ ones. Then `glotfile export` and commit.
173
+
174
+ Rule of thumb: **`get` to read, `set`/`apply` to write, `translate` for the AI gaps** —
175
+ reach for a raw-file edit only when no command covers what you need.