@x18n/cli 0.0.3 → 0.0.5

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/cli.mjs CHANGED
@@ -12,7 +12,7 @@ import { existsSync } from "node:fs";
12
12
  import { namespaceFromPath } from "@x18n/builtins/finders";
13
13
  import { parse, stringify } from "yaml";
14
14
  //#region package.json
15
- var version = "0.0.3";
15
+ var version = "0.0.5";
16
16
  //#endregion
17
17
  //#region src/commands/change.ts
18
18
  async function project(options) {
@@ -128,6 +128,13 @@ function reportInputs(workspace, options) {
128
128
  };
129
129
  });
130
130
  }
131
+ /** History costs a git walk, so it loads only when `stale-translation` runs. */
132
+ function needsHistory(loaded, options) {
133
+ const setting = loaded.project.config.checks["stale-translation"];
134
+ const level = typeof setting === "string" ? setting : setting?.[0];
135
+ const only = list(options.only);
136
+ return level !== "off" && (only.length === 0 || only.includes("stale-translation")) && !list(options.skip).includes("stale-translation");
137
+ }
131
138
  async function report(workspace, options) {
132
139
  const inputs = reportInputs(workspace, options);
133
140
  const reporterIds = list(options.reporter);
@@ -152,6 +159,10 @@ async function report(workspace, options) {
152
159
  }
153
160
  async function check(options) {
154
161
  const workspace = await openWorkspace(options);
162
+ await Promise.all(workspace.projects.filter((loaded) => needsHistory(loaded, options)).map(async (loaded) => {
163
+ await loaded.project.loadHistory({ watch: options.watch });
164
+ await loaded.host.flush();
165
+ }));
155
166
  const code = await report(workspace, options);
156
167
  if (!options.watch) return code;
157
168
  const rerun = async () => {
@@ -492,13 +503,18 @@ async function prompt(kind, options) {
492
503
  if (kind !== "translate" && kind !== "review") throw new X18nError("X18N0001", `unknown prompt "${kind}" (translate, review)`);
493
504
  const locale = kind === "translate" ? options.to : options.locale ?? options.to;
494
505
  if (!locale) throw new X18nError("X18N0001", kind === "translate" ? "pass --to <locale>" : "pass --locale <locale>");
495
- const { project } = singleProject(await openWorkspace(options));
506
+ const { project, host } = singleProject(await openWorkspace(options));
507
+ if (options.stale) {
508
+ await project.loadHistory();
509
+ await host.flush();
510
+ }
496
511
  const data = project.prompt(kind, locale, {
497
512
  keys: options.keys,
498
513
  namespace: options.namespace,
499
514
  limit: options.limit === void 0 ? void 0 : Number(options.limit),
500
515
  cursor: options.cursor,
501
- context: options.context
516
+ context: options.context,
517
+ stale: options.stale
502
518
  });
503
519
  if (options.json || options.format === "json") printJson(`prompt ${kind}`, data);
504
520
  else out(renderPrompt(data));
@@ -659,7 +675,7 @@ function createCli() {
659
675
  cli.command("fill", "Add missing keys with empty values").option("--locale <locale>", "Only these locales (repeatable)").option("--copy-source", "Use the source value instead of an empty string").option("--dry-run", "Print the plan instead of writing").action(run("fill", fill));
660
676
  cli.command("format", "Re-write resources with the configured sort and indent").option("--dry-run", "Print the plan instead of writing").action(run("format", format));
661
677
  cli.command("extract [...files]", "Find hard-coded strings and turn them into keys").option("--list", "Print candidates and proposed keys").option("--plan <file>", "Write the extraction plan to a file to edit").option("--apply <file>", "Apply an (edited) extraction plan").option("--dry-run", "Print the plan instead of writing").action(run("extract", extract));
662
- cli.command("prompt <kind>", "Print a prompt for an agent or LLM: `prompt translate --to <locale>` or `prompt review --locale <locale>`").option("--to <locale>", "Target locale (translate)").option("--locale <locale>", "Locale to review").option("--keys <glob>", "Only keys matching a glob").option("--namespace <ns>", "Only keys of a namespace").option("--limit <n>", "Messages per prompt (default 50)").option("--cursor <cursor>", "Continue from `nextCursor`").option("--format <format>", "markdown (default) or json").option("--no-context", "Leave out code around usages").action(run("prompt", prompt));
678
+ cli.command("prompt <kind>", "Print a prompt for an agent or LLM: `prompt translate --to <locale>` or `prompt review --locale <locale>`").option("--to <locale>", "Target locale (translate)").option("--locale <locale>", "Locale to review").option("--keys <glob>", "Only keys matching a glob").option("--namespace <ns>", "Only keys of a namespace").option("--limit <n>", "Messages per prompt (default 50)").option("--cursor <cursor>", "Continue from `nextCursor`").option("--format <format>", "markdown (default) or json").option("--no-context", "Leave out code around usages").option("--stale", "Also translate stale translations (needs git history)").action(run("prompt", prompt));
663
679
  cli.command("ui", "Start the UI: browse and edit messages in the browser, with HTTP MCP at /__mcp").option("--port <port>", "Port (default: a free one from 9999)").option("--host <host>", "Bind host (default: localhost)").option("--no-open", "Do not open the browser").option("--no-auth", "Skip authentication (CI and e2e only)").action(run("ui", async (options) => (await uiCommands()).ui(options)));
664
680
  cli.command("build", "Write a static, read-only report of the UI").option("--out-dir <dir>", "Output directory (default: .x18n/report)").action(run("build", async (options) => (await uiCommands()).build(options)));
665
681
  cli.command("mcp", "Serve the MCP tools over stdio").action(run("mcp", async (options) => (await uiCommands()).mcp(options)));
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@x18n/cli",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.0.5",
5
5
  "description": "Manage and view i18n files of any project, for humans and agents",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/antfu",
9
- "homepage": "https://github.com/antfu/x18n#readme",
9
+ "homepage": "https://github.com/x18n-dev/x18n#readme",
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/antfu/x18n.git",
12
+ "url": "git+https://github.com/x18n-dev/x18n.git",
13
13
  "directory": "packages/cli"
14
14
  },
15
- "bugs": "https://github.com/antfu/x18n/issues",
15
+ "bugs": "https://github.com/x18n-dev/x18n/issues",
16
16
  "keywords": [
17
17
  "i18n",
18
18
  "cli",
@@ -38,12 +38,12 @@
38
38
  "node": ">=22"
39
39
  },
40
40
  "dependencies": {
41
- "@x18n/builtins": "0.0.3",
42
- "@x18n/core": "0.0.3",
43
- "@x18n/node": "0.0.3",
44
- "@x18n/ui": "0.0.3",
41
+ "@x18n/builtins": "0.0.5",
42
+ "@x18n/core": "0.0.5",
43
+ "@x18n/node": "0.0.5",
44
+ "@x18n/ui": "0.0.5",
45
45
  "cac": "^7.1.0",
46
- "devframe": "^1.2.5",
46
+ "devframe": "^1.2.6",
47
47
  "yaml": "^2.9.1"
48
48
  },
49
49
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: x18n
3
- description: Set up and run x18n (`@x18n/cli`) to check and manage a project's translations. Use when the task touches locale files or translation keys - adding, renaming or deleting keys, finding missing or unused keys, translating missing messages, extracting hard-coded strings, keeping a glossary of terms - or when setting up x18n or migrating from i18n-ally.
3
+ description: Manage a project's translations with x18n (`@x18n/cli`). Use when the task touches locale files or translation keys - adding, renaming or deleting keys, finding missing or unused keys, translating missing messages, reviewing translations, extracting hard-coded strings, keeping a glossary of terms.
4
4
  ---
5
5
 
6
6
  # x18n
@@ -17,7 +17,6 @@ You do the legwork; the user makes the **decisions** that shape the product. Bef
17
17
  - tone and formality per locale (German *du* or *Sie*, French *tu* or *vous*)
18
18
  - deleting keys that look unused, since keys built at runtime look unused too
19
19
  - renames that touch many files or move keys between namespaces
20
- - turning on checks, and making warnings fail CI with `--strict`
21
20
 
22
21
  Batch the questions into one message. Routine work needs no question: adding a key you use, translating a missing message, fixing a placeholder.
23
22
 
@@ -28,14 +27,51 @@ Batch the questions into one message. Routine work needs no question: adding a k
28
27
  - Keep every placeholder of the source message (`{name}`, `{{count}}`, `%{name}`, `:name`, `%s`, `@:key`).
29
28
  - Pass `--json` and read the envelope `{ "x18n": 1, "command", "data", "diagnostics", "errors" }`.
30
29
  - Run renames and deletes with `--dry-run` first, and read the plan.
30
+ - Write every key as a full string literal. Do not build keys at runtime (see below).
31
31
 
32
- ## Set up x18n
32
+ ## Avoid composed keys
33
33
 
34
- 1. Run `npx @x18n/cli doctor` in the project root. Without a config, x18n uses the project's i18n-ally settings if it has any, and detects the i18n library and the locale files. Check the detected presets, the locale files and the source locale. **Done when** the doctor output names the library the project uses and lists every locale file.
35
- 2. If the project has i18n-ally settings (`i18n-ally.*` keys in `.vscode/settings.json`), follow [references/migrate-i18n-ally.md](references/migrate-i18n-ally.md) instead of step 3.
36
- 3. If detection is wrong or incomplete, run `x18n init` to write `x18n.config.ts`, then fix `resources`, `sourceLocale` or `presets` in it. `x18n init --format yml` writes YAML for projects without Node.js tooling. **Done when** `x18n doctor` shows the right result.
37
- 4. Install the CLI as a dev dependency with the project's package manager (`pnpm add -D @x18n/cli`), and add a script such as `"i18n:check": "x18n check"`. Ask the user before you add it to CI.
38
- 5. Run `x18n check --json` and report the summary to the user. **Done when** the user has the counts of errors and warnings per check, and has decided what to fix first.
34
+ Do not build a key from parts, such as `` t(`foo.bar.${name}`) `` or `t('foo.bar.' + name)`. x18n and the compiler cannot see the full key, so:
35
+
36
+ - `x18n keys --unused` reports the key as unused, and `x18n delete` can remove a message that the code needs
37
+ - `x18n rename` does not update the usage
38
+ - `x18n check` cannot find a missing message until the user hits it at runtime
39
+ - translators cannot see the full key in context
40
+
41
+ Write each key out in full and map the value to it:
42
+
43
+ ```ts
44
+ // Avoid
45
+ t(`status.${status}`)
46
+
47
+ // Prefer: a lookup of full literal keys
48
+ const labels = {
49
+ active: t('status.active'),
50
+ paused: t('status.paused'),
51
+ } as const
52
+ labels[status]
53
+ ```
54
+
55
+ ```ts
56
+ // Avoid
57
+ t(`plans.${plan}.title`)
58
+
59
+ // Prefer: a switch, or a record of functions when the call must be lazy
60
+ const planTitle = {
61
+ free: () => t('plans.free.title'),
62
+ pro: () => t('plans.pro.title'),
63
+ }
64
+ planTitle[plan]()
65
+ ```
66
+
67
+ When the variable part is only a word inside a sentence, use a placeholder instead of a key part: `t('greeting', { name })` with the message `Hello, {name}`. When the text differs by count, use the library's plural syntax, not `t(count === 1 ? 'item.one' : 'item.many')`.
68
+
69
+ If the user's code already composes keys, do not rewrite it unasked. Tell the user which keys are affected, and treat those keys as used when you clean up.
70
+
71
+ ## Set up and check
72
+
73
+ - To set up x18n, or migrate from i18n-ally, use the `x18n-setup` skill.
74
+ - To run `x18n check` and fix what it reports, use the `x18n-check` skill.
39
75
 
40
76
  Commands below use `x18n`; without the dev dependency, run them as `npx @x18n/cli <command>`.
41
77
 
@@ -65,13 +101,6 @@ Maintain it while you work:
65
101
 
66
102
  ## Loops
67
103
 
68
- ### Check the project
69
-
70
- ```sh
71
- x18n check --json
72
- x18n status --json
73
- ```
74
-
75
104
  ### Add a key while coding
76
105
 
77
106
  ```sh
@@ -134,6 +163,16 @@ x18n extract --apply extract-plan.json --json
134
163
 
135
164
  Edit the keys in the plan file before you apply it. The proposed keys follow the project's style; ask the user when there is none yet.
136
165
 
166
+ Tests, fixtures, mocks, stories, configs and type declarations are never scanned, in any language (`*.test.ts`, `*_test.go`, `test_*.py`, `*_spec.rb`, `*Test.java`, `vite.config.ts`, `tests/`, `spec/`, `e2e/`, ...). To keep strings that are not UI text out of the plan, drop them from the plan file, or add a control comment to the code:
167
+
168
+ | Comment | Skips |
169
+ | --- | --- |
170
+ | `x18n-ignore-file` | the whole file, wherever the comment is |
171
+ | `x18n-ignore-start` ... `x18n-ignore-end` | everything between them (to the end of the file when unclosed) |
172
+ | `x18n-ignore` | strings on its line and on the next line |
173
+
174
+ Use any comment syntax: `// x18n-ignore-file`, `<!-- x18n-ignore -->`. For strings that recur across files, use `extract.ignore` in the config instead.
175
+
137
176
  ## Output
138
177
 
139
178
  Exit codes:
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: x18n-check
3
+ description: Run `x18n check` and fix what it reports - missing keys and translations, placeholder mismatches, invalid message syntax, unused keys, glossary violations. Use when the user asks to check, lint or fix translations, when `x18n check` fails locally or in CI, or before opening a PR that touches locale files or translation keys.
4
+ ---
5
+
6
+ # Check and fix translations
7
+
8
+ `x18n check` reports broken, untranslated and unused messages, like `tsc --noEmit` for i18n. Run it, fix the routine problems, and bring the rest to the user. Change locale files only through the `x18n` CLI (`set`, `apply`, `rename`, `delete`), never by hand. The `x18n` skill covers those commands.
9
+
10
+ If the project has no x18n setup yet, use the `x18n-setup` skill first.
11
+
12
+ ## Decisions belong to the user
13
+
14
+ Before you act on one of these, stop and ask, with your recommendation and the facts you found. Batch the questions into one message.
15
+
16
+ - deleting keys reported as unused, since keys built at runtime look unused too
17
+ - a glossary term and its translation per locale
18
+ - changing the severity of a check in the `checks` map of the config, such as `'unused-key': 'error'` or `glossary: 'warning'`
19
+ - making warnings fail CI with `--strict` or `--max-warnings <n>`
20
+
21
+ ## Loop
22
+
23
+ 1. Run `x18n check --json`. Read `data.projects[].summary` for the counts and `diagnostics` for the problems. Group the diagnostics by `code` and `severity`. **Done when** you know the count per check, errors first.
24
+ 2. Fix the errors, check by check, with the table below. Re-run only that check to confirm: `x18n check --only placeholder-mismatch --json`. **Done when** the summary shows no errors, or each remaining error waits on a user decision.
25
+ 3. Fix the warnings you can fix without a decision, such as missing translations. **Done when** the remaining warnings each need a decision.
26
+ 4. Ask the user about the remaining diagnostics in one message: list them by check, with your recommendation for each. Apply what they decide.
27
+ 5. Run `x18n check --json` again and report the result. **Done when** `x18n check` exits with code 0, or every remaining diagnostic is one the user chose to keep.
28
+
29
+ ## Fixes per check
30
+
31
+ | Check (`code`) | Fix |
32
+ | --- | --- |
33
+ | `missing-key` | A usage names a key the source locale lacks. If it is a typo, fix the usage to an existing key (`x18n keys` lists them). Else add it: `x18n set <key> --value <text> --json`. |
34
+ | `missing-translation`, `empty-translation` | Translate: `x18n prompt translate --to <locale> --limit 50`, write the result to a file, then `x18n apply <file> --json`. Follow `nextCursor` when the prompt reports more keys. |
35
+ | `placeholder-mismatch` | Rewrite the target value with every placeholder of the source: `x18n set <key> --locale <locale> --value <text> --json`. |
36
+ | `invalid-syntax` | Fix the message syntax (ICU braces, linked messages) with `x18n set`. When a linked message does not exist, fix the link or add that key. |
37
+ | `key-conflict` | A key is both a message and a parent, or is defined twice in one locale. Ask which one to keep, then `x18n rename` or `x18n delete` with `--dry-run` first. |
38
+ | `unused-key` | Show the list from `x18n keys --unused --json` and delete only the keys the user confirms: `x18n delete <key> --dry-run --json`, then without `--dry-run`. Keys built at runtime go in `usage.keysInUse` in the config instead. |
39
+ | `glossary` | Rewrite the message with the term from the glossary (`x18n doctor --json` lists it under `config.glossary`), with `x18n set`. |
40
+ | `locale-code-conflict`, `dynamic-key`, `key-style`, `identical-to-source`, `hard-string` | Report them. Fix only when the user asks; `hard-string` is fixed with `x18n extract`, or silenced with an `x18n-ignore` control comment when the string is not UI text. |
41
+
42
+ After a batch of fixes, check only what you touched: `x18n check --only placeholder-mismatch,invalid-syntax,glossary --locale de --json`.
43
+
44
+ ## CI
45
+
46
+ `x18n check` exits with code 1 on errors, so CI fails on broken keys without more setup. Warnings do not fail it unless the project uses `--strict` or `--max-warnings <n>`. For pull request annotations, use `x18n check --reporter github --reporter pretty`. Change none of this without asking.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: x18n-setup
3
+ description: Set up x18n (`@x18n/cli`) in a project, or migrate it from i18n-ally. Use when a project has no x18n config yet, when x18n detects the wrong locale files or library, when the user asks to add, install or configure x18n, or when `.vscode/settings.json` has `i18n-ally.*` settings.
4
+ ---
5
+
6
+ # Set up x18n
7
+
8
+ x18n reads a project's i18n setup (locale files, translate calls in code) and often needs no config. Set it up by checking what it detects, and write a config only when detection needs help.
9
+
10
+ ## Decisions belong to the user
11
+
12
+ Before you act on one of these, stop and ask, with your recommendation and the facts you found. Batch the questions into one message.
13
+
14
+ - the source locale, and which locales the project supports
15
+ - the config format: `x18n.config.ts`, or YAML for projects without Node.js tooling
16
+ - what an i18n-ally migration drops (see the reference below)
17
+ - adding `x18n check` to CI or to a git hook
18
+
19
+ ## Steps
20
+
21
+ 1. Run `npx @x18n/cli doctor` in the project root. Without a config, x18n uses the project's i18n-ally settings if it has any, and detects the i18n library and the locale files. Check the detected presets, the locale files and the source locale. **Done when** the doctor output names the library the project uses and lists every locale file.
22
+ 2. If the project has i18n-ally settings (`i18n-ally.*` keys in `.vscode/settings.json`), follow [references/migrate-i18n-ally.md](references/migrate-i18n-ally.md) instead of step 3.
23
+ 3. If detection is wrong or incomplete, run `x18n init` to write `x18n.config.ts`, then fix `resources`, `sourceLocale` or `presets` in it. `x18n init --format yml` writes YAML. **Done when** `x18n doctor` shows the right result.
24
+ 4. Install the CLI as a dev dependency with the project's package manager (`pnpm add -D @x18n/cli`), and add a script such as `"i18n:check": "x18n check"`. Ask the user before you add it to CI.
25
+ 5. Run `x18n check --json` and report the summary to the user: errors and warnings per check (`code` of each diagnostic) and coverage per locale. Do not fix anything in this skill. **Done when** the user has the counts and knows that the `x18n-check` skill fixes them.
26
+
27
+ Without the dev dependency, run commands as `npx @x18n/cli <command>`. In a monorepo, `x18n status --json` lists every project; pass `--project <name>` to pick one.