@chesterbrains/translify-cli 0.4.0 → 0.4.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/README.md CHANGED
@@ -9,6 +9,8 @@ The CLI reads a `translify.json` that maps your translation files (i18next JSON,
9
9
  - `status` to fail CI when local files differ from Translify,
10
10
  - `publish` an environment.
11
11
 
12
+ Complete example apps — React + i18next at runtime, Flutter (ARB) and Angular (XLIFF) at build time — live in [translify-examples](https://github.com/chesterbrains/translify-examples).
13
+
12
14
  ## Install
13
15
 
14
16
  ### Standalone binary (no Node needed)
@@ -77,9 +79,7 @@ npx translify pull
77
79
  }
78
80
  ```
79
81
 
80
- - `pattern` must contain `{locale}`. `{namespace}` is optional; without it, set a fixed `"namespace"` on the rule.
81
- - `format` is `json`, `xliff` or `arb`. JSON also takes `"jsonStyle": "nested"` (default) or `"flat"`.
82
- - `locales` maps a locale name on disk to the Translify locale code, for example `{ "en_US": "en-US" }`. Omit it when the names already match.
82
+ Every field is described in the [`translify.json` reference](#translifyjson-reference).
83
83
 
84
84
  ## Quickstart: Flutter
85
85
 
@@ -117,6 +117,26 @@ flutter gen-l10n
117
117
 
118
118
  `"@@locale"` is optional in ARB files: when it is absent, the file's locale is used. When it is present it must match the file's locale (case and `_`/`-` are ignored). On `pull`, the CLI writes `@@locale` using the locale name on disk (for example `en_US` in `app_en_US.arb`), as `flutter gen-l10n` expects.
119
119
 
120
+ ## `translify.json` reference
121
+
122
+ `translify.json` lives in the repository root; every command reads it from the current directory. The
123
+ [JSON schema](schema/translify.schema.json) gives editors completion and validation through `$schema`. Unknown
124
+ fields are refused, and so is any field whose name contains `key`, `secret` or `token`: keys never go in this file.
125
+
126
+ | Field | Type | Required | Description |
127
+ |---|---|---|---|
128
+ | `$schema` | string | no | Schema URL for editor support. Ignored by the CLI. |
129
+ | `apiUrl` | URL | yes | The Translify API base, ending in `/api`, e.g. `https://translify.tommasofeltrin.work/api`. |
130
+ | `files` | array | yes | One rule per kind of translation file. Each rule has the fields below. |
131
+ | `files[].pattern` | string | yes | Path relative to `translify.json`, with `/` separators. Must contain `{locale}` once and may contain `{namespace}` once, e.g. `locales/{locale}/{namespace}.json`. |
132
+ | `files[].format` | `json` \| `xliff` \| `arb` | yes | File format: i18next JSON, XLIFF or Flutter ARB. |
133
+ | `files[].namespace` | string | when the pattern has no `{namespace}`, and always for `arb` | Fixed namespace for every file the rule matches. Lowercase kebab-case, at most 64 characters. |
134
+ | `files[].jsonStyle` | `nested` \| `flat` | no | Only for `json`. `nested` (the default) writes `{ "a": { "b": "…" } }`, `flat` writes `{ "a.b": "…" }`. |
135
+ | `locales` | object | no | Maps a locale name on disk to a Translify locale code, e.g. `{ "en_US": "en-US" }`. Unmapped names pass through unchanged. Two names may not map to the same code, and a target may not itself be a mapped name. |
136
+
137
+ Each file must be matched by one rule only, and each (locale, namespace) pair must come from one file only;
138
+ otherwise the CLI stops before sending anything and names the files involved.
139
+
120
140
  ## Commands
121
141
 
122
142
  | Command | What it does |
@@ -145,10 +165,44 @@ Options:
145
165
  - **Source locale: your files win.** Existing source translations are overwritten with what is in the file.
146
166
  - **Target locales: Translify fills only the gaps.** Keys that already have a translation are left alone and counted as skipped.
147
167
  - `--overwrite-targets` also overwrites existing target translations.
148
- - `--prune` deletes keys that no longer exist in your source files, together with their translations. Keys that are published to an environment are never deleted: they are listed as kept, and you unpublish them in Translify first. `--prune` deletes exactly the keys you confirmed: the real push sends a digest of the dry run's list, and if anything changed since (keys added, removed or published) the server refuses and you re-run `translify push --prune` to review the new list. A server too old to return that digest makes `--prune` refuse.
168
+ - `--prune` deletes keys that no longer exist in your source files, together with their translations. See [Prune safety](#prune-safety).
149
169
  - `--yes` skips the confirmation prompt for the two destructive flags. Without a terminal and without `--yes`, a destructive push is refused and nothing changes.
150
170
  - `--dry-run` shows the effect of any combination and writes nothing.
151
171
 
172
+ ## Prune safety
173
+
174
+ `translify push --prune` is the only command that deletes keys. What it deletes, and what stops it:
175
+
176
+ - **Only the namespaces in this push.** A key is an orphan when its namespace is in the push and its source file no
177
+ longer has it. Namespaces you did not push (another rule, another repository, `--namespace` left them out) are never
178
+ touched.
179
+ - **Never published keys.** A key published to any environment is listed as kept and survives. Unpublish it in
180
+ Translify first if it really should go.
181
+ - **Never a whole namespace by accident.** If a source file in the push has no entries, the push is refused
182
+ (`VALIDATION_FAILED` with `emptySourcePrune`) instead of deleting every key in that namespace. Emptying a namespace
183
+ is done in the Translify web app.
184
+ - **Exactly the list you confirmed.** The CLI always runs a dry run first, shows the keys it would delete and asks
185
+ for confirmation. The real push sends a digest of that list; if the server's list differs by then (keys added,
186
+ removed or published in between), it refuses with `ORPHANS_CHANGED` and writes nothing. A server too old to return
187
+ the digest makes `--prune` refuse.
188
+ - **No silent prune in CI.** Without a terminal, `--prune` is refused unless you pass `--yes`.
189
+ - **Atomic.** The whole push, deletions included, is one transaction: if anything fails, nothing is written.
190
+
191
+ `--yes` confirms whatever that run's dry run lists, so in CI review the list before it runs unattended. One way:
192
+ show the orphans on pull requests and prune on `main`.
193
+
194
+ ```yaml
195
+ # on pull_request: list what a prune would delete, change nothing
196
+ - run: npx translify push --prune --dry-run
197
+ env:
198
+ TRANSLIFY_SECRET_KEY: ${{ secrets.TRANSLIFY_SECRET_KEY }}
199
+
200
+ # on push to main: prune for real
201
+ - run: npx translify push --prune --yes
202
+ env:
203
+ TRANSLIFY_SECRET_KEY: ${{ secrets.TRANSLIFY_SECRET_KEY }}
204
+ ```
205
+
152
206
  ## Review
153
207
 
154
208
  When the project has **Require review** turned on in Translify:
@@ -262,6 +316,7 @@ The server's error `code` decides the exit code first; the HTTP status is only u
262
316
  | `ORPHANS_CHANGED` | 1 | The orphan list changed between the dry run and the real push (keys were added, removed or published). Nothing was written; run `translify push --prune` again to review the new list. |
263
317
  | `REVIEW_DISABLED` | 1 | `push --status draft` or `--status needs-review` on a project without review. Turn on **Require review** in the project settings, or drop `--status`. Nothing was written. |
264
318
  | `APPROVED_ONLY_REQUIRES_WORKING` | 1 | `--only-approved` was combined with `--from env:<slug>`. Pull an environment without it; it already holds only what its gate allowed. |
319
+ | `INVALID_CELLS` | 6 | `publish` refused: translations that would go live have placeholder errors. Nothing was published; the output lists them like `lint` does. Fix them in Translify, or re-run with `--allow-errors` to publish anyway (recorded in the activity log). |
265
320
  | `TRANSACTION_CONFLICT` | 4 | Another write collided with yours. Retry. |
266
321
  | `RATE_LIMITED` | 4 | The CLI already retried 3 times, honouring `Retry-After`. Wait a moment and retry. |
267
322
  | `INTERNAL_ERROR` / `DATABASE_ERROR` / 5xx | 4 | A server problem. Retry shortly; if it persists, re-run with `--debug` and report it. |
package/dist/http.js CHANGED
@@ -8,7 +8,7 @@ const REQUEST_TIMEOUT_MS = 180_000;
8
8
  const UPGRADE = 'npm i -g @chesterbrains/translify-cli@latest, or for the standalone binary: curl -fsSL https://github.com/chesterbrains/translify-cli/releases/latest/download/install.sh | sh';
9
9
  const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10
10
  /** Exit code by the body's `code` first; the status only decides for a code we do not know. */
11
- const CODE_EXIT = {
11
+ export const CODE_EXIT = {
12
12
  VALIDATION_FAILED: EXIT.failed,
13
13
  LOCALE_NOT_FOUND: EXIT.failed,
14
14
  NAMESPACE_NOT_FOUND: EXIT.failed,
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Must equal package.json "version"; src/version.test.ts fails when they drift.
2
- export const VERSION = '0.4.0';
2
+ export const VERSION = '0.4.2';
3
3
  export const USER_AGENT = `translify-cli/${VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chesterbrains/translify-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Push, pull and publish Translify translations from your repository",
5
5
  "license": "MIT",
6
6
  "repository": {