@x18n/cli 0.0.13 → 0.0.15
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 +139 -36
- package/dist/presets.d.mts +44 -1
- package/dist/presets.mjs +44 -2
- package/package.json +5 -5
- package/skills/x18n/SKILL.md +32 -6
- package/skills/x18n-setup/references/migrate-i18n-ally.md +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as out, c as runPlan, d as useColor, f as wireDiagnostics, i as openWorkspace, l as singleProject, n as displayPath, o as printJson, r as exitCodeOf, s as readInput, t as cwd, u as stdinArg } from "./context-j5IMF1tl.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { PluginFileSchema, errors, isJsonObject, isX18nError, parseMessageBatch, placeholderProblems, renderPrompt, toErrorInfo, toWireRange, validate } from "@x18n/core";
|
|
3
|
+
import { PluginFileSchema, errors, isJsonObject, isX18nError, parseMessageBatch, placeholderProblems, renderExplanation, renderPrompt, toErrorInfo, toWireRange, validate } from "@x18n/core";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { styleText } from "node:util";
|
|
6
6
|
import cac from "cac";
|
|
@@ -15,7 +15,7 @@ import { existsSync } from "node:fs";
|
|
|
15
15
|
import { namespaceFromPath } from "@x18n/builtins/finders";
|
|
16
16
|
import { parse, stringify } from "yaml";
|
|
17
17
|
//#region package.json
|
|
18
|
-
var version = "0.0.
|
|
18
|
+
var version = "0.0.15";
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/commands/change.ts
|
|
21
21
|
async function project(options) {
|
|
@@ -104,13 +104,13 @@ async function format(options) {
|
|
|
104
104
|
}
|
|
105
105
|
//#endregion
|
|
106
106
|
//#region src/commands/check.ts
|
|
107
|
-
function list(value) {
|
|
107
|
+
function list$1(value) {
|
|
108
108
|
return value === void 0 ? [] : [value].flat().flatMap((item) => String(item).split(",")).map((item) => item.trim()).filter(Boolean);
|
|
109
109
|
}
|
|
110
110
|
function filterDiagnostics(diagnostics, options) {
|
|
111
|
-
const locales = list(options.locale);
|
|
112
|
-
const only = list(options.only);
|
|
113
|
-
const skip = list(options.skip);
|
|
111
|
+
const locales = list$1(options.locale);
|
|
112
|
+
const only = list$1(options.only);
|
|
113
|
+
const skip = list$1(options.skip);
|
|
114
114
|
return diagnostics.filter((d) => locales.length === 0 || d.locale === void 0 || locales.includes(d.locale) || d.code === "missing-translation" && locales.some((l) => d.message.includes(l))).filter((d) => only.length === 0 || only.includes(d.code)).filter((d) => !skip.includes(d.code)).map((d) => options.strict && d.severity === "warning" ? {
|
|
115
115
|
...d,
|
|
116
116
|
severity: "error"
|
|
@@ -138,14 +138,14 @@ function reportInputs(workspace, options) {
|
|
|
138
138
|
function needsHistory(loaded, options) {
|
|
139
139
|
const setting = loaded.project.config.checks["stale-translation"];
|
|
140
140
|
const level = typeof setting === "string" ? setting : setting?.[0];
|
|
141
|
-
const only = list(options.only);
|
|
142
|
-
return level !== "off" && (only.length === 0 || only.includes("stale-translation")) && !list(options.skip).includes("stale-translation");
|
|
141
|
+
const only = list$1(options.only);
|
|
142
|
+
return level !== "off" && (only.length === 0 || only.includes("stale-translation")) && !list$1(options.skip).includes("stale-translation");
|
|
143
143
|
}
|
|
144
144
|
async function report(workspace, options) {
|
|
145
145
|
const inputs = reportInputs(workspace, options);
|
|
146
|
-
const reporterIds = list(options.reporter);
|
|
146
|
+
const reporterIds = list$1(options.reporter);
|
|
147
147
|
const ids = reporterIds.length ? reporterIds : [options.json ? "json" : "pretty"];
|
|
148
|
-
const outputs = list(options.output);
|
|
148
|
+
const outputs = list$1(options.output);
|
|
149
149
|
const available = workspace.projects[0]?.project.reporters ?? /* @__PURE__ */ new Map();
|
|
150
150
|
const roots = new Map(workspace.projects.map((p) => [p.name, p.root]));
|
|
151
151
|
for (const [i, id] of ids.entries()) {
|
|
@@ -201,7 +201,7 @@ async function readPlan(file) {
|
|
|
201
201
|
return data.items;
|
|
202
202
|
}
|
|
203
203
|
/** One question per string in a TTY: extract with the proposed key, a typed key, or skip. */
|
|
204
|
-
async function review(candidates, root) {
|
|
204
|
+
async function review$1(candidates, root) {
|
|
205
205
|
const rl = createInterface({
|
|
206
206
|
input: process.stdin,
|
|
207
207
|
output: process.stderr
|
|
@@ -258,7 +258,7 @@ async function extract(files, options) {
|
|
|
258
258
|
out(`${candidates.length} strings. Apply with \`x18n extract --yes\`, or review with \`x18n extract --plan plan.json\`.`);
|
|
259
259
|
return;
|
|
260
260
|
}
|
|
261
|
-
const chosen = interactive ? await review(candidates, loaded.root) : candidates;
|
|
261
|
+
const chosen = interactive ? await review$1(candidates, loaded.root) : candidates;
|
|
262
262
|
if (chosen.length === 0) {
|
|
263
263
|
out("Nothing to extract.");
|
|
264
264
|
return;
|
|
@@ -368,6 +368,39 @@ function locate(loaded, path, start, end) {
|
|
|
368
368
|
column: range.column
|
|
369
369
|
};
|
|
370
370
|
}
|
|
371
|
+
async function search(query, options) {
|
|
372
|
+
const workspace = await openWorkspace(options);
|
|
373
|
+
const projects = workspace.projects.map((loaded) => {
|
|
374
|
+
const { project } = loaded;
|
|
375
|
+
const { items, total, nextCursor } = page(project.search(String(query), { exact: options.exact }), options);
|
|
376
|
+
return {
|
|
377
|
+
name: loaded.name,
|
|
378
|
+
total,
|
|
379
|
+
nextCursor,
|
|
380
|
+
matches: items.map((m) => ({
|
|
381
|
+
key: m.key,
|
|
382
|
+
locale: m.locale,
|
|
383
|
+
value: m.value,
|
|
384
|
+
usages: project.usages({ key: m.key }).map((u) => ({
|
|
385
|
+
possible: u.possible,
|
|
386
|
+
...locate(loaded, u.usage.path, u.usage.range.start, u.usage.range.end)
|
|
387
|
+
}))
|
|
388
|
+
}))
|
|
389
|
+
};
|
|
390
|
+
});
|
|
391
|
+
if (options.json) {
|
|
392
|
+
printJson("search", { projects });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
for (const p of projects) {
|
|
396
|
+
if (workspace.projects.length > 1) out(`# ${p.name}`);
|
|
397
|
+
for (const m of p.matches) {
|
|
398
|
+
out(`${m.key} ${m.locale} ${JSON.stringify(m.value)}`);
|
|
399
|
+
for (const u of m.usages) out(` ${u.file}:${u.line}:${u.column}${u.possible ? " (possible)" : ""}`);
|
|
400
|
+
}
|
|
401
|
+
if (p.nextCursor) out(`… ${p.total - Number(p.nextCursor)} more (--cursor ${p.nextCursor})`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
371
404
|
async function get(input, options) {
|
|
372
405
|
const workspace = await openWorkspace(options);
|
|
373
406
|
const projects = workspace.projects.flatMap((loaded) => {
|
|
@@ -390,7 +423,8 @@ async function get(input, options) {
|
|
|
390
423
|
raw: u.usage.raw,
|
|
391
424
|
...locate(loaded, u.usage.path, u.usage.range.start, u.usage.range.end)
|
|
392
425
|
})),
|
|
393
|
-
diagnostics: wireDiagnostics(loaded, info.diagnostics)
|
|
426
|
+
diagnostics: wireDiagnostics(loaded, info.diagnostics),
|
|
427
|
+
reviews: info.reviews
|
|
394
428
|
}];
|
|
395
429
|
});
|
|
396
430
|
if (options.json) {
|
|
@@ -405,6 +439,7 @@ async function get(input, options) {
|
|
|
405
439
|
out(` ${p.usages.length} usages`);
|
|
406
440
|
for (const u of p.usages) out(` ${u.file}:${u.line}:${u.column}${u.possible ? " (possible)" : ""}`);
|
|
407
441
|
for (const d of p.diagnostics) out(` ${d.severity} ${d.code}: ${d.message}`);
|
|
442
|
+
for (const thread of p.reviews) out(` review ${thread.locale}: ${thread.state ?? "resolved"}, ${thread.comments.length} comment${thread.comments.length === 1 ? "" : "s"}`);
|
|
408
443
|
}
|
|
409
444
|
}
|
|
410
445
|
async function usages(input, options) {
|
|
@@ -458,25 +493,7 @@ async function doctor(options) {
|
|
|
458
493
|
printJson("doctor", { projects });
|
|
459
494
|
return;
|
|
460
495
|
}
|
|
461
|
-
for (const e of projects)
|
|
462
|
-
out(`# ${e.name} (${e.root})`);
|
|
463
|
-
out(`config: ${e.configFile ?? "none (detection only)"}`);
|
|
464
|
-
out(`presets:`);
|
|
465
|
-
const applied = e.presets.filter((p) => p.reason === "detected" || p.reason === "listed");
|
|
466
|
-
for (const p of applied) out(` ✓ ${p.name} (${p.reason})`);
|
|
467
|
-
if (applied.length === 0) out(" none");
|
|
468
|
-
if (e.presets.length > applied.length) out(` ${e.presets.length - applied.length} others not applied (--json lists them)`);
|
|
469
|
-
out("finders:");
|
|
470
|
-
for (const f of e.finders) out(` ${f.id}: ${f.resources.length} resources${f.resources.length ? `\n ${f.resources.slice(0, 20).join("\n ")}` : ""}`);
|
|
471
|
-
out("formats:");
|
|
472
|
-
for (const f of e.formats.filter((f) => f.resources > 0)) out(` ${f.id} (${f.extensions.join(", ")}): ${f.resources} resources`);
|
|
473
|
-
out("scanners:");
|
|
474
|
-
for (const s of e.scanners) out(` ${s.id} (${s.extensions.join(", ") || "none"}): ${s.files} files`);
|
|
475
|
-
if (e.conflicts.length) out(`conflicts:\n${e.conflicts.map((c) => ` .${c.extension}: ${c.winner} wins over ${c.loser}`).join("\n")}`);
|
|
476
|
-
if (e.errors.length) out(`errors:\n${e.errors.map((err) => ` ${err.name} ${err.why}`).join("\n")}`);
|
|
477
|
-
out("config origins:");
|
|
478
|
-
for (const [key, origin] of Object.entries(e.config.origins)) out(` ${key}: ${origin}`);
|
|
479
|
-
}
|
|
496
|
+
for (const e of projects) out(renderExplanation(e, e.configFile));
|
|
480
497
|
}
|
|
481
498
|
//#endregion
|
|
482
499
|
//#region src/commands/migrate.ts
|
|
@@ -526,10 +543,94 @@ async function prompt(kind, options) {
|
|
|
526
543
|
else out(renderPrompt(data));
|
|
527
544
|
}
|
|
528
545
|
//#endregion
|
|
546
|
+
//#region src/commands/review.ts
|
|
547
|
+
const commentTypes = {
|
|
548
|
+
"comment": "comment",
|
|
549
|
+
"approve": "approve",
|
|
550
|
+
"request-change": "request-change",
|
|
551
|
+
"suggest": "suggestion"
|
|
552
|
+
};
|
|
553
|
+
/** `Ada <ada@example.com>`, `Ada` or `ada@example.com`. */
|
|
554
|
+
function parseAuthor(input) {
|
|
555
|
+
const text = input.trim();
|
|
556
|
+
const open = text.lastIndexOf("<");
|
|
557
|
+
if (open >= 0 && text.endsWith(">")) {
|
|
558
|
+
const name = text.slice(0, open).trim();
|
|
559
|
+
return {
|
|
560
|
+
...name && { name },
|
|
561
|
+
email: text.slice(open + 1, -1).trim()
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return text.includes("@") ? { email: text } : { name: text };
|
|
565
|
+
}
|
|
566
|
+
function required(value, flag) {
|
|
567
|
+
if (value === void 0 || value === "") throw errors.X18N4008({ reason: `pass ${flag}` });
|
|
568
|
+
return String(value);
|
|
569
|
+
}
|
|
570
|
+
async function list(target, options) {
|
|
571
|
+
const workspace = await openWorkspace(options);
|
|
572
|
+
const locale = options.locale === void 0 ? void 0 : String([options.locale].flat()[0]);
|
|
573
|
+
const projects = workspace.projects.map(({ name, project }) => ({
|
|
574
|
+
name,
|
|
575
|
+
threads: project.reviews({
|
|
576
|
+
key: target === void 0 ? void 0 : project.parseKey(target),
|
|
577
|
+
locale,
|
|
578
|
+
open: options.open
|
|
579
|
+
})
|
|
580
|
+
}));
|
|
581
|
+
if (options.json) {
|
|
582
|
+
printJson("review list", { projects });
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const threads = projects.flatMap((p) => p.threads);
|
|
586
|
+
if (threads.length === 0) out(options.open ? "No open reviews." : "No reviews.");
|
|
587
|
+
for (const thread of threads) {
|
|
588
|
+
out(`${thread.key} ${thread.locale}${thread.state ? ` ${thread.state.replace("-", " ")}` : ""}`);
|
|
589
|
+
for (const c of thread.comments) {
|
|
590
|
+
const flags = [c.resolved && "resolved", c.stale && "stale"].filter(Boolean).join(", ");
|
|
591
|
+
out(` ${c.id} ${c.type.padEnd(14)} ${c.author?.name ?? c.author?.email ?? "-"} ${c.time.slice(0, 10)}${flags ? ` (${flags})` : ""}`);
|
|
592
|
+
if (c.text) out(` ${c.text}`);
|
|
593
|
+
if (c.suggestion !== void 0) out(` suggests ${JSON.stringify(c.suggestion)}`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/** `x18n review <action> [target]`: list, comment, approve, request-change, suggest, resolve, apply-suggestion, mark-fresh. */
|
|
598
|
+
async function review(action, target, options) {
|
|
599
|
+
if (action === "list") return list(target, options);
|
|
600
|
+
const loaded = singleProject(await openWorkspace(options));
|
|
601
|
+
const { project } = loaded;
|
|
602
|
+
const locales = options.locale === void 0 ? void 0 : [options.locale].flat().map(String);
|
|
603
|
+
let op;
|
|
604
|
+
const type = commentTypes[action];
|
|
605
|
+
if (type) {
|
|
606
|
+
const locale = locales?.[0];
|
|
607
|
+
if (locale === void 0) throw errors.X18N4008({ reason: "pass --locale <locale>" });
|
|
608
|
+
op = {
|
|
609
|
+
type: "review",
|
|
610
|
+
key: project.parseKey(required(target, "<key>")),
|
|
611
|
+
locale,
|
|
612
|
+
review: type,
|
|
613
|
+
text: type === "request-change" ? required(options.text, "--text <text>") : options.text === void 0 ? void 0 : String(options.text),
|
|
614
|
+
suggestion: type === "suggestion" ? required(options.value, "--value <value>") : void 0,
|
|
615
|
+
author: options.author === void 0 ? await project.reviewAuthor() : parseAuthor(options.author)
|
|
616
|
+
};
|
|
617
|
+
} else if (action === "resolve" || action === "apply-suggestion") op = {
|
|
618
|
+
type: action === "resolve" ? "resolve-review" : "apply-suggestion",
|
|
619
|
+
id: required(target, "<id>")
|
|
620
|
+
};
|
|
621
|
+
else if (action === "mark-fresh") op = {
|
|
622
|
+
type: "mark-fresh",
|
|
623
|
+
key: project.parseKey(required(target, "<key>")),
|
|
624
|
+
locales
|
|
625
|
+
};
|
|
626
|
+
else throw errors.X18N4008({ reason: `unknown review action "${action}" (list, comment, approve, request-change, suggest, resolve, apply-suggestion, mark-fresh)` });
|
|
627
|
+
await runPlan(`review ${action}`, loaded, project.plan([op]), options);
|
|
628
|
+
}
|
|
629
|
+
//#endregion
|
|
529
630
|
//#region src/commands/setup.ts
|
|
530
631
|
async function schema(name, options) {
|
|
531
632
|
const which = name ?? "config";
|
|
532
|
-
if (which !== "config" && which !== "plugin") throw errors.X18N0001({ issue: `unknown schema "${which}" (config, plugin)` });
|
|
633
|
+
if (which !== "config" && which !== "plugin" && which !== "reviews") throw errors.X18N0001({ issue: `unknown schema "${which}" (config, plugin, reviews)` });
|
|
533
634
|
const require = createRequire(import.meta.url);
|
|
534
635
|
const text = await readFile(require.resolve(`@x18n/core/schema/${which}.json`), "utf8");
|
|
535
636
|
if (options.json) printJson("schema", {
|
|
@@ -617,7 +718,7 @@ function ejectPlugin(data) {
|
|
|
617
718
|
const imports = ["definePlugin", ...data.detect ? ["definePreset"] : []];
|
|
618
719
|
lines.push(`import { ${imports.join(", ")} } from '@x18n/core'`);
|
|
619
720
|
if (data.placeholders) lines.push(`import { placeholderFromShape } from '@x18n/builtins/placeholders'`);
|
|
620
|
-
if (data.scopes) lines.push(`import
|
|
721
|
+
if (data.scopes) lines.push(`import regexScopes from '@x18n/builtins/scanners/regex-scopes'`);
|
|
621
722
|
lines.push("", `const plugin = definePlugin({`, ...fields, `})`, "");
|
|
622
723
|
const plugins = `[plugin${data.scopes ? `, regexScopes(${literal(data.scopes.regex)})` : ""}]`;
|
|
623
724
|
if (data.detect) {
|
|
@@ -676,10 +777,11 @@ function createCli() {
|
|
|
676
777
|
cli.option("--root <dir>", "Project root (default: nearest config upward from the cwd)").option("--config <file>", "Explicit config file").option("--project <name>", "Pick one project in a monorepo").option("--json", "Print the JSON contract instead of human output").option("--no-cache", "Ignore the persistent cache").option("--yes", "Accept default answers for decisions");
|
|
677
778
|
cli.command("", "Show the status and the main commands").action(run("status", async (options) => {
|
|
678
779
|
await status(options);
|
|
679
|
-
if (!options.json) process.stdout.write("\nCommands: check, keys, get, usages, set, apply, rename, delete, fill, prompt translate, ui. Run `x18n --help` for all.\n");
|
|
780
|
+
if (!options.json) process.stdout.write("\nCommands: check, keys, search, get, usages, set, apply, rename, delete, fill, prompt translate, ui. Run `x18n --help` for all.\n");
|
|
680
781
|
}));
|
|
681
782
|
cli.command("status", "Locales with coverage, resources, keys and diagnostics").action(run("status", status));
|
|
682
783
|
cli.command("keys [glob]", "List keys").option("--missing <locale>", "Only keys missing or empty in a locale").option("--unused", "Only keys without usages").option("--namespace <ns>", "Only keys of a namespace").option("--values", "Include the value per locale").option("--limit <n>", "Page size").option("--cursor <cursor>", "Start of the page (from `nextCursor`)").action(run("keys", keys));
|
|
784
|
+
cli.command("search <query>", "Find keys by their value in any locale, with their usages").option("--exact", "Only values containing the query, instead of a fuzzy match").option("--limit <n>", "Page size").option("--cursor <cursor>", "Start of the page").action(run("search", search));
|
|
683
785
|
cli.command("get <key>", "Values, resources, usages and diagnostics of one key").action(run("get", get));
|
|
684
786
|
cli.command("usages [key]", "Usages of a key, or all usages").option("--file <path>", "Only usages in a file").option("--limit <n>", "Page size").option("--cursor <cursor>", "Start of the page").action(run("usages", usages));
|
|
685
787
|
cli.command("locales", "Locale codes as found, with display names and resources").action(run("locales", locales));
|
|
@@ -693,12 +795,13 @@ function createCli() {
|
|
|
693
795
|
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));
|
|
694
796
|
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));
|
|
695
797
|
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));
|
|
798
|
+
cli.command("review <action> [target]", "Reviews: `list [key]`, `comment|approve|request-change|suggest <key>`, `resolve|apply-suggestion <id>`, `mark-fresh <key>`").option("--locale <locale>", "Locale of the message (repeatable for mark-fresh)").option("--text <text>", "Comment text").option("--value <value>", "Suggested value (suggest)").option("--author <author>", "Author as `Name <email>` (default: review.author, then git user)").option("--open", "Only unresolved reviews (list)").option("--dry-run", "Print the plan instead of writing").action(run("review", review));
|
|
696
799
|
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)));
|
|
697
800
|
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)));
|
|
698
801
|
cli.command("mcp", "Serve the MCP tools over stdio").action(run("mcp", async (options) => (await uiCommands()).mcp(options)));
|
|
699
802
|
cli.command("init", "Detect the setup and write x18n.config.ts").option("--format <format>", "ts (default), yml or json").option("--force", "Replace an existing config").action(run("init", init));
|
|
700
803
|
cli.command("migrate <source>", "Convert i18n-ally settings: `migrate i18n-ally`").option("--format <format>", "ts (default), yml or json").option("--dry-run", "Print the files instead of writing").option("--force", "Replace an existing config").action(run("migrate", migrate));
|
|
701
|
-
cli.command("schema [name]", "Print a JSON Schema: config (default) or
|
|
804
|
+
cli.command("schema [name]", "Print a JSON Schema: config (default), plugin or reviews").action(run("schema", schema));
|
|
702
805
|
cli.command("plugin <action> <file>", "Plugin tools: `plugin eject <file.yml>` prints the TS equivalent").action(run("plugin eject", async (action, file, options) => {
|
|
703
806
|
if (action !== "eject") throw new Error(`unknown plugin action "${action}"`);
|
|
704
807
|
await eject(file, options);
|
package/dist/presets.d.mts
CHANGED
|
@@ -1 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
import { cargoI18n } from "@x18n/builtins/presets/cargo-i18n";
|
|
2
|
+
import { chromeExtension } from "@x18n/builtins/presets/chrome-extension";
|
|
3
|
+
import { dotnetResx } from "@x18n/builtins/presets/dotnet-resx";
|
|
4
|
+
import { easyLocalization } from "@x18n/builtins/presets/easy-localization";
|
|
5
|
+
import { emberIntl } from "@x18n/builtins/presets/ember-intl";
|
|
6
|
+
import { fluentReact } from "@x18n/builtins/presets/fluent-react";
|
|
7
|
+
import { fluentVue } from "@x18n/builtins/presets/fluent-vue";
|
|
8
|
+
import { flutter } from "@x18n/builtins/presets/flutter";
|
|
9
|
+
import { flutterTranslate } from "@x18n/builtins/presets/flutter-translate";
|
|
10
|
+
import { general } from "@x18n/builtins/presets/general";
|
|
11
|
+
import { globalize } from "@x18n/builtins/presets/globalize";
|
|
12
|
+
import { gravityUiI18n } from "@x18n/builtins/presets/gravity-ui-i18n";
|
|
13
|
+
import { i18nTag } from "@x18n/builtins/presets/i18n-tag";
|
|
14
|
+
import { i18next } from "@x18n/builtins/presets/i18next";
|
|
15
|
+
import { i18nextShopify } from "@x18n/builtins/presets/i18next-shopify";
|
|
16
|
+
import { i18nice } from "@x18n/builtins/presets/i18nice";
|
|
17
|
+
import { jekyll } from "@x18n/builtins/presets/jekyll";
|
|
18
|
+
import { laravel } from "@x18n/builtins/presets/laravel";
|
|
19
|
+
import { laravelVueI18n } from "@x18n/builtins/presets/laravel-vue-i18n";
|
|
20
|
+
import { lingui } from "@x18n/builtins/presets/lingui";
|
|
21
|
+
import { nextInternational } from "@x18n/builtins/presets/next-international";
|
|
22
|
+
import { nextIntl } from "@x18n/builtins/presets/next-intl";
|
|
23
|
+
import { nextTranslate } from "@x18n/builtins/presets/next-translate";
|
|
24
|
+
import { ngxTranslate } from "@x18n/builtins/presets/ngx-translate";
|
|
25
|
+
import { nuxtI18n } from "@x18n/builtins/presets/nuxt-i18n";
|
|
26
|
+
import { nuxtI18nMicro } from "@x18n/builtins/presets/nuxt-i18n-micro";
|
|
27
|
+
import { paraglide } from "@x18n/builtins/presets/paraglide";
|
|
28
|
+
import { phpGettext } from "@x18n/builtins/presets/php-gettext";
|
|
29
|
+
import { phpJoomla } from "@x18n/builtins/presets/php-joomla";
|
|
30
|
+
import { polyglot } from "@x18n/builtins/presets/polyglot";
|
|
31
|
+
import { pythonGettext } from "@x18n/builtins/presets/python-gettext";
|
|
32
|
+
import { reactI18next } from "@x18n/builtins/presets/react-i18next";
|
|
33
|
+
import { reactIntl } from "@x18n/builtins/presets/react-intl";
|
|
34
|
+
import { rubyI18nJs } from "@x18n/builtins/presets/ruby-i18n-js";
|
|
35
|
+
import { railsLazyKey, rubyRails } from "@x18n/builtins/presets/ruby-rails";
|
|
36
|
+
import { solidPrimitivesI18n } from "@x18n/builtins/presets/solid-primitives-i18n";
|
|
37
|
+
import { svelteI18n } from "@x18n/builtins/presets/svelte-i18n";
|
|
38
|
+
import { svelteI18next } from "@x18n/builtins/presets/svelte-i18next";
|
|
39
|
+
import { transloco } from "@x18n/builtins/presets/transloco";
|
|
40
|
+
import { typesafeI18n } from "@x18n/builtins/presets/typesafe-i18n";
|
|
41
|
+
import { ui5 } from "@x18n/builtins/presets/ui5";
|
|
42
|
+
import { vscodeExtension } from "@x18n/builtins/presets/vscode-extension";
|
|
43
|
+
import { vueI18n } from "@x18n/builtins/presets/vue-i18n";
|
|
44
|
+
export { cargoI18n, chromeExtension, dotnetResx, easyLocalization, emberIntl, fluentReact, fluentVue, flutter, flutterTranslate, general, globalize, gravityUiI18n, i18nTag, i18next, i18nextShopify, i18nice, jekyll, laravel, laravelVueI18n, lingui, nextInternational, nextIntl, nextTranslate, ngxTranslate, nuxtI18n, nuxtI18nMicro, paraglide, phpGettext, phpJoomla, polyglot, pythonGettext, railsLazyKey, reactI18next, reactIntl, rubyI18nJs, rubyRails, solidPrimitivesI18n, svelteI18n, svelteI18next, transloco, typesafeI18n, ui5, vscodeExtension, vueI18n };
|
package/dist/presets.mjs
CHANGED
|
@@ -1,2 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { cargoI18n } from "@x18n/builtins/presets/cargo-i18n";
|
|
2
|
+
import { chromeExtension } from "@x18n/builtins/presets/chrome-extension";
|
|
3
|
+
import { dotnetResx } from "@x18n/builtins/presets/dotnet-resx";
|
|
4
|
+
import { easyLocalization } from "@x18n/builtins/presets/easy-localization";
|
|
5
|
+
import { emberIntl } from "@x18n/builtins/presets/ember-intl";
|
|
6
|
+
import { fluentReact } from "@x18n/builtins/presets/fluent-react";
|
|
7
|
+
import { fluentVue } from "@x18n/builtins/presets/fluent-vue";
|
|
8
|
+
import { flutter } from "@x18n/builtins/presets/flutter";
|
|
9
|
+
import { flutterTranslate } from "@x18n/builtins/presets/flutter-translate";
|
|
10
|
+
import { general } from "@x18n/builtins/presets/general";
|
|
11
|
+
import { globalize } from "@x18n/builtins/presets/globalize";
|
|
12
|
+
import { gravityUiI18n } from "@x18n/builtins/presets/gravity-ui-i18n";
|
|
13
|
+
import { i18nTag } from "@x18n/builtins/presets/i18n-tag";
|
|
14
|
+
import { i18next } from "@x18n/builtins/presets/i18next";
|
|
15
|
+
import { i18nextShopify } from "@x18n/builtins/presets/i18next-shopify";
|
|
16
|
+
import { i18nice } from "@x18n/builtins/presets/i18nice";
|
|
17
|
+
import { jekyll } from "@x18n/builtins/presets/jekyll";
|
|
18
|
+
import { laravel } from "@x18n/builtins/presets/laravel";
|
|
19
|
+
import { laravelVueI18n } from "@x18n/builtins/presets/laravel-vue-i18n";
|
|
20
|
+
import { lingui } from "@x18n/builtins/presets/lingui";
|
|
21
|
+
import { nextInternational } from "@x18n/builtins/presets/next-international";
|
|
22
|
+
import { nextIntl } from "@x18n/builtins/presets/next-intl";
|
|
23
|
+
import { nextTranslate } from "@x18n/builtins/presets/next-translate";
|
|
24
|
+
import { ngxTranslate } from "@x18n/builtins/presets/ngx-translate";
|
|
25
|
+
import { nuxtI18n } from "@x18n/builtins/presets/nuxt-i18n";
|
|
26
|
+
import { nuxtI18nMicro } from "@x18n/builtins/presets/nuxt-i18n-micro";
|
|
27
|
+
import { paraglide } from "@x18n/builtins/presets/paraglide";
|
|
28
|
+
import { phpGettext } from "@x18n/builtins/presets/php-gettext";
|
|
29
|
+
import { phpJoomla } from "@x18n/builtins/presets/php-joomla";
|
|
30
|
+
import { polyglot } from "@x18n/builtins/presets/polyglot";
|
|
31
|
+
import { pythonGettext } from "@x18n/builtins/presets/python-gettext";
|
|
32
|
+
import { reactI18next } from "@x18n/builtins/presets/react-i18next";
|
|
33
|
+
import { reactIntl } from "@x18n/builtins/presets/react-intl";
|
|
34
|
+
import { rubyI18nJs } from "@x18n/builtins/presets/ruby-i18n-js";
|
|
35
|
+
import { railsLazyKey, rubyRails } from "@x18n/builtins/presets/ruby-rails";
|
|
36
|
+
import { solidPrimitivesI18n } from "@x18n/builtins/presets/solid-primitives-i18n";
|
|
37
|
+
import { svelteI18n } from "@x18n/builtins/presets/svelte-i18n";
|
|
38
|
+
import { svelteI18next } from "@x18n/builtins/presets/svelte-i18next";
|
|
39
|
+
import { transloco } from "@x18n/builtins/presets/transloco";
|
|
40
|
+
import { typesafeI18n } from "@x18n/builtins/presets/typesafe-i18n";
|
|
41
|
+
import { ui5 } from "@x18n/builtins/presets/ui5";
|
|
42
|
+
import { vscodeExtension } from "@x18n/builtins/presets/vscode-extension";
|
|
43
|
+
import { vueI18n } from "@x18n/builtins/presets/vue-i18n";
|
|
44
|
+
export { cargoI18n, chromeExtension, dotnetResx, easyLocalization, emberIntl, fluentReact, fluentVue, flutter, flutterTranslate, general, globalize, gravityUiI18n, i18nTag, i18next, i18nextShopify, i18nice, jekyll, laravel, laravelVueI18n, lingui, nextInternational, nextIntl, nextTranslate, ngxTranslate, nuxtI18n, nuxtI18nMicro, paraglide, phpGettext, phpJoomla, polyglot, pythonGettext, railsLazyKey, reactI18next, reactIntl, rubyI18nJs, rubyRails, solidPrimitivesI18n, svelteI18n, svelteI18next, transloco, typesafeI18n, ui5, vscodeExtension, vueI18n };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x18n/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.15",
|
|
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",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"node": ">=22"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@x18n/builtins": "0.0.
|
|
42
|
-
"@x18n/core": "0.0.
|
|
43
|
-
"@x18n/node": "0.0.
|
|
44
|
-
"@x18n/ui": "0.0.
|
|
41
|
+
"@x18n/builtins": "0.0.15",
|
|
42
|
+
"@x18n/core": "0.0.15",
|
|
43
|
+
"@x18n/node": "0.0.15",
|
|
44
|
+
"@x18n/ui": "0.0.15",
|
|
45
45
|
"cac": "^7.1.0",
|
|
46
46
|
"devframe": "^1.2.6",
|
|
47
47
|
"nostics": "^1.3.0",
|
package/skills/x18n/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: x18n
|
|
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.
|
|
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 the key behind a text shown in the app, 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
|
|
@@ -23,7 +23,7 @@ Batch the questions into one message. Routine work needs no question: adding a k
|
|
|
23
23
|
## Rules
|
|
24
24
|
|
|
25
25
|
- Change locale files through `x18n set`, `x18n apply`, `x18n rename` and `x18n delete`. The config file (`x18n.config.ts`) is the one i18n file you edit by hand.
|
|
26
|
-
- Check a key before you use it in code: `x18n get <key> --json`. Reuse an existing key with the same meaning.
|
|
26
|
+
- Check a key before you use it in code: `x18n get <key> --json`. Reuse an existing key with the same meaning: `x18n search "<text>" --json` finds keys whose value already says it.
|
|
27
27
|
- Keep every placeholder of the source message (`{name}`, `{{count}}`, `%{name}`, `:name`, `%s`, `@:key`).
|
|
28
28
|
- Pass `--json` and read the envelope `{ "x18n": 2, "command", "data", "diagnostics", "errors" }`.
|
|
29
29
|
- Run renames and deletes with `--dry-run` first, and read the plan.
|
|
@@ -85,9 +85,16 @@ import { defineConfig } from '@x18n/core'
|
|
|
85
85
|
|
|
86
86
|
export default defineConfig({
|
|
87
87
|
glossary: [
|
|
88
|
-
{
|
|
88
|
+
{
|
|
89
|
+
term: 'Workspace',
|
|
90
|
+
translations: { de: 'Arbeitsbereich', fr: 'Espace de travail' }
|
|
91
|
+
},
|
|
89
92
|
{ term: 'x18n', doNotTranslate: true },
|
|
90
|
-
{
|
|
93
|
+
{
|
|
94
|
+
term: 'Save',
|
|
95
|
+
translations: { de: 'Speichern' },
|
|
96
|
+
note: 'The button label, not "rescue"'
|
|
97
|
+
},
|
|
91
98
|
],
|
|
92
99
|
checks: { glossary: 'warning' },
|
|
93
100
|
})
|
|
@@ -101,15 +108,27 @@ Maintain it while you work:
|
|
|
101
108
|
|
|
102
109
|
## Loops
|
|
103
110
|
|
|
111
|
+
### Find the key behind a text
|
|
112
|
+
|
|
113
|
+
When the user names a text they see in the app, and not a key, search the values:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
x18n search "Sign in" --json
|
|
117
|
+
x18n search "Anmelden" --exact --json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The search covers every locale and ignores case. It is fuzzy by default, and values that contain the query rank first. `--exact` keeps only those. Each match in `data.projects[].matches` has `key`, `locale`, `value` and `usages` (`file`, `line`, `column`), so you can open the code that shows the text.
|
|
121
|
+
|
|
104
122
|
### Add a key while coding
|
|
105
123
|
|
|
106
124
|
```sh
|
|
125
|
+
x18n search "Settings" --exact --json
|
|
107
126
|
x18n get settings.title --json
|
|
108
127
|
x18n set settings.title --value "Settings" --json
|
|
109
128
|
x18n keys --missing de --json --limit 50
|
|
110
129
|
```
|
|
111
130
|
|
|
112
|
-
`x18n set` without `--locale` writes the source locale. Then use the key in code, for example `t('settings.title')`.
|
|
131
|
+
Reuse a key that `x18n search` finds with the same meaning. `x18n set` without `--locale` writes the source locale. Then use the key in code, for example `t('settings.title')`.
|
|
113
132
|
|
|
114
133
|
### Translate missing messages
|
|
115
134
|
|
|
@@ -132,7 +151,14 @@ x18n check --only placeholder-mismatch,invalid-syntax,glossary --locale de --jso
|
|
|
132
151
|
x18n prompt review --locale de --limit 50
|
|
133
152
|
```
|
|
134
153
|
|
|
135
|
-
|
|
154
|
+
Do not change values yourself. For each wrong translation, leave a review for a human to accept, then list what you reviewed:
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
x18n review suggest <key> --locale de --value <better text> --text <why> --json
|
|
158
|
+
x18n review request-change <key> --locale de --text <why> --json
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Keep every placeholder in a suggested value. `x18n review list --locale de --open --json` shows the open reviews. Only when the user asks, apply one with `x18n review apply-suggestion <id> --json`. When reviews are off (`review.enabled: false`, error X18N5003), fix values with `x18n set <key> --locale de --value <text> --json` instead.
|
|
136
162
|
|
|
137
163
|
### Clean up
|
|
138
164
|
|
|
@@ -11,11 +11,11 @@ Use this when a project has i18n-ally settings: `i18n-ally.*`, legacy `vue-i18n-
|
|
|
11
11
|
- `languageTagSystem` is dropped: x18n uses locale codes exactly as they appear in file names.
|
|
12
12
|
- A custom framework with `monopoly: true` turns the built-in presets off (`presets: []`).
|
|
13
13
|
- Executed JS/TS locale files become read-only (`execute: true`).
|
|
14
|
-
- Reviews
|
|
14
|
+
- Reviews are converted to `.x18n/reviews.yml`; key descriptions and `review.removeCommentOnResolved` are dropped.
|
|
15
15
|
- The config format: `x18n.config.ts` by default, or `--format yml` / `--format json` for projects without Node.js tooling.
|
|
16
16
|
4. Write the files: `x18n migrate i18n-ally --json` (add `--format yml` if the user chose YAML; `--force` replaces an existing x18n config). It writes:
|
|
17
17
|
- `x18n.config.ts` (or `.yml`, `.json`) from the project settings
|
|
18
|
-
-
|
|
18
|
+
- `.x18n/custom.x18n-plugin.yml`, when there are custom regexes, a custom framework, refactor templates or `localeCountryMap` flags
|
|
19
19
|
- editor settings in `.vscode/settings.json`: `x18n.displayLocale`, `x18n.annotationInPlace`, `x18n.annotations`, `x18n.annotationMaxLength`, `x18n.showFlags`
|
|
20
20
|
- `.x18n/reviews.yml`, when `.vscode/i18n-ally-reviews.yml` or `.vscode/i18n-ally-next-reviews.yml` exists
|
|
21
21
|
|