@chesterbrains/translify-cli 0.4.0 → 0.4.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/README.md CHANGED
@@ -77,9 +77,7 @@ npx translify pull
77
77
  }
78
78
  ```
79
79
 
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.
80
+ Every field is described in the [`translify.json` reference](#translifyjson-reference).
83
81
 
84
82
  ## Quickstart: Flutter
85
83
 
@@ -117,6 +115,26 @@ flutter gen-l10n
117
115
 
118
116
  `"@@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
117
 
118
+ ## `translify.json` reference
119
+
120
+ `translify.json` lives in the repository root; every command reads it from the current directory. The
121
+ [JSON schema](schema/translify.schema.json) gives editors completion and validation through `$schema`. Unknown
122
+ fields are refused, and so is any field whose name contains `key`, `secret` or `token`: keys never go in this file.
123
+
124
+ | Field | Type | Required | Description |
125
+ |---|---|---|---|
126
+ | `$schema` | string | no | Schema URL for editor support. Ignored by the CLI. |
127
+ | `apiUrl` | URL | yes | The Translify API base, ending in `/api`, e.g. `https://translify.tommasofeltrin.work/api`. |
128
+ | `files` | array | yes | One rule per kind of translation file. Each rule has the fields below. |
129
+ | `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`. |
130
+ | `files[].format` | `json` \| `xliff` \| `arb` | yes | File format: i18next JSON, XLIFF or Flutter ARB. |
131
+ | `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. |
132
+ | `files[].jsonStyle` | `nested` \| `flat` | no | Only for `json`. `nested` (the default) writes `{ "a": { "b": "…" } }`, `flat` writes `{ "a.b": "…" }`. |
133
+ | `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. |
134
+
135
+ Each file must be matched by one rule only, and each (locale, namespace) pair must come from one file only;
136
+ otherwise the CLI stops before sending anything and names the files involved.
137
+
120
138
  ## Commands
121
139
 
122
140
  | Command | What it does |
@@ -145,10 +163,44 @@ Options:
145
163
  - **Source locale: your files win.** Existing source translations are overwritten with what is in the file.
146
164
  - **Target locales: Translify fills only the gaps.** Keys that already have a translation are left alone and counted as skipped.
147
165
  - `--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.
166
+ - `--prune` deletes keys that no longer exist in your source files, together with their translations. See [Prune safety](#prune-safety).
149
167
  - `--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
168
  - `--dry-run` shows the effect of any combination and writes nothing.
151
169
 
170
+ ## Prune safety
171
+
172
+ `translify push --prune` is the only command that deletes keys. What it deletes, and what stops it:
173
+
174
+ - **Only the namespaces in this push.** A key is an orphan when its namespace is in the push and its source file no
175
+ longer has it. Namespaces you did not push (another rule, another repository, `--namespace` left them out) are never
176
+ touched.
177
+ - **Never published keys.** A key published to any environment is listed as kept and survives. Unpublish it in
178
+ Translify first if it really should go.
179
+ - **Never a whole namespace by accident.** If a source file in the push has no entries, the push is refused
180
+ (`VALIDATION_FAILED` with `emptySourcePrune`) instead of deleting every key in that namespace. Emptying a namespace
181
+ is done in the Translify web app.
182
+ - **Exactly the list you confirmed.** The CLI always runs a dry run first, shows the keys it would delete and asks
183
+ for confirmation. The real push sends a digest of that list; if the server's list differs by then (keys added,
184
+ removed or published in between), it refuses with `ORPHANS_CHANGED` and writes nothing. A server too old to return
185
+ the digest makes `--prune` refuse.
186
+ - **No silent prune in CI.** Without a terminal, `--prune` is refused unless you pass `--yes`.
187
+ - **Atomic.** The whole push, deletions included, is one transaction: if anything fails, nothing is written.
188
+
189
+ `--yes` confirms whatever that run's dry run lists, so in CI review the list before it runs unattended. One way:
190
+ show the orphans on pull requests and prune on `main`.
191
+
192
+ ```yaml
193
+ # on pull_request: list what a prune would delete, change nothing
194
+ - run: npx translify push --prune --dry-run
195
+ env:
196
+ TRANSLIFY_SECRET_KEY: ${{ secrets.TRANSLIFY_SECRET_KEY }}
197
+
198
+ # on push to main: prune for real
199
+ - run: npx translify push --prune --yes
200
+ env:
201
+ TRANSLIFY_SECRET_KEY: ${{ secrets.TRANSLIFY_SECRET_KEY }}
202
+ ```
203
+
152
204
  ## Review
153
205
 
154
206
  When the project has **Require review** turned on in Translify:
@@ -262,6 +314,7 @@ The server's error `code` decides the exit code first; the HTTP status is only u
262
314
  | `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
315
  | `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
316
  | `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. |
317
+ | `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
318
  | `TRANSACTION_CONFLICT` | 4 | Another write collided with yours. Retry. |
266
319
  | `RATE_LIMITED` | 4 | The CLI already retried 3 times, honouring `Retry-After`. Wait a moment and retry. |
267
320
  | `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.1';
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.1",
4
4
  "description": "Push, pull and publish Translify translations from your repository",
5
5
  "license": "MIT",
6
6
  "repository": {