@the-i18n-kit/cli 4.9.0 → 5.0.0

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.
Files changed (87) hide show
  1. package/README.md +31 -298
  2. package/dist/{_shared-Dv1EW2gy.js → _shared-DFyy3sh8.js} +24 -12
  3. package/dist/_shared-DFyy3sh8.js.map +1 -0
  4. package/dist/{add-BvXrJkqZ.js → add-C-qs8iBa.js} +5 -5
  5. package/dist/{add-BvXrJkqZ.js.map → add-C-qs8iBa.js.map} +1 -1
  6. package/dist/bin.js +6 -1
  7. package/dist/bin.js.map +1 -1
  8. package/dist/{check-9dcNfHtC.js → check-BI1orjXb.js} +11 -19
  9. package/dist/check-BI1orjXb.js.map +1 -0
  10. package/dist/cli-DsFuVJNP.js +74 -0
  11. package/dist/cli-DsFuVJNP.js.map +1 -0
  12. package/dist/config/framework/stubs/{next-intl-routing-DEwC7Bbk.d.ts → next-intl-routing-Cwaa3f4R.d.ts} +1 -1
  13. package/dist/config/framework/stubs/{next-intl-routing-DEwC7Bbk.d.ts.map → next-intl-routing-Cwaa3f4R.d.ts.map} +1 -1
  14. package/dist/config/framework/stubs/{unplugin-vue-i18n-D9yaDzRk.d.ts → unplugin-vue-i18n-1ud7-Ly8.d.ts} +1 -1
  15. package/dist/config/framework/stubs/{unplugin-vue-i18n-D9yaDzRk.d.ts.map → unplugin-vue-i18n-1ud7-Ly8.d.ts.map} +1 -1
  16. package/dist/define-config-4ubtfvUy.js.map +1 -1
  17. package/dist/{define-config-Dhg13592.d.ts → define-config-BaZ1dZph.d.ts} +11 -4
  18. package/dist/define-config-BaZ1dZph.d.ts.map +1 -0
  19. package/dist/{define-config-lQrIpPSK.d.ts → define-config-CLgKN-7N.d.ts} +1 -1
  20. package/dist/define-config.d.ts +1 -1
  21. package/dist/{detect-Dr2skxeL.js → detect-7cVfsNJP.js} +5 -5
  22. package/dist/{detect-Dr2skxeL.js.map → detect-7cVfsNJP.js.map} +1 -1
  23. package/dist/{empty-p0wct8Hk.js → empty-DB8Hl_8u.js} +5 -5
  24. package/dist/{empty-p0wct8Hk.js.map → empty-DB8Hl_8u.js.map} +1 -1
  25. package/dist/find-duplicates-CmEqBfby.js +42 -0
  26. package/dist/find-duplicates-CmEqBfby.js.map +1 -0
  27. package/dist/{get-BskDC2wT.js → get-BE21Iy6N.js} +5 -5
  28. package/dist/{get-BskDC2wT.js.map → get-BE21Iy6N.js.map} +1 -1
  29. package/dist/{index-BtSA6-iT.d.ts → index-DCG3dOdC.d.ts} +351 -3
  30. package/dist/index-DCG3dOdC.d.ts.map +1 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +5 -4
  33. package/dist/{init-D7r6iX3q.js → init-BtzmhsOM.js} +5 -5
  34. package/dist/{init-D7r6iX3q.js.map → init-BtzmhsOM.js.map} +1 -1
  35. package/dist/{list-dirs-BHk38JXZ.js → list-dirs-DUQbwvlE.js} +5 -5
  36. package/dist/{list-dirs-BHk38JXZ.js.map → list-dirs-DUQbwvlE.js.map} +1 -1
  37. package/dist/{missing-0gSZKTgt.js → missing-CcYrgd54.js} +5 -5
  38. package/dist/{missing-0gSZKTgt.js.map → missing-CcYrgd54.js.map} +1 -1
  39. package/dist/move-B2px6Ck-.js +50 -0
  40. package/dist/move-B2px6Ck-.js.map +1 -0
  41. package/dist/{operations-BCJd7in3.js → operations-CTo44gPu.js} +1332 -241
  42. package/dist/operations-CTo44gPu.js.map +1 -0
  43. package/dist/php-reader-3Fgw80zK.js +64 -0
  44. package/dist/php-reader-3Fgw80zK.js.map +1 -0
  45. package/dist/php-reader-CpnaPSpZ.js +2 -0
  46. package/dist/{providers-BAwPjeCy.js → providers-CHE2ffi6.js} +53 -27
  47. package/dist/providers-CHE2ffi6.js.map +1 -0
  48. package/dist/{remove-DR_mRiMA.js → remove-DTbqM4bR.js} +5 -5
  49. package/dist/{remove-DR_mRiMA.js.map → remove-DTbqM4bR.js.map} +1 -1
  50. package/dist/{remove-orphans-CcA3XQHJ.js → remove-orphans-Dfzu7hRz.js} +5 -5
  51. package/dist/{remove-orphans-CcA3XQHJ.js.map → remove-orphans-Dfzu7hRz.js.map} +1 -1
  52. package/dist/{rename-CWLfThC1.js → rename-C_rFKkU4.js} +5 -5
  53. package/dist/{rename-CWLfThC1.js.map → rename-C_rFKkU4.js.map} +1 -1
  54. package/dist/rename-notice-BV8HNX3O.js +25 -0
  55. package/dist/rename-notice-BV8HNX3O.js.map +1 -0
  56. package/dist/rename-notice-Cx1vuTRz.js +2 -0
  57. package/dist/{scaffold-BahXKbo9.js → scaffold-CUimlNqR.js} +5 -5
  58. package/dist/{scaffold-BahXKbo9.js.map → scaffold-CUimlNqR.js.map} +1 -1
  59. package/dist/{scan-CTxs3kl7.js → scan-BkOIYhjZ.js} +5 -5
  60. package/dist/{scan-CTxs3kl7.js.map → scan-BkOIYhjZ.js.map} +1 -1
  61. package/dist/{search-BonEgmSt.js → search-0io1BcsY.js} +5 -5
  62. package/dist/{search-BonEgmSt.js.map → search-0io1BcsY.js.map} +1 -1
  63. package/dist/{status-C_5Khw14.js → status-Bp-SzjTN.js} +5 -5
  64. package/dist/{status-C_5Khw14.js.map → status-Bp-SzjTN.js.map} +1 -1
  65. package/dist/{translate-CnXvBi5O.js → translate-cCb2Z57w.js} +6 -6
  66. package/dist/{translate-CnXvBi5O.js.map → translate-cCb2Z57w.js.map} +1 -1
  67. package/dist/{translate-key-DwO9XZMc.js → translate-key-DHLWEmrA.js} +5 -5
  68. package/dist/{translate-key-DwO9XZMc.js.map → translate-key-DHLWEmrA.js.map} +1 -1
  69. package/dist/{update-HR8MvPlI.js → update-DV4ijRVB.js} +5 -5
  70. package/dist/{update-HR8MvPlI.js.map → update-DV4ijRVB.js.map} +1 -1
  71. package/dist/{write-CAD9xQDy.js → write-Czzm_Nb0.js} +5 -5
  72. package/dist/{write-CAD9xQDy.js.map → write-Czzm_Nb0.js.map} +1 -1
  73. package/package.json +15 -5
  74. package/dist/_shared-Dv1EW2gy.js.map +0 -1
  75. package/dist/check-9dcNfHtC.js.map +0 -1
  76. package/dist/cli-CiX4llY1.js +0 -82
  77. package/dist/cli-CiX4llY1.js.map +0 -1
  78. package/dist/define-config-Dhg13592.d.ts.map +0 -1
  79. package/dist/find-duplicates-DuI8niF7.js +0 -31
  80. package/dist/find-duplicates-DuI8niF7.js.map +0 -1
  81. package/dist/index-BtSA6-iT.d.ts.map +0 -1
  82. package/dist/operations-BCJd7in3.js.map +0 -1
  83. package/dist/php-reader-DcgOAhTw.js +0 -2
  84. package/dist/php-reader-DuZ0Hyl_.js +0 -32
  85. package/dist/php-reader-DuZ0Hyl_.js.map +0 -1
  86. package/dist/providers-BAwPjeCy.js.map +0 -1
  87. /package/dist/{bin-Cs-dvpEC.d.ts → bin-DrKRgnr9.d.ts} +0 -0
@@ -0,0 +1,64 @@
1
+ import { i as toErrorMessage, n as FileIOError } from "./errors-coI1dhw1.js";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { existsSync } from "node:fs";
5
+ import { readFile, stat } from "node:fs/promises";
6
+ //#region src/io/php-reader.ts
7
+ /**
8
+ * `php-array-reader` is an optional peer (#406): only Laravel projects carry
9
+ * it. Resolved from the locale file outward, so a CLI running from the npx
10
+ * cache finds the project's install. Unlike the scanner's parser, locale IO
11
+ * has no fallback to degrade to — a missing install is an error naming the
12
+ * one-line fix.
13
+ */
14
+ const LARAVEL_INSTALL_HINT = "Laravel projects need the PHP packages installed: npm i -D php-parser php-array-reader";
15
+ const fromStringPromises = /* @__PURE__ */ new Map();
16
+ function loadFromString(fromFile) {
17
+ const key = dirname(fromFile);
18
+ let promise = fromStringPromises.get(key);
19
+ if (!promise) {
20
+ promise = resolveFromString(fromFile);
21
+ fromStringPromises.set(key, promise);
22
+ }
23
+ return promise;
24
+ }
25
+ async function resolveFromString(fromFile) {
26
+ try {
27
+ return createRequire(isAbsolute(fromFile) ? fromFile : join(process.cwd(), fromFile))("php-array-reader").fromString;
28
+ } catch {}
29
+ try {
30
+ return (await import("php-array-reader")).fromString;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ const fileCache = /* @__PURE__ */ new Map();
36
+ function clearPhpFileCacheEntry(filePath) {
37
+ fileCache.delete(filePath);
38
+ }
39
+ async function readPhpLocaleFile(filePath) {
40
+ if (!existsSync(filePath)) throw new FileIOError(`File not found: ${filePath}`, filePath, "FILE_NOT_FOUND");
41
+ try {
42
+ const mtime = (await stat(filePath)).mtimeMs;
43
+ const cached = fileCache.get(filePath);
44
+ if (cached && cached.mtime === mtime) return structuredClone(cached.data);
45
+ const content = await readFile(filePath, "utf-8");
46
+ const fromString = await loadFromString(filePath);
47
+ if (!fromString) throw new FileIOError(`Cannot read PHP locale files: php-array-reader is not installed. ${LARAVEL_INSTALL_HINT}`, filePath);
48
+ const parsed = fromString(content);
49
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new FileIOError(`PHP locale file did not return an associative array: ${filePath}`, filePath);
50
+ const data = parsed;
51
+ fileCache.set(filePath, {
52
+ data: structuredClone(data),
53
+ mtime
54
+ });
55
+ return data;
56
+ } catch (error) {
57
+ if (error instanceof FileIOError) throw error;
58
+ throw new FileIOError(`Failed to read PHP locale file: ${filePath}: ${toErrorMessage(error)}`, filePath);
59
+ }
60
+ }
61
+ //#endregion
62
+ export { readPhpLocaleFile as n, clearPhpFileCacheEntry as t };
63
+
64
+ //# sourceMappingURL=php-reader-3Fgw80zK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"php-reader-3Fgw80zK.js","names":[],"sources":["../src/io/php-reader.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, isAbsolute, join } from 'node:path'\nimport { FileIOError, toErrorMessage } from '../utils/errors'\n\n/**\n * `php-array-reader` is an optional peer (#406): only Laravel projects carry\n * it. Resolved from the locale file outward, so a CLI running from the npx\n * cache finds the project's install. Unlike the scanner's parser, locale IO\n * has no fallback to degrade to — a missing install is an error naming the\n * one-line fix.\n */\nconst LARAVEL_INSTALL_HINT = 'Laravel projects need the PHP packages installed: npm i -D php-parser php-array-reader'\n\ntype FromString = (content: string) => unknown\n// Keyed by the locale file's directory — see the scanner's parser cache: a\n// long-lived server must not pin resolution (or a cached failure) to the\n// first project it served.\nconst fromStringPromises = new Map<string, Promise<FromString | null>>()\n\nfunction loadFromString(fromFile: string): Promise<FromString | null> {\n const key = dirname(fromFile)\n let promise = fromStringPromises.get(key)\n if (!promise) {\n promise = resolveFromString(fromFile)\n fromStringPromises.set(key, promise)\n }\n return promise\n}\n\nasync function resolveFromString(fromFile: string): Promise<FromString | null> {\n try {\n const from = isAbsolute(fromFile) ? fromFile : join(process.cwd(), fromFile)\n return (createRequire(from)('php-array-reader') as { fromString: FromString }).fromString\n } catch { /* project has no install of its own */ }\n try {\n return (await import('php-array-reader')).fromString\n } catch {\n return null\n }\n}\n\nconst fileCache = new Map<string, { data: Record<string, unknown>; mtime: number }>()\n\nexport function clearPhpFileCache(): void {\n fileCache.clear()\n}\n\nexport function clearPhpFileCacheEntry(filePath: string): void {\n fileCache.delete(filePath)\n}\n\nexport async function readPhpLocaleFile(filePath: string): Promise<Record<string, unknown>> {\n if (!existsSync(filePath)) {\n throw new FileIOError(`File not found: ${filePath}`, filePath, 'FILE_NOT_FOUND')\n }\n\n try {\n const fileStat = await stat(filePath)\n const mtime = fileStat.mtimeMs\n\n const cached = fileCache.get(filePath)\n if (cached && cached.mtime === mtime) {\n return structuredClone(cached.data)\n }\n\n const content = await readFile(filePath, 'utf-8')\n const fromString = await loadFromString(filePath)\n if (!fromString) {\n throw new FileIOError(`Cannot read PHP locale files: php-array-reader is not installed. ${LARAVEL_INSTALL_HINT}`, filePath)\n }\n const parsed = fromString(content)\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new FileIOError(\n `PHP locale file did not return an associative array: ${filePath}`,\n filePath,\n )\n }\n\n const data = parsed as Record<string, unknown>\n fileCache.set(filePath, { data: structuredClone(data), mtime })\n return data\n }\n catch (error) {\n if (error instanceof FileIOError) throw error\n throw new FileIOError(\n `Failed to read PHP locale file: ${filePath}: ${toErrorMessage(error)}`,\n filePath,\n )\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,uBAAuB;AAM7B,MAAM,qCAAqB,IAAI,KAAyC;AAExE,SAAS,eAAe,UAA8C;CACpE,MAAM,MAAM,QAAQ,SAAS;CAC7B,IAAI,UAAU,mBAAmB,IAAI,IAAI;AACzC,KAAI,CAAC,SAAS;AACZ,YAAU,kBAAkB,SAAS;AACrC,qBAAmB,IAAI,KAAK,QAAQ;;AAEtC,QAAO;;AAGT,eAAe,kBAAkB,UAA8C;AAC7E,KAAI;AAEF,SAAQ,cADK,WAAW,SAAS,GAAG,WAAW,KAAK,QAAQ,KAAK,EAAE,SAAS,CACjD,CAAC,mBAAmB,CAAgC;SACzE;AACR,KAAI;AACF,UAAQ,MAAM,OAAO,qBAAqB;SACpC;AACN,SAAO;;;AAIX,MAAM,4BAAY,IAAI,KAA+D;AAMrF,SAAgB,uBAAuB,UAAwB;AAC7D,WAAU,OAAO,SAAS;;AAG5B,eAAsB,kBAAkB,UAAoD;AAC1F,KAAI,CAAC,WAAW,SAAS,CACvB,OAAM,IAAI,YAAY,mBAAmB,YAAY,UAAU,iBAAiB;AAGlF,KAAI;EAEF,MAAM,SADW,MAAM,KAAK,SAAS,EACd;EAEvB,MAAM,SAAS,UAAU,IAAI,SAAS;AACtC,MAAI,UAAU,OAAO,UAAU,MAC7B,QAAO,gBAAgB,OAAO,KAAK;EAGrC,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ;EACjD,MAAM,aAAa,MAAM,eAAe,SAAS;AACjD,MAAI,CAAC,WACH,OAAM,IAAI,YAAY,oEAAoE,wBAAwB,SAAS;EAE7H,MAAM,SAAS,WAAW,QAAQ;AAElC,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CACxE,OAAM,IAAI,YACR,wDAAwD,YACxD,SACD;EAGH,MAAM,OAAO;AACb,YAAU,IAAI,UAAU;GAAE,MAAM,gBAAgB,KAAK;GAAE;GAAO,CAAC;AAC/D,SAAO;UAEF,OAAO;AACZ,MAAI,iBAAiB,YAAa,OAAM;AACxC,QAAM,IAAI,YACR,mCAAmC,SAAS,IAAI,eAAe,MAAM,IACrE,SACD"}
@@ -0,0 +1,2 @@
1
+ import { n as readPhpLocaleFile } from "./php-reader-3Fgw80zK.js";
2
+ export { readPhpLocaleFile };
@@ -20,35 +20,46 @@ const log = createConsola({
20
20
  * `nuxt.config.ts` all accept this same shape, and all validate against this
21
21
  * same schema. The `ProjectConfig` interface in `./types.ts` documents it;
22
22
  * the guard at the bottom of this file fails the build if the two drift.
23
+ *
24
+ * The `.describe()` text on each field is the published documentation of that
25
+ * field: `./schema-json.ts` converts this schema into `packages/mcp/schema.json`,
26
+ * which editors read for validation and tooltips. Write it for a user looking
27
+ * at their own config file, not for a reader of this module.
23
28
  */
24
29
  const nonEmptyString = z.string().min(1).refine((s) => s.trim().length > 0, "Must not be empty or whitespace-only");
25
30
  const layerRuleSchema = z.object({
26
- layer: z.string(),
27
- description: z.string(),
28
- when: z.string()
31
+ layer: z.string().describe("Layer name (e.g., 'root', 'app-admin', 'app-shop', 'lang')."),
32
+ description: z.string().describe("What this layer contains — key namespaces, domain scope, etc."),
33
+ when: z.string().describe("Natural-language rule describing when a key should go in this layer.")
29
34
  });
30
- const localeDirEntrySchema = z.union([nonEmptyString, z.object({
31
- path: nonEmptyString,
32
- layer: nonEmptyString
35
+ const localeDirEntrySchema = z.union([nonEmptyString.describe("Relative path to a locale directory. Layer name defaults to 'default'."), z.object({
36
+ path: nonEmptyString.describe("Relative path to a locale directory."),
37
+ layer: nonEmptyString.describe("Layer name for this locale directory.")
33
38
  })]);
34
39
  const projectConfigSchema = z.object({
35
- $schema: z.string().optional(),
36
- framework: z.string().optional(),
37
- context: z.string().optional(),
38
- layerRules: z.array(layerRuleSchema).optional(),
39
- glossary: z.record(z.string(), z.string()).optional(),
40
- translationPrompt: z.string().optional(),
41
- localeNotes: z.record(z.string(), z.string()).optional(),
42
- examples: z.array(z.record(z.string(), z.string())).optional(),
43
- orphanScan: z.record(z.string(), z.object({ ignorePatterns: z.array(z.string()).optional() }).passthrough()).optional(),
44
- localeDirs: z.array(localeDirEntrySchema).optional(),
45
- defaultLocale: nonEmptyString.optional(),
46
- locales: z.array(nonEmptyString).optional(),
47
- protectedLocales: z.array(nonEmptyString).optional(),
48
- reportOutput: z.union([z.literal(true), nonEmptyString]).optional(),
49
- localeFileFormat: z.enum(["json", "php-array"]).optional(),
50
- providerBaseUrl: nonEmptyString.optional(),
51
- samplingPreferences: z.unknown().optional()
40
+ $schema: z.string().describe("Path or URL to the JSON schema for IDE autocompletion.").optional(),
41
+ framework: z.string().describe("Force framework detection instead of auto-detecting from project structure. Any registered adapter name is accepted — the suggestions are the adapters that ship today, not the only permitted values.").optional(),
42
+ context: z.string().describe("Free-form project background for the agent — business domain, user base, brand voice, anything that helps the agent understand the project.").optional(),
43
+ layerRules: z.array(layerRuleSchema).describe("Rules that help the agent decide which layer a new translation key belongs to. The agent interprets the natural-language 'when' field.").optional(),
44
+ glossary: z.record(z.string(), z.string()).describe("Term dictionary for consistent translations. Keys are source terms, values describe the required translation or usage note.").optional(),
45
+ translationPrompt: z.string().describe("System prompt prepended to all translation requests (provider mode and agent-mode fallback contexts). Sets tone, style, and constraints.").optional(),
46
+ localeNotes: z.record(z.string(), z.string()).describe("Per-locale context included in translation prompts. Keys are locale codes (e.g., 'de-DE', 'en-US', 'de-DE-formal'), values describe register, regional conventions, or other locale-specific guidance.").optional(),
47
+ examples: z.array(z.object({
48
+ key: z.string().describe("The dot-path translation key this example demonstrates (e.g., 'common.actions.save')."),
49
+ note: z.string().describe("Optional style comment shown alongside the example.").optional()
50
+ }).catchall(z.string()).describe("A single translation example. 'key' holds the dot-path translation key (e.g., 'common.actions.save'), 'note' an optional style comment, and every other property is a locale code (e.g., 'de-DE', 'en-US') mapped to its translated value.")).describe("Few-shot translation examples that demonstrate the project's style. The agent uses these as reference when generating translations.").optional(),
51
+ orphanScan: z.record(z.string(), z.object({ ignorePatterns: z.array(z.string()).describe("Glob patterns for translation keys to exclude from orphan detection (e.g., \"common.datetime.months.*\"). Use * to match a single key segment, ** to match any depth.").optional() }).passthrough()).describe("Per-layer configuration for orphan key detection. Map each layer name to its settings. Scan directories are automatically determined from each layer's root directory.").optional(),
52
+ localeDirs: z.array(localeDirEntrySchema).describe("Locale directories for the generic adapter. Each entry is a path string (layer defaults to 'default') or an object with 'path' and 'layer' properties.").optional(),
53
+ defaultLocale: nonEmptyString.describe("Default locale code. Required for generic adapter activation.").optional(),
54
+ locales: z.array(nonEmptyString).describe("Explicit list of locale codes to operate on. If absent, locales are auto-discovered from files on disk.").optional(),
55
+ protectedLocales: z.array(nonEmptyString).describe("Human-maintained locales excluded from automatic translation. Entries may be any locale ref (code, language tag, or file name); entries that do not match a known locale are ignored with a warning. Explicitly naming a protected locale in targetLocales overrides the protection (with a warning).").optional(),
56
+ reportOutput: z.union([z.literal(true).describe("Set to true to write reports to the default '.i18n-reports/' directory."), nonEmptyString.describe("Custom directory path (relative to project root) where diagnostic tool reports are written.")]).describe("Enable file output for diagnostic tools (get_missing_translations, search_translations, find_orphan_keys, remove_orphan_keys). When set, each tool writes its full JSON report to <reportOutput>/<toolName>.json and returns only a summary in the MCP response. Set to true for the default '.i18n-reports/' directory, or a string for a custom path.").optional(),
57
+ localeFileFormat: z.enum(["json", "php-array"]).describe("Override the auto-detected locale file format. Useful when both formats exist or auto-detection picks wrong.").optional(),
58
+ providerBaseUrl: nonEmptyString.describe("Base URL for the LLM provider — gateways, self-hosted model servers and corporate proxies that speak the provider's own protocol. Overrides the endpoint only, not the request shape or auth header. Overridden by the I18N_BASE_URL environment variable and by --baseUrl. Not supported by the \"google\" provider, which rejects it rather than ignoring it.").optional(),
59
+ samplingPreferences: z.unknown().meta({
60
+ deprecated: true,
61
+ description: "Deprecated and ignored — MCP sampling was removed. Still accepted so existing config files keep validating. Configure a provider instead (e.g., I18N_PROVIDER/I18N_MODEL)."
62
+ }).optional()
52
63
  }).strict();
53
64
  /**
54
65
  * Validate a candidate project config against the published schema, returning
@@ -158,7 +169,7 @@ async function loadTypedConfig(projectDir) {
158
169
  const commonJs = path.endsWith(".cts") || path.endsWith(".cjs");
159
170
  if (!("default" in module) && !commonJs) throw new ConfigError(`${path} must export its config as the default export — write \`export default defineI18nKitConfig({ ... })\`.`);
160
171
  const exported = "default" in module ? module.default : module;
161
- if (exported === null || typeof exported !== "object" || Array.isArray(exported)) throw new ConfigError(`${path} must export a config object as its default export — got ${describe(exported)}. Wrap it in defineI18nKitConfig() from "the-i18n-cli/config".`);
172
+ if (exported === null || typeof exported !== "object" || Array.isArray(exported)) throw new ConfigError(`${path} must export a config object as its default export — got ${describe(exported)}. Wrap it in defineI18nKitConfig() from "${CONFIG_SPECIFIER}".`);
162
173
  const result = validateProjectConfig(exported);
163
174
  if (!result.ok) throw new ConfigError(`Invalid ${path}:\n${result.error}`);
164
175
  return {
@@ -167,7 +178,15 @@ async function loadTypedConfig(projectDir) {
167
178
  };
168
179
  }
169
180
  /**
170
- * Point `the-i18n-cli/config` at the copy of `defineI18nKitConfig` that is
181
+ * The specifier the documentation tells you to import from. Both spellings
182
+ * publish the same code at the same versions (#315), so a config file may
183
+ * legitimately name either — but only one of them belongs in a message that
184
+ * tells someone what to write.
185
+ */
186
+ const CONFIG_SPECIFIER = "@the-i18n-kit/cli/config";
187
+ const DEPRECATED_CONFIG_SPECIFIER = "the-i18n-cli/config";
188
+ /**
189
+ * Point the `/config` specifiers at the copy of `defineI18nKitConfig` that is
171
190
  * already running.
172
191
  *
173
192
  * A config file imports it for the types, and types only need the package
@@ -176,6 +195,10 @@ async function loadTypedConfig(projectDir) {
176
195
  * unresolvable and take the whole config down with it. Resolving it to
177
196
  * ourselves costs nothing and is the same function either way: identity.
178
197
  *
198
+ * Both spellings are aliased: the rescue path is worth nothing if it covers a
199
+ * name the docs no longer recommend, and the deprecated one has to keep working
200
+ * until the window in #344 closes.
201
+ *
179
202
  * Returns no alias when the entry cannot be located, which leaves normal
180
203
  * resolution to find the installed package.
181
204
  */
@@ -186,7 +209,10 @@ function selfAlias() {
186
209
  "../define-config.ts"
187
210
  ]) {
188
211
  const path = fileURLToPath(new URL(candidate, import.meta.url));
189
- if (existsSync(path)) return { "the-i18n-cli/config": path };
212
+ if (existsSync(path)) return {
213
+ [CONFIG_SPECIFIER]: path,
214
+ [DEPRECATED_CONFIG_SPECIFIER]: path
215
+ };
190
216
  }
191
217
  return {};
192
218
  }
@@ -479,4 +505,4 @@ async function createTranslateFn(config) {
479
505
  //#endregion
480
506
  export { resolveProviderBaseUrl as a, validateProjectConfig as c, createTranslateFn as i, log as l, TranslateProviderError as n, CONFIG_FILENAME as o, classifyProviderError as r, loadProjectConfig as s, BASE_URL_ENV as t };
481
507
 
482
- //# sourceMappingURL=providers-BAwPjeCy.js.map
508
+ //# sourceMappingURL=providers-CHE2ffi6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers-CHE2ffi6.js","names":[],"sources":["../src/utils/logger.ts","../src/config/schema.ts","../src/config/typed-config.ts","../src/config/project-config.ts","../src/llm/providers.ts"],"sourcesContent":["import { createConsola } from 'consola'\n\n// All diagnostics go to stderr — stdout is reserved for command results so\n// that `the-i18n-cli … | jq` and CI `$(…)` captures always receive pure JSON.\nexport const log = createConsola({\n stdout: process.stderr,\n stderr: process.stderr,\n}).withTag('the-i18n-cli')\n","/**\n * The one definition of the declared half of the kit's configuration —\n * everything a human tells the tool, as opposed to what it can see for itself.\n *\n * `.i18n-mcp.json`, `i18n-kit.config.ts` and the `i18nKit` block in\n * `nuxt.config.ts` all accept this same shape, and all validate against this\n * same schema. The `ProjectConfig` interface in `./types.ts` documents it;\n * the guard at the bottom of this file fails the build if the two drift.\n *\n * The `.describe()` text on each field is the published documentation of that\n * field: `./schema-json.ts` converts this schema into `packages/mcp/schema.json`,\n * which editors read for validation and tooltips. Write it for a user looking\n * at their own config file, not for a reader of this module.\n */\nimport { z } from 'zod'\nimport type { ProjectConfig } from './types.js'\nimport { log } from '../utils/logger.js'\n\nconst nonEmptyString = z.string().min(1).refine(s => s.trim().length > 0, 'Must not be empty or whitespace-only')\n\nconst layerRuleSchema = z.object({\n layer: z.string()\n .describe('Layer name (e.g., \\'root\\', \\'app-admin\\', \\'app-shop\\', \\'lang\\').'),\n description: z.string()\n .describe('What this layer contains — key namespaces, domain scope, etc.'),\n when: z.string()\n .describe('Natural-language rule describing when a key should go in this layer.'),\n})\n\nconst localeDirEntrySchema = z.union([\n nonEmptyString\n .describe('Relative path to a locale directory. Layer name defaults to \\'default\\'.'),\n z.object({\n path: nonEmptyString.describe('Relative path to a locale directory.'),\n layer: nonEmptyString.describe('Layer name for this locale directory.'),\n }),\n])\n\nexport const projectConfigSchema = z.object({\n $schema: z.string()\n .describe('Path or URL to the JSON schema for IDE autocompletion.')\n .optional(),\n framework: z.string()\n .describe(\n 'Force framework detection instead of auto-detecting from project structure. '\n + 'Any registered adapter name is accepted — the suggestions are the adapters '\n + 'that ship today, not the only permitted values.',\n )\n .optional(),\n context: z.string()\n .describe(\n 'Free-form project background for the agent — business domain, user base, brand '\n + 'voice, anything that helps the agent understand the project.',\n )\n .optional(),\n layerRules: z.array(layerRuleSchema)\n .describe(\n 'Rules that help the agent decide which layer a new translation key belongs to. '\n + 'The agent interprets the natural-language \\'when\\' field.',\n )\n .optional(),\n glossary: z.record(z.string(), z.string())\n .describe(\n 'Term dictionary for consistent translations. Keys are source terms, values '\n + 'describe the required translation or usage note.',\n )\n .optional(),\n translationPrompt: z.string()\n .describe(\n 'System prompt prepended to all translation requests (provider mode and agent-mode '\n + 'fallback contexts). Sets tone, style, and constraints.',\n )\n .optional(),\n localeNotes: z.record(z.string(), z.string())\n .describe(\n 'Per-locale context included in translation prompts. Keys are locale codes (e.g., '\n + '\\'de-DE\\', \\'en-US\\', \\'de-DE-formal\\'), values describe register, regional '\n + 'conventions, or other locale-specific guidance.',\n )\n .optional(),\n examples: z.array(\n // `key` is required because the prompt builder renders `- ${ex.key}: …`\n // unconditionally: an entry without one reached the provider as the literal\n // string \"undefined\", offered as a translation key to imitate (#367).\n z.object({\n key: z.string().describe('The dot-path translation key this example demonstrates (e.g., \\'common.actions.save\\').'),\n note: z.string().describe('Optional style comment shown alongside the example.').optional(),\n })\n .catchall(z.string())\n .describe(\n 'A single translation example. \\'key\\' holds the dot-path translation key (e.g., '\n + '\\'common.actions.save\\'), \\'note\\' an optional style comment, and every other '\n + 'property is a locale code (e.g., \\'de-DE\\', \\'en-US\\') mapped to its translated value.',\n ),\n )\n .describe(\n 'Few-shot translation examples that demonstrate the project\\'s style. The agent uses '\n + 'these as reference when generating translations.',\n )\n .optional(),\n orphanScan: z.record(z.string(), z.object({\n ignorePatterns: z.array(z.string())\n .describe(\n 'Glob patterns for translation keys to exclude from orphan detection (e.g., '\n + '\"common.datetime.months.*\"). Use * to match a single key segment, ** to match any depth.',\n )\n .optional(),\n // passthrough for backwards-compat with deprecated keys like includeParentLayer\n }).passthrough())\n .describe(\n 'Per-layer configuration for orphan key detection. Map each layer name to its settings. '\n + 'Scan directories are automatically determined from each layer\\'s root directory.',\n )\n .optional(),\n localeDirs: z.array(localeDirEntrySchema)\n .describe(\n 'Locale directories for the generic adapter. Each entry is a path string (layer defaults '\n + 'to \\'default\\') or an object with \\'path\\' and \\'layer\\' properties.',\n )\n .optional(),\n defaultLocale: nonEmptyString\n .describe('Default locale code. Required for generic adapter activation.')\n .optional(),\n locales: z.array(nonEmptyString)\n .describe(\n 'Explicit list of locale codes to operate on. If absent, locales are auto-discovered '\n + 'from files on disk.',\n )\n .optional(),\n protectedLocales: z.array(nonEmptyString)\n .describe(\n 'Human-maintained locales excluded from automatic translation. Entries may be any locale '\n + 'ref (code, language tag, or file name); entries that do not match a known locale are '\n + 'ignored with a warning. Explicitly naming a protected locale in targetLocales overrides '\n + 'the protection (with a warning).',\n )\n .optional(),\n reportOutput: z.union([\n z.literal(true)\n .describe('Set to true to write reports to the default \\'.i18n-reports/\\' directory.'),\n nonEmptyString\n .describe('Custom directory path (relative to project root) where diagnostic tool reports are written.'),\n ])\n .describe(\n 'Enable file output for diagnostic tools (get_missing_translations, search_translations, '\n + 'find_orphan_keys, remove_orphan_keys). When set, each tool writes its full JSON report to '\n + '<reportOutput>/<toolName>.json and returns only a summary in the MCP response. Set to true '\n + 'for the default \\'.i18n-reports/\\' directory, or a string for a custom path.',\n )\n .optional(),\n localeFileFormat: z.enum(['json', 'php-array'])\n .describe(\n 'Override the auto-detected locale file format. Useful when both formats exist or '\n + 'auto-detection picks wrong.',\n )\n .optional(),\n providerBaseUrl: nonEmptyString\n .describe(\n 'Base URL for the LLM provider — gateways, self-hosted model servers and corporate proxies '\n + 'that speak the provider\\'s own protocol. Overrides the endpoint only, not the request shape '\n + 'or auth header. Overridden by the I18N_BASE_URL environment variable and by --baseUrl. '\n + 'Not supported by the \"google\" provider, which rejects it rather than ignoring it.',\n )\n .optional(),\n // Deprecated with the removal of MCP sampling: accepted so existing config\n // files keep validating (the schema is strict), warned about, and ignored.\n samplingPreferences: z.unknown()\n .meta({\n deprecated: true,\n description:\n 'Deprecated and ignored — MCP sampling was removed. Still accepted so existing config '\n + 'files keep validating. Configure a provider instead (e.g., I18N_PROVIDER/I18N_MODEL).',\n })\n .optional(),\n}).strict()\n\n/**\n * Validate a candidate project config against the published schema, returning\n * the formatted issue list rather than throwing. `init` uses this to refuse to\n * emit a config the tool would then reject; the loaders turn the same failure\n * into a ConfigError naming the file it came from.\n */\nexport function validateProjectConfig(\n value: unknown,\n): { ok: true, data: ProjectConfig } | { ok: false, error: string } {\n const result = projectConfigSchema.safeParse(value)\n if (result.success) return { ok: true, data: result.data as ProjectConfig }\n\n const error = result.error.issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)'\n return ` ${path}: ${issue.message}`\n })\n .join('\\n')\n return { ok: false, error }\n}\n\n/**\n * Strip the deprecated keys the schema still accepts, warning once, so that a\n * config keeps validating without the value going on to mean anything. Applied\n * by every loader — a key ignored in `.i18n-mcp.json` but silently honoured in\n * `i18n-kit.config.ts` would be exactly the divergence this file exists to\n * prevent.\n */\nexport function dropDeprecatedKeys(config: ProjectConfig, source: string): ProjectConfig {\n const data = config as ProjectConfig & { samplingPreferences?: unknown }\n if ('samplingPreferences' in data) {\n log.warn(\n `${source}: \"samplingPreferences\" is deprecated and ignored — `\n + 'MCP sampling was removed. Configure a provider (e.g. I18N_PROVIDER/I18N_MODEL) instead.',\n )\n delete data.samplingPreferences\n }\n return data\n}\n\n// ─── Drift guard ────────────────────────────────────────────────\n//\n// A key added to the interface but not the schema is rejected at runtime as an\n// unknown key (the schema is strict) — a typed config that the editor accepts\n// and the CLI refuses. A key added to the schema but not the interface is\n// accepted at runtime and invisible in an editor. Both are the drift this\n// module exists to prevent, so both are a type error here.\n//\n// `$schema` is JSON-only, and `samplingPreferences` is accepted purely so old\n// files keep validating; neither belongs in the documented type.\n\ntype SchemaKey = Exclude<keyof z.infer<typeof projectConfigSchema>, '$schema' | 'samplingPreferences'>\n\n/** Fails to compile unless `T` is `never`, naming the offending key when not. */\ntype NoDrift<T extends never> = T\n\ntype _SchemaCoversType = NoDrift<Exclude<keyof ProjectConfig, SchemaKey>>\ntype _TypeCoversSchema = NoDrift<Exclude<SchemaKey, keyof ProjectConfig>>\n","/**\n * Loading `i18n-kit.config.ts` — the same configuration `.i18n-mcp.json`\n * carries, written in TypeScript so the editor checks it.\n *\n * Read with no build step, which is the whole point: a `protectedLocales` entry\n * that only takes effect once something has been built is a locale that quietly\n * goes unprotected in a fresh checkout. Everything here runs off the source\n * file as it sits on disk.\n */\nimport { existsSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { ProjectConfig } from './types.js'\nimport { dropDeprecatedKeys, validateProjectConfig } from './schema.js'\nimport { ConfigError, toErrorMessage } from '../utils/errors.js'\nimport { log } from '../utils/logger.js'\n\n/**\n * Candidate names, in the order a directory is searched. `.ts` first because\n * it is the one worth having; the rest exist so a JavaScript project is not\n * told to adopt TypeScript to get a typed config.\n */\nconst TYPED_CONFIG_FILENAMES = [\n 'i18n-kit.config.ts',\n 'i18n-kit.config.mts',\n 'i18n-kit.config.js',\n 'i18n-kit.config.mjs',\n 'i18n-kit.config.cjs',\n 'i18n-kit.config.cts',\n] as const\n\n/**\n * Walk up from `startDir` for the nearest `i18n-kit.config.*`, the same way\n * `findConfigFile` looks for `.i18n-mcp.json`. Both stop at the first\n * directory that has one, so a config in an app directory shadows one in the\n * repository root rather than merging with it.\n */\nexport function findTypedConfigFile(startDir: string): string | null {\n // Absolute from the outset: a relative `--projectDir` would otherwise yield\n // a relative config path, and jiti reads a relative path as a bare module\n // specifier — resolved against node_modules, where it is never found.\n let dir = resolve(startDir)\n while (true) {\n const found = TYPED_CONFIG_FILENAMES\n .map(name => join(dir, name))\n .filter(path => existsSync(path))\n\n if (found[0]) {\n if (found.length > 1) {\n log.warn(\n `Several i18n-kit configs in ${dir} (${found.map(p => p.slice(dir.length + 1)).join(', ')}). `\n + `Using ${found[0].slice(dir.length + 1)} — delete the others, they are being ignored.`,\n )\n }\n return found[0]\n }\n\n const parent = dirname(dir)\n if (parent === dir) break // filesystem root\n dir = parent\n }\n return null\n}\n\n/**\n * Load and validate the nearest typed config, or null when there isn't one.\n *\n * Throws, unlike the framework-config readers this issue also adds. Those read\n * a file someone wrote for another tool, so failing to read it means falling\n * back to a guess. This file exists only for the kit: if it throws, or declares\n * a key that does not exist, carrying on without it is precisely the silent\n * non-application the typed config was added to prevent.\n */\nexport async function loadTypedConfig(\n projectDir: string,\n): Promise<{ path: string, config: ProjectConfig } | null> {\n const path = findTypedConfigFile(projectDir)\n if (!path) {\n log.debug(`No i18n-kit.config.* found in ${projectDir} or any parent directory — skipping`)\n return null\n }\n\n log.info(`Found typed project config: ${path}`)\n\n let module: unknown\n try {\n const { createJiti } = await import('jiti')\n // interopDefault stays off so that a file with only named exports is\n // distinguishable from one with a default: with the interop on, the whole\n // namespace comes back and the schema rejects it as a config full of\n // unknown keys, which explains nothing.\n const jiti = createJiti(import.meta.url, {\n interopDefault: false,\n alias: selfAlias(),\n })\n module = await jiti.import(path)\n }\n catch (error) {\n throw new ConfigError(\n `Failed to load ${path}: ${toErrorMessage(error)}`,\n )\n }\n\n if (module === null || typeof module !== 'object') {\n throw new ConfigError(\n `${path} must export its config as the default export — `\n + 'write `export default defineI18nKitConfig({ ... })`.',\n )\n }\n\n // In a `.cts` file `module.exports` comes back as the module itself, with no\n // `default` to unwrap — so for the CommonJS spellings, that object *is* the\n // config. Everywhere else a missing `default` means a file of named exports,\n // and saying so beats letting the schema report a config full of unknown keys.\n const commonJs = path.endsWith('.cts') || path.endsWith('.cjs')\n if (!('default' in module) && !commonJs) {\n throw new ConfigError(\n `${path} must export its config as the default export — `\n + 'write `export default defineI18nKitConfig({ ... })`.',\n )\n }\n\n const exported = 'default' in module ? (module as { default: unknown }).default : module\n\n if (exported === null || typeof exported !== 'object' || Array.isArray(exported)) {\n throw new ConfigError(\n `${path} must export a config object as its default export — got ${describe(exported)}. `\n + `Wrap it in defineI18nKitConfig() from \"${CONFIG_SPECIFIER}\".`,\n )\n }\n\n const result = validateProjectConfig(exported)\n if (!result.ok) {\n throw new ConfigError(`Invalid ${path}:\\n${result.error}`)\n }\n\n return { path, config: dropDeprecatedKeys(result.data, path) }\n}\n\n/**\n * The specifier the documentation tells you to import from. Both spellings\n * publish the same code at the same versions (#315), so a config file may\n * legitimately name either — but only one of them belongs in a message that\n * tells someone what to write.\n */\nconst CONFIG_SPECIFIER = '@the-i18n-kit/cli/config'\nconst DEPRECATED_CONFIG_SPECIFIER = 'the-i18n-cli/config'\n\n/**\n * Point the `/config` specifiers at the copy of `defineI18nKitConfig` that is\n * already running.\n *\n * A config file imports it for the types, and types only need the package\n * installed at author time — but the CLI is often run through `npx` in a\n * project that never added it as a dependency, where a bare import would be\n * unresolvable and take the whole config down with it. Resolving it to\n * ourselves costs nothing and is the same function either way: identity.\n *\n * Both spellings are aliased: the rescue path is worth nothing if it covers a\n * name the docs no longer recommend, and the deprecated one has to keep working\n * until the window in #344 closes.\n *\n * Returns no alias when the entry cannot be located, which leaves normal\n * resolution to find the installed package.\n */\nfunction selfAlias(): Record<string, string> {\n // `./` when running from the bundle, where define-config.js sits beside the\n // chunk this code ends up in; `../` when running from source, as tests do.\n for (const candidate of ['./define-config.js', '../define-config.js', '../define-config.ts']) {\n const path = fileURLToPath(new URL(candidate, import.meta.url))\n if (existsSync(path)) {\n return { [CONFIG_SPECIFIER]: path, [DEPRECATED_CONFIG_SPECIFIER]: path }\n }\n }\n return {}\n}\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'an array'\n if (typeof value === 'function') return 'a function (call defineI18nKitConfig, do not pass it)'\n return typeof value\n}\n","import { readFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport type { ProjectConfig } from './types.js'\nimport { dropDeprecatedKeys, validateProjectConfig } from './schema.js'\nimport { loadTypedConfig } from './typed-config.js'\nimport { ConfigError, toErrorMessage } from '../utils/errors.js'\nimport { log } from '../utils/logger.js'\n\nexport const CONFIG_FILENAME = '.i18n-mcp.json'\n\n// The schema itself lives in ./schema.ts, shared with the typed-config loader.\n// Re-exported here because this has been its import path since before there\n// was a second config format.\nexport { validateProjectConfig } from './schema.js'\n\n// ─── Config file discovery ──────────────────────────────────────\n\n/**\n * Walk up the directory tree from `startDir` to find the nearest config file.\n * Returns the full path if found, null otherwise.\n */\nexport function findConfigFile(startDir: string): string | null {\n let dir = startDir\n while (true) {\n const candidate = join(dir, CONFIG_FILENAME)\n if (existsSync(candidate)) {\n return candidate\n }\n const parent = dirname(dir)\n if (parent === dir) break // filesystem root\n dir = parent\n }\n return null\n}\n\n/**\n * Load the project's declared configuration, from either place it can be\n * declared: `i18n-kit.config.ts` and `.i18n-mcp.json`. Both are searched from\n * projectDir upwards, the way ESLint, Prettier and tsconfig resolve configs.\n *\n * Returns null when neither exists — the case that must keep behaving exactly\n * as it did before there were two. Throws ConfigError when either file is\n * present but unusable, or when the two disagree.\n *\n * Every adapter funnels through here, which is what makes the typed config\n * work for all of them rather than for whichever one was taught about it.\n */\nexport async function loadProjectConfig(projectDir: string): Promise<ProjectConfig | null> {\n const [json, typed] = await Promise.all([\n loadJsonConfig(projectDir),\n loadTypedConfig(projectDir),\n ])\n\n if (!typed) return json?.config ?? null\n if (!json) return typed.config\n\n const conflicts = conflictingKeys(typed.config, json.config)\n if (conflicts.length > 0) {\n throw new ConfigError(\n `${conflicts.join(', ')} ${conflicts.length === 1 ? 'is' : 'are'} declared in both\\n`\n + ` ${typed.path}\\n`\n + ` ${json.path}\\n`\n + 'Neither wins — a reader cannot tell which one took effect. '\n + 'Delete the duplicate from one of them.',\n )\n }\n\n // Disjoint by the check above, so the spread order only decides where\n // untouched keys come from, not who wins.\n return { ...json.config, ...typed.config }\n}\n\n/** Keys present in both files, ignoring the JSON-only `$schema`. */\nfunction conflictingKeys(typed: ProjectConfig, json: ProjectConfig): string[] {\n const a = typed as Record<string, unknown>\n const b = json as Record<string, unknown>\n return Object.keys(a).filter(key => key !== '$schema' && a[key] !== undefined && b[key] !== undefined)\n}\n\n/**\n * Load the optional `.i18n-mcp.json`, with the path it was found at.\n * Returns null if no config file is found in any ancestor.\n * Throws ConfigError if a file is found but is invalid.\n */\nasync function loadJsonConfig(\n projectDir: string,\n): Promise<{ path: string, config: ProjectConfig } | null> {\n log.debug(`Searching for ${CONFIG_FILENAME} starting from: ${projectDir}`)\n\n const configPath = findConfigFile(projectDir)\n\n if (!configPath) {\n log.info(`No ${CONFIG_FILENAME} found in ${projectDir} or any parent directory — skipping`)\n return null\n }\n\n log.info(`Found project config: ${configPath}`)\n\n let raw: string\n try {\n raw = await readFile(configPath, 'utf-8')\n } catch (error) {\n throw new ConfigError(\n `Failed to read ${CONFIG_FILENAME}: ${toErrorMessage(error)}`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (error) {\n throw new ConfigError(\n `Invalid JSON in ${CONFIG_FILENAME}: ${toErrorMessage(error)}`,\n )\n }\n\n const result = validateProjectConfig(parsed)\n\n if (!result.ok) {\n throw new ConfigError(`Invalid ${CONFIG_FILENAME}:\\n${result.error}`)\n }\n\n log.debug(`Project config loaded successfully from ${configPath}`)\n\n return { path: configPath, config: dropDeprecatedKeys(result.data, CONFIG_FILENAME) }\n}\n","import type { TranslateFn, TranslateRequest, TranslateResponse } from '../core/types.js'\n\nexport type LlmProvider = 'openai' | 'anthropic' | 'google'\n\n// ─── Error classification ───────────────────────────────────────\n\n/** How a provider failure should be handled by the caller. */\nexport type TranslateProviderErrorKind = 'auth' | 'rate-limit' | 'provider' | 'config'\n\n/**\n * A classified provider failure. `auth` errors are not retryable (the caller\n * should abort the run), `rate-limit` errors should be retried with backoff,\n * and `provider` covers everything else (server errors, network, …).\n * `config` marks an unusable provider setup and is raised while building the\n * TranslateFn, before any request exists — so it surfaces from the command\n * and never reaches the retry loop.\n */\nexport class TranslateProviderError extends Error {\n public readonly kind: TranslateProviderErrorKind\n public readonly status?: number\n\n constructor(message: string, kind: TranslateProviderErrorKind, status?: number) {\n super(message)\n this.name = 'TranslateProviderError'\n this.kind = kind\n this.status = status\n }\n}\n\n/** Defensively extract an HTTP status code from an unknown SDK error shape. */\nfunction extractStatus(error: unknown): number | undefined {\n if (error === null || typeof error !== 'object') return undefined\n const e = error as { status?: unknown, response?: unknown }\n if (typeof e.status === 'number') return e.status\n if (e.response !== null && typeof e.response === 'object') {\n const status = (e.response as { status?: unknown }).status\n if (typeof status === 'number') return status\n }\n return undefined\n}\n\n/**\n * Classify an SDK error into a TranslateProviderError:\n * 401/403 → auth, 429 → rate-limit, anything else → provider.\n * Already-classified errors pass through unchanged.\n */\nexport function classifyProviderError(error: unknown): TranslateProviderError {\n if (error instanceof TranslateProviderError) return error\n const status = extractStatus(error)\n const message = error instanceof Error ? error.message : String(error)\n const kind: TranslateProviderErrorKind\n = status === 401 || status === 403 ? 'auth'\n : status === 429 ? 'rate-limit'\n : 'provider'\n return new TranslateProviderError(message, kind, status)\n}\n\nexport interface LlmProviderConfig {\n provider: LlmProvider\n model: string\n /** Override API key. Falls back to env vars */\n apiKey?: string\n /** Base URL override for proxies / compatible APIs */\n baseUrl?: string\n}\n\n/** Environment variable carrying the provider base URL override. */\nexport const BASE_URL_ENV = 'I18N_BASE_URL'\n\n/**\n * Resolve the provider base URL from its three sources, highest precedence\n * first: an explicit flag, the I18N_BASE_URL env var, then the project\n * config's `providerBaseUrl`.\n *\n * Blank values count as unset. Shells produce them routinely — `--baseUrl\n * \"$UNSET\"` or an exported-but-empty variable — and a blank must not shadow a\n * real endpoint configured further down the chain. A blank in the config file\n * is a different case: it cannot arise by accident, so the strict schema\n * rejects it at load time rather than letting it reach this function.\n */\nexport function resolveProviderBaseUrl(sources: {\n flag?: string\n env?: string\n config?: string\n}): string | undefined {\n for (const value of [sources.flag, sources.env, sources.config]) {\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n }\n return undefined\n}\n\nconst ENV_KEY_MAP: Record<LlmProvider, string> = {\n openai: 'OPENAI_API_KEY',\n anthropic: 'ANTHROPIC_API_KEY',\n google: 'GEMINI_API_KEY',\n}\n\nfunction resolveApiKey(provider: LlmProvider, configKey?: string): string {\n if (configKey) return configKey\n const envKey = ENV_KEY_MAP[provider]\n const key = process.env[envKey]\n if (!key) {\n throw new Error(\n `No API key found for provider \"${provider}\". Set ${envKey} environment variable or pass apiKey in config.`,\n )\n }\n return key\n}\n\nasync function createOpenAiTranslateFn(config: LlmProviderConfig): Promise<TranslateFn> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic import of optional peer dep\n let OpenAI: any\n try {\n OpenAI = (await import('openai')).default as any // eslint-disable-line @typescript-eslint/no-explicit-any\n } catch {\n throw new Error(\n 'Provider \"openai\" requires the \"openai\" package. Install it with:\\n npm install openai\\n pnpm add openai\\n yarn add openai',\n )\n }\n\n const apiKey = resolveApiKey('openai', config.apiKey)\n const client = new OpenAI({ apiKey, baseURL: config.baseUrl })\n\n return async (opts: TranslateRequest): Promise<TranslateResponse> => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK types not available\n let response: any\n try {\n response = await client.chat.completions.create({\n model: config.model,\n messages: [\n { role: 'system', content: opts.systemPrompt },\n { role: 'user', content: opts.userMessage },\n ],\n max_tokens: opts.maxTokens,\n temperature: 0,\n response_format: { type: 'json_object' },\n })\n } catch (error) {\n throw classifyProviderError(error)\n }\n\n const choice = response.choices?.[0]\n const text = choice?.message?.content ?? ''\n const truncated = choice?.finish_reason === 'length'\n return { text, model: response.model || config.model, ...(truncated ? { truncated: true } : {}) }\n }\n}\n\nasync function createGoogleTranslateFn(config: LlmProviderConfig): Promise<TranslateFn> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic import of optional peer dep\n let GoogleGenAI: any\n try {\n GoogleGenAI = (await import('@google/genai')).GoogleGenAI as any // eslint-disable-line @typescript-eslint/no-explicit-any\n } catch {\n throw new Error(\n 'Provider \"google\" requires the \"@google/genai\" package. Install it with:\\n npm install @google/genai\\n pnpm add @google/genai\\n yarn add @google/genai',\n )\n }\n\n const apiKey = resolveApiKey('google', config.apiKey)\n const client = new GoogleGenAI({ apiKey })\n\n return async (opts: TranslateRequest): Promise<TranslateResponse> => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK types not available\n let response: any\n try {\n response = await client.models.generateContent({\n model: config.model,\n contents: opts.userMessage,\n config: {\n systemInstruction: opts.systemPrompt,\n maxOutputTokens: opts.maxTokens,\n temperature: 0,\n responseMimeType: 'application/json',\n },\n })\n } catch (error) {\n throw classifyProviderError(error)\n }\n\n const text = response.text ?? ''\n const truncated = response.candidates?.[0]?.finishReason === 'MAX_TOKENS'\n return { text, model: response.modelVersion || config.model, ...(truncated ? { truncated: true } : {}) }\n }\n}\n\nasync function createAnthropicTranslateFn(config: LlmProviderConfig): Promise<TranslateFn> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic import of optional peer dep\n let Anthropic: any\n try {\n Anthropic = (await import('@anthropic-ai/sdk')).Anthropic as any // eslint-disable-line @typescript-eslint/no-explicit-any\n } catch {\n throw new Error(\n 'Provider \"anthropic\" requires the \"@anthropic-ai/sdk\" package. Install it with:\\n npm install @anthropic-ai/sdk\\n pnpm add @anthropic-ai/sdk\\n yarn add @anthropic-ai/sdk',\n )\n }\n\n const apiKey = resolveApiKey('anthropic', config.apiKey)\n const client = new Anthropic({ apiKey, baseURL: config.baseUrl })\n\n return async (opts: TranslateRequest): Promise<TranslateResponse> => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK types not available\n let response: any\n try {\n response = await client.messages.create({\n model: config.model,\n system: opts.systemPrompt,\n messages: [{ role: 'user', content: opts.userMessage }],\n max_tokens: opts.maxTokens,\n temperature: 0,\n })\n } catch (error) {\n throw classifyProviderError(error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK types not available\n const textBlock = response.content?.find((block: any) => block.type === 'text')\n const text = textBlock?.text ?? ''\n const truncated = response.stop_reason === 'max_tokens'\n return { text, model: response.model || config.model, ...(truncated ? { truncated: true } : {}) }\n }\n}\n\n/**\n * Create a TranslateFn from an LLM provider config.\n * Throws if the provider SDK is not installed or API key is missing.\n */\nexport async function createTranslateFn(config: LlmProviderConfig): Promise<TranslateFn> {\n // @google/genai exposes no endpoint override, so a base URL here would be\n // silently ignored and translate against the wrong endpoint. Refuse loudly.\n if (config.provider === 'google' && config.baseUrl) {\n throw new TranslateProviderError(\n 'Provider \"google\" does not support a custom base URL. '\n + `Remove --baseUrl / ${BASE_URL_ENV} / providerBaseUrl, or use an OpenAI-compatible provider.`,\n 'config',\n )\n }\n\n switch (config.provider) {\n case 'openai':\n return createOpenAiTranslateFn(config)\n case 'anthropic':\n return createAnthropicTranslateFn(config)\n case 'google':\n return createGoogleTranslateFn(config)\n default: {\n const _exhaustive: never = config.provider\n throw new Error(`Unknown provider: ${_exhaustive}`)\n }\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { l as removeTranslations } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { o as splitList, t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { u as removeTranslations } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { o as splitList, t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/remove.ts
7
7
  var remove_default = createCommand({
8
8
  name: "remove",
@@ -38,4 +38,4 @@ var remove_default = createCommand({
38
38
  //#endregion
39
39
  export { remove_default as default };
40
40
 
41
- //# sourceMappingURL=remove-DR_mRiMA.js.map
41
+ //# sourceMappingURL=remove-DTbqM4bR.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remove-DR_mRiMA.js","names":[],"sources":["../src/commands/remove.ts"],"sourcesContent":["import { createCommand, splitList } from './_shared.js'\nimport { removeTranslations } from '../core/operations.js'\n\nexport default createCommand({\n name: 'remove',\n description: 'Remove translation keys from all locale files in a layer',\n args: {\n layer: { type: 'string', description: 'Layer name', required: true },\n keys: { type: 'string', description: 'Comma-separated key paths to remove', required: true },\n dryRun: { type: 'boolean', description: 'Preview changes without writing', default: false },\n },\n async run(args) {\n const keys = splitList(args.keys) ?? []\n if (keys.length === 0) {\n throw new Error('No keys provided. Pass comma-separated key paths via --keys')\n }\n return removeTranslations({ layer: args.layer, keys, dryRun: args.dryRun, projectDir: args.projectDir })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAAc,UAAU;GAAM;EACpE,MAAM;GAAE,MAAM;GAAU,aAAa;GAAuC,UAAU;GAAM;EAC5F,QAAQ;GAAE,MAAM;GAAW,aAAa;GAAmC,SAAS;GAAO;EAC5F;CACD,MAAM,IAAI,MAAM;EACd,MAAM,OAAO,UAAU,KAAK,KAAK,IAAI,EAAE;AACvC,MAAI,KAAK,WAAW,EAClB,OAAM,IAAI,MAAM,8DAA8D;AAEhF,SAAO,mBAAmB;GAAE,OAAO,KAAK;GAAO;GAAM,QAAQ,KAAK;GAAQ,YAAY,KAAK;GAAY,CAAC;;CAE3G,CAAC"}
1
+ {"version":3,"file":"remove-DTbqM4bR.js","names":[],"sources":["../src/commands/remove.ts"],"sourcesContent":["import { createCommand, splitList } from './_shared.js'\nimport { removeTranslations } from '../core/operations.js'\n\nexport default createCommand({\n name: 'remove',\n description: 'Remove translation keys from all locale files in a layer',\n args: {\n layer: { type: 'string', description: 'Layer name', required: true },\n keys: { type: 'string', description: 'Comma-separated key paths to remove', required: true },\n dryRun: { type: 'boolean', description: 'Preview changes without writing', default: false },\n },\n async run(args) {\n const keys = splitList(args.keys) ?? []\n if (keys.length === 0) {\n throw new Error('No keys provided. Pass comma-separated key paths via --keys')\n }\n return removeTranslations({ layer: args.layer, keys, dryRun: args.dryRun, projectDir: args.projectDir })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAAc,UAAU;GAAM;EACpE,MAAM;GAAE,MAAM;GAAU,aAAa;GAAuC,UAAU;GAAM;EAC5F,QAAQ;GAAE,MAAM;GAAW,aAAa;GAAmC,SAAS;GAAO;EAC5F;CACD,MAAM,IAAI,MAAM;EACd,MAAM,OAAO,UAAU,KAAK,KAAK,IAAI,EAAE;AACvC,MAAI,KAAK,WAAW,EAClB,OAAM,IAAI,MAAM,8DAA8D;AAEhF,SAAO,mBAAmB;GAAE,OAAO,KAAK;GAAO;GAAM,QAAQ,KAAK;GAAQ,YAAY,KAAK;GAAY,CAAC;;CAE3G,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { i as removeOrphanKeys } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { i as removeOrphanKeys } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/remove-orphans.ts
7
7
  var remove_orphans_default = createCommand({
8
8
  name: "remove-orphans",
@@ -54,4 +54,4 @@ var remove_orphans_default = createCommand({
54
54
  //#endregion
55
55
  export { remove_orphans_default as default };
56
56
 
57
- //# sourceMappingURL=remove-orphans-CcA3XQHJ.js.map
57
+ //# sourceMappingURL=remove-orphans-Dfzu7hRz.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remove-orphans-CcA3XQHJ.js","names":[],"sources":["../src/commands/remove-orphans.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { removeOrphanKeys } from '../core/operations.js'\n\nexport default createCommand({\n name: 'remove-orphans',\n description: 'Find and remove orphan translation keys not referenced in source code',\n args: {\n layer: { type: 'string', description: 'Filter to a specific layer' },\n locale: { type: 'string', description: 'Locale to check (default: project default)' },\n dryRun: { type: 'boolean', description: 'Preview without removing (default: true)', default: true },\n outputFile: { type: 'string', description: 'Write full output to this file path and return only a summary (useful for large outputs)' },\n codequalityOutput: { type: 'string', description: 'Also write the orphan findings as a GitLab Code Quality (CodeClimate) JSON report to this file path' },\n failOnOrphans: { type: 'boolean', description: 'Exit 2 when any orphan key is found (CI gate)', default: false },\n },\n gates: [{ flag: 'failOnOrphans', counter: 'orphanCount', threshold: 0 }],\n async run(args) {\n return removeOrphanKeys({\n layer: args.layer,\n locale: args.locale,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n outputFile: args.outputFile,\n codequalityOutput: args.codequalityOutput,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,yBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,QAAQ;GAAE,MAAM;GAAU,aAAa;GAA8C;EACrF,QAAQ;GAAE,MAAM;GAAW,aAAa;GAA4C,SAAS;GAAM;EACnG,YAAY;GAAE,MAAM;GAAU,aAAa;GAA4F;EACvI,mBAAmB;GAAE,MAAM;GAAU,aAAa;GAAuG;EACzJ,eAAe;GAAE,MAAM;GAAW,aAAa;GAAiD,SAAS;GAAO;EACjH;CACD,OAAO,CAAC;EAAE,MAAM;EAAiB,SAAS;EAAe,WAAW;EAAG,CAAC;CACxE,MAAM,IAAI,MAAM;AACd,SAAO,iBAAiB;GACtB,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,YAAY,KAAK;GACjB,YAAY,KAAK;GACjB,mBAAmB,KAAK;GACzB,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"remove-orphans-Dfzu7hRz.js","names":[],"sources":["../src/commands/remove-orphans.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { removeOrphanKeys } from '../core/operations.js'\n\nexport default createCommand({\n name: 'remove-orphans',\n description: 'Find and remove orphan translation keys not referenced in source code',\n args: {\n layer: { type: 'string', description: 'Filter to a specific layer' },\n locale: { type: 'string', description: 'Locale to check (default: project default)' },\n dryRun: { type: 'boolean', description: 'Preview without removing (default: true)', default: true },\n outputFile: { type: 'string', description: 'Write full output to this file path and return only a summary (useful for large outputs)' },\n codequalityOutput: { type: 'string', description: 'Also write the orphan findings as a GitLab Code Quality (CodeClimate) JSON report to this file path' },\n failOnOrphans: { type: 'boolean', description: 'Exit 2 when any orphan key is found (CI gate)', default: false },\n },\n gates: [{ flag: 'failOnOrphans', counter: 'orphanCount', threshold: 0 }],\n async run(args) {\n return removeOrphanKeys({\n layer: args.layer,\n locale: args.locale,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n outputFile: args.outputFile,\n codequalityOutput: args.codequalityOutput,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,yBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,QAAQ;GAAE,MAAM;GAAU,aAAa;GAA8C;EACrF,QAAQ;GAAE,MAAM;GAAW,aAAa;GAA4C,SAAS;GAAM;EACnG,YAAY;GAAE,MAAM;GAAU,aAAa;GAA4F;EACvI,mBAAmB;GAAE,MAAM;GAAU,aAAa;GAAuG;EACzJ,eAAe;GAAE,MAAM;GAAW,aAAa;GAAiD,SAAS;GAAO;EACjH;CACD,OAAO,CAAC;EAAE,MAAM;EAAiB,SAAS;EAAe,WAAW;EAAG,CAAC;CACxE,MAAM,IAAI,MAAM;AACd,SAAO,iBAAiB;GACtB,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,YAAY,KAAK;GACjB,YAAY,KAAK;GACjB,mBAAmB,KAAK;GACzB,CAAC;;CAEL,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { u as renameTranslationKey } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { d as renameTranslationKey } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/rename.ts
7
7
  var rename_default = createCommand({
8
8
  name: "rename",
@@ -42,4 +42,4 @@ var rename_default = createCommand({
42
42
  //#endregion
43
43
  export { rename_default as default };
44
44
 
45
- //# sourceMappingURL=rename-CWLfThC1.js.map
45
+ //# sourceMappingURL=rename-C_rFKkU4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rename-CWLfThC1.js","names":[],"sources":["../src/commands/rename.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { renameTranslationKey } from '../core/operations.js'\n\nexport default createCommand({\n name: 'rename',\n description: 'Rename/move a translation key across all locale files',\n args: {\n layer: { type: 'string', description: 'Layer name', required: true },\n oldKey: { type: 'string', description: 'Current key path', required: true },\n newKey: { type: 'string', description: 'New key path', required: true },\n dryRun: { type: 'boolean', description: 'Preview changes without writing', default: false },\n },\n async run(args) {\n return renameTranslationKey({\n layer: args.layer,\n oldKey: args.oldKey,\n newKey: args.newKey,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAAc,UAAU;GAAM;EACpE,QAAQ;GAAE,MAAM;GAAU,aAAa;GAAoB,UAAU;GAAM;EAC3E,QAAQ;GAAE,MAAM;GAAU,aAAa;GAAgB,UAAU;GAAM;EACvE,QAAQ;GAAE,MAAM;GAAW,aAAa;GAAmC,SAAS;GAAO;EAC5F;CACD,MAAM,IAAI,MAAM;AACd,SAAO,qBAAqB;GAC1B,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"rename-C_rFKkU4.js","names":[],"sources":["../src/commands/rename.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { renameTranslationKey } from '../core/operations.js'\n\nexport default createCommand({\n name: 'rename',\n description: 'Rename/move a translation key across all locale files',\n args: {\n layer: { type: 'string', description: 'Layer name', required: true },\n oldKey: { type: 'string', description: 'Current key path', required: true },\n newKey: { type: 'string', description: 'New key path', required: true },\n dryRun: { type: 'boolean', description: 'Preview changes without writing', default: false },\n },\n async run(args) {\n return renameTranslationKey({\n layer: args.layer,\n oldKey: args.oldKey,\n newKey: args.newKey,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAAc,UAAU;GAAM;EACpE,QAAQ;GAAE,MAAM;GAAU,aAAa;GAAoB,UAAU;GAAM;EAC3E,QAAQ;GAAE,MAAM;GAAU,aAAa;GAAgB,UAAU;GAAM;EACvE,QAAQ;GAAE,MAAM;GAAW,aAAa;GAAmC,SAAS;GAAO;EAC5F;CACD,MAAM,IAAI,MAAM;AACd,SAAO,qBAAqB;GAC1B,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
@@ -0,0 +1,25 @@
1
+ //#region src/utils/rename-notice.ts
2
+ const RENAMES = {
3
+ "the-i18n-cli": {
4
+ to: "@the-i18n-kit/cli",
5
+ how: "Install it with: npm i -g @the-i18n-kit/cli"
6
+ },
7
+ "the-i18n-mcp": {
8
+ to: "@the-i18n-kit/mcp",
9
+ how: "Update your MCP client config to run: npx @the-i18n-kit/mcp@latest"
10
+ }
11
+ };
12
+ /**
13
+ * The notice for a package running under a legacy name, or null when it is
14
+ * already running under its new one — which is the case that must stay silent,
15
+ * since a notice there would be telling people to do what they have done.
16
+ */
17
+ function renameNotice(packageName) {
18
+ const rename = RENAMES[packageName];
19
+ if (!rename) return null;
20
+ return `${packageName} is now ${rename.to}. Both names publish from the same source at the same versions, and the old one will stop receiving updates. ${rename.how}`;
21
+ }
22
+ //#endregion
23
+ export { renameNotice as t };
24
+
25
+ //# sourceMappingURL=rename-notice-BV8HNX3O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rename-notice-BV8HNX3O.js","names":[],"sources":["../src/utils/rename-notice.ts"],"sourcesContent":["/**\n * The kit is renaming to the `@the-i18n-kit` scope (#315). During the window\n * both names publish from one source at matching versions, so nothing breaks —\n * but a user on the old name has no way of learning that unless the package\n * tells them.\n *\n * A package finds out it is the old one by reading its own `name` at runtime,\n * which is why this takes the name rather than deciding for itself: the same\n * code ships under both names, and only the manifest differs.\n */\n\ninterface Rename {\n /** Where it moved to. */\n to: string\n /** How to move, phrased for whoever is going to read this particular notice. */\n how: string\n}\n\nconst RENAMES: Record<string, Rename> = {\n 'the-i18n-cli': {\n to: '@the-i18n-kit/cli',\n how: 'Install it with: npm i -g @the-i18n-kit/cli',\n },\n 'the-i18n-mcp': {\n to: '@the-i18n-kit/mcp',\n how: 'Update your MCP client config to run: npx @the-i18n-kit/mcp@latest',\n },\n}\n\n/**\n * The notice for a package running under a legacy name, or null when it is\n * already running under its new one — which is the case that must stay silent,\n * since a notice there would be telling people to do what they have done.\n */\nexport function renameNotice(packageName: string): string | null {\n const rename = RENAMES[packageName]\n if (!rename) return null\n\n return `${packageName} is now ${rename.to}. Both names publish from the same source at the same versions, `\n + `and the old one will stop receiving updates. ${rename.how}`\n}\n"],"mappings":";AAkBA,MAAM,UAAkC;CACtC,gBAAgB;EACd,IAAI;EACJ,KAAK;EACN;CACD,gBAAgB;EACd,IAAI;EACJ,KAAK;EACN;CACF;;;;;;AAOD,SAAgB,aAAa,aAAoC;CAC/D,MAAM,SAAS,QAAQ;AACvB,KAAI,CAAC,OAAQ,QAAO;AAEpB,QAAO,GAAG,YAAY,UAAU,OAAO,GAAG,+GACU,OAAO"}
@@ -0,0 +1,2 @@
1
+ import { t as renameNotice } from "./rename-notice-BV8HNX3O.js";
2
+ export { renameNotice };
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { d as scaffoldLocaleFiles } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { o as splitList, t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { f as scaffoldLocaleFiles } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { o as splitList, t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/scaffold.ts
7
7
  var scaffold_default = createCommand({
8
8
  name: "scaffold",
@@ -36,4 +36,4 @@ var scaffold_default = createCommand({
36
36
  //#endregion
37
37
  export { scaffold_default as default };
38
38
 
39
- //# sourceMappingURL=scaffold-BahXKbo9.js.map
39
+ //# sourceMappingURL=scaffold-CUimlNqR.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold-BahXKbo9.js","names":[],"sources":["../src/commands/scaffold.ts"],"sourcesContent":["import { createCommand, splitList } from './_shared.js'\nimport { scaffoldLocaleFiles } from '../core/operations.js'\n\nexport default createCommand({\n name: 'scaffold',\n description: 'Create empty locale files for new languages',\n args: {\n locales: { type: 'string', description: 'Comma-separated locale codes to scaffold' },\n layer: { type: 'string', description: 'Filter to a specific layer' },\n dryRun: { type: 'boolean', description: 'Preview without writing', default: false },\n },\n async run(args) {\n const locales = splitList(args.locales)\n if (locales && locales.length === 0) {\n throw new Error('No locales provided. Pass comma-separated locale codes via --locales')\n }\n return scaffoldLocaleFiles({ locales, layer: args.layer, dryRun: args.dryRun, projectDir: args.projectDir })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,mBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,SAAS;GAAE,MAAM;GAAU,aAAa;GAA4C;EACpF,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,QAAQ;GAAE,MAAM;GAAW,aAAa;GAA2B,SAAS;GAAO;EACpF;CACD,MAAM,IAAI,MAAM;EACd,MAAM,UAAU,UAAU,KAAK,QAAQ;AACvC,MAAI,WAAW,QAAQ,WAAW,EAChC,OAAM,IAAI,MAAM,uEAAuE;AAEzF,SAAO,oBAAoB;GAAE;GAAS,OAAO,KAAK;GAAO,QAAQ,KAAK;GAAQ,YAAY,KAAK;GAAY,CAAC;;CAE/G,CAAC"}
1
+ {"version":3,"file":"scaffold-CUimlNqR.js","names":[],"sources":["../src/commands/scaffold.ts"],"sourcesContent":["import { createCommand, splitList } from './_shared.js'\nimport { scaffoldLocaleFiles } from '../core/operations.js'\n\nexport default createCommand({\n name: 'scaffold',\n description: 'Create empty locale files for new languages',\n args: {\n locales: { type: 'string', description: 'Comma-separated locale codes to scaffold' },\n layer: { type: 'string', description: 'Filter to a specific layer' },\n dryRun: { type: 'boolean', description: 'Preview without writing', default: false },\n },\n async run(args) {\n const locales = splitList(args.locales)\n if (locales && locales.length === 0) {\n throw new Error('No locales provided. Pass comma-separated locale codes via --locales')\n }\n return scaffoldLocaleFiles({ locales, layer: args.layer, dryRun: args.dryRun, projectDir: args.projectDir })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,mBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,SAAS;GAAE,MAAM;GAAU,aAAa;GAA4C;EACpF,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,QAAQ;GAAE,MAAM;GAAW,aAAa;GAA2B,SAAS;GAAO;EACpF;CACD,MAAM,IAAI,MAAM;EACd,MAAM,UAAU,UAAU,KAAK,QAAQ;AACvC,MAAI,WAAW,QAAQ,WAAW,EAChC,OAAM,IAAI,MAAM,uEAAuE;AAEzF,SAAO,oBAAoB;GAAE;GAAS,OAAO,KAAK;GAAO,QAAQ,KAAK;GAAQ,YAAY,KAAK;GAAY,CAAC;;CAE/G,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { a as scanCodeUsage } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { o as splitList, t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { a as scanCodeUsage } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { o as splitList, t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/scan.ts
7
7
  var scan_default = createCommand({
8
8
  name: "scan",
@@ -28,4 +28,4 @@ var scan_default = createCommand({
28
28
  //#endregion
29
29
  export { scan_default as default };
30
30
 
31
- //# sourceMappingURL=scan-CTxs3kl7.js.map
31
+ //# sourceMappingURL=scan-BkOIYhjZ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scan-CTxs3kl7.js","names":[],"sources":["../src/commands/scan.ts"],"sourcesContent":["import { createCommand, splitList } from './_shared.js'\nimport { scanCodeUsage } from '../core/operations.js'\n\nexport default createCommand({\n name: 'scan',\n description: 'Scan source code for translation key usage (file paths + line numbers)',\n args: {\n keys: { type: 'string', description: 'Comma-separated keys to filter by' },\n outputFile: { type: 'string', description: 'Write full output to file, return summary only' },\n },\n async run(args) {\n return scanCodeUsage({\n keys: splitList(args.keys),\n projectDir: args.projectDir,\n outputFile: args.outputFile,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,eAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,MAAM;GAAE,MAAM;GAAU,aAAa;GAAqC;EAC1E,YAAY;GAAE,MAAM;GAAU,aAAa;GAAkD;EAC9F;CACD,MAAM,IAAI,MAAM;AACd,SAAO,cAAc;GACnB,MAAM,UAAU,KAAK,KAAK;GAC1B,YAAY,KAAK;GACjB,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"scan-BkOIYhjZ.js","names":[],"sources":["../src/commands/scan.ts"],"sourcesContent":["import { createCommand, splitList } from './_shared.js'\nimport { scanCodeUsage } from '../core/operations.js'\n\nexport default createCommand({\n name: 'scan',\n description: 'Scan source code for translation key usage (file paths + line numbers)',\n args: {\n keys: { type: 'string', description: 'Comma-separated keys to filter by' },\n outputFile: { type: 'string', description: 'Write full output to file, return summary only' },\n },\n async run(args) {\n return scanCodeUsage({\n keys: splitList(args.keys),\n projectDir: args.projectDir,\n outputFile: args.outputFile,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,eAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,MAAM;GAAE,MAAM;GAAU,aAAa;GAAqC;EAC1E,YAAY;GAAE,MAAM;GAAU,aAAa;GAAkD;EAC9F;CACD,MAAM,IAAI,MAAM;AACd,SAAO,cAAc;GACnB,MAAM,UAAU,KAAK,KAAK;GAC1B,YAAY,KAAK;GACjB,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { b as searchTranslations } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { k as searchTranslations } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/search.ts
7
7
  var search_default = createCommand({
8
8
  name: "search",
@@ -46,4 +46,4 @@ var search_default = createCommand({
46
46
  //#endregion
47
47
  export { search_default as default };
48
48
 
49
- //# sourceMappingURL=search-BonEgmSt.js.map
49
+ //# sourceMappingURL=search-0io1BcsY.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-BonEgmSt.js","names":[],"sources":["../src/commands/search.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { searchTranslations } from '../core/operations.js'\n\nexport default createCommand({\n name: 'search',\n description: 'Search translation files by key or value',\n args: {\n query: { type: 'string', description: 'Search query (matched case-insensitively against keys and values)', required: true },\n in: { type: 'string', description: 'Where to search: keys, values, or both', default: 'both' },\n layer: { type: 'string', description: 'Filter to a specific layer' },\n locale: { type: 'string', description: 'Filter to a specific locale' },\n },\n async run(args) {\n const validSearchIn = ['keys', 'values', 'both'] as const\n const searchIn = validSearchIn.includes(args.in as typeof validSearchIn[number])\n ? (args.in as 'keys' | 'values' | 'both')\n : undefined\n if (args.in && !searchIn) {\n throw new Error(`Invalid --in value: \"${args.in}\". Must be one of: keys, values, both`)\n }\n return searchTranslations({\n query: args.query,\n searchIn,\n layer: args.layer,\n locale: args.locale,\n projectDir: args.projectDir,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAAqE,UAAU;GAAM;EAC3H,IAAI;GAAE,MAAM;GAAU,aAAa;GAA0C,SAAS;GAAQ;EAC9F,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,QAAQ;GAAE,MAAM;GAAU,aAAa;GAA+B;EACvE;CACD,MAAM,IAAI,MAAM;EAEd,MAAM,WADgB;GAAC;GAAQ;GAAU;GAAO,CACjB,SAAS,KAAK,GAAmC,GAC3E,KAAK,KACN,KAAA;AACJ,MAAI,KAAK,MAAM,CAAC,SACd,OAAM,IAAI,MAAM,wBAAwB,KAAK,GAAG,uCAAuC;AAEzF,SAAO,mBAAmB;GACxB,OAAO,KAAK;GACZ;GACA,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"search-0io1BcsY.js","names":[],"sources":["../src/commands/search.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { searchTranslations } from '../core/operations.js'\n\nexport default createCommand({\n name: 'search',\n description: 'Search translation files by key or value',\n args: {\n query: { type: 'string', description: 'Search query (matched case-insensitively against keys and values)', required: true },\n in: { type: 'string', description: 'Where to search: keys, values, or both', default: 'both' },\n layer: { type: 'string', description: 'Filter to a specific layer' },\n locale: { type: 'string', description: 'Filter to a specific locale' },\n },\n async run(args) {\n const validSearchIn = ['keys', 'values', 'both'] as const\n const searchIn = validSearchIn.includes(args.in as typeof validSearchIn[number])\n ? (args.in as 'keys' | 'values' | 'both')\n : undefined\n if (args.in && !searchIn) {\n throw new Error(`Invalid --in value: \"${args.in}\". Must be one of: keys, values, both`)\n }\n return searchTranslations({\n query: args.query,\n searchIn,\n layer: args.layer,\n locale: args.locale,\n projectDir: args.projectDir,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAAqE,UAAU;GAAM;EAC3H,IAAI;GAAE,MAAM;GAAU,aAAa;GAA0C,SAAS;GAAQ;EAC9F,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,QAAQ;GAAE,MAAM;GAAU,aAAa;GAA+B;EACvE;CACD,MAAM,IAAI,MAAM;EAEd,MAAM,WADgB;GAAC;GAAQ;GAAU;GAAO,CACjB,SAAS,KAAK,GAAmC,GAC3E,KAAK,KACN,KAAA;AACJ,MAAI,KAAK,MAAM,CAAC,SACd,OAAM,IAAI,MAAM,wBAAwB,KAAK,GAAG,uCAAuC;AAEzF,SAAO,mBAAmB;GACxB,OAAO,KAAK;GACZ;GACA,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { o as getTranslationStatus } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { o as getTranslationStatus } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/status.ts
7
7
  var status_default = createCommand({
8
8
  name: "status",
@@ -42,4 +42,4 @@ var status_default = createCommand({
42
42
  //#endregion
43
43
  export { status_default as default };
44
44
 
45
- //# sourceMappingURL=status-C_5Khw14.js.map
45
+ //# sourceMappingURL=status-Bp-SzjTN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"status-C_5Khw14.js","names":[],"sources":["../src/commands/status.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { getTranslationStatus } from '../core/operations.js'\n\nexport default createCommand({\n name: 'status',\n description: 'Translation coverage per locale and per layer, with an overall completion percentage',\n args: {\n layer: { type: 'string', description: 'Filter to a specific layer' },\n ref: { type: 'string', description: 'Reference locale (default: project default)' },\n outputFile: { type: 'string', description: 'Write the full breakdown to this file path and return only a summary' },\n failUnder: { type: 'string', description: 'Exit 2 when overall completion is below this percentage (CI gate)' },\n },\n // Threshold comes from the flag's own value; `direction: below` is what\n // makes this a floor rather than a ceiling (see resolveExitCode).\n gates: [{ flag: 'failUnder', counter: 'completionPercent', direction: 'below' }],\n async run(args) {\n return getTranslationStatus({\n layer: args.layer,\n referenceLocale: args.ref,\n projectDir: args.projectDir,\n outputFile: args.outputFile,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,KAAK;GAAE,MAAM;GAAU,aAAa;GAA+C;EACnF,YAAY;GAAE,MAAM;GAAU,aAAa;GAAwE;EACnH,WAAW;GAAE,MAAM;GAAU,aAAa;GAAqE;EAChH;CAGD,OAAO,CAAC;EAAE,MAAM;EAAa,SAAS;EAAqB,WAAW;EAAS,CAAC;CAChF,MAAM,IAAI,MAAM;AACd,SAAO,qBAAqB;GAC1B,OAAO,KAAK;GACZ,iBAAiB,KAAK;GACtB,YAAY,KAAK;GACjB,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"status-Bp-SzjTN.js","names":[],"sources":["../src/commands/status.ts"],"sourcesContent":["import { createCommand } from './_shared.js'\nimport { getTranslationStatus } from '../core/operations.js'\n\nexport default createCommand({\n name: 'status',\n description: 'Translation coverage per locale and per layer, with an overall completion percentage',\n args: {\n layer: { type: 'string', description: 'Filter to a specific layer' },\n ref: { type: 'string', description: 'Reference locale (default: project default)' },\n outputFile: { type: 'string', description: 'Write the full breakdown to this file path and return only a summary' },\n failUnder: { type: 'string', description: 'Exit 2 when overall completion is below this percentage (CI gate)' },\n },\n // Threshold comes from the flag's own value; `direction: below` is what\n // makes this a floor rather than a ceiling (see resolveExitCode).\n gates: [{ flag: 'failUnder', counter: 'completionPercent', direction: 'below' }],\n async run(args) {\n return getTranslationStatus({\n layer: args.layer,\n referenceLocale: args.ref,\n projectDir: args.projectDir,\n outputFile: args.outputFile,\n })\n },\n})\n"],"mappings":";;;;;;AAGA,IAAA,iBAAe,cAAc;CAC3B,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GAAE,MAAM;GAAU,aAAa;GAA8B;EACpE,KAAK;GAAE,MAAM;GAAU,aAAa;GAA+C;EACnF,YAAY;GAAE,MAAM;GAAU,aAAa;GAAwE;EACnH,WAAW;GAAE,MAAM;GAAU,aAAa;GAAqE;EAChH;CAGD,OAAO,CAAC;EAAE,MAAM;EAAa,SAAS;EAAqB,WAAW;EAAS,CAAC;CAChF,MAAM,IAAI,MAAM;AACd,SAAO,qBAAqB;GAC1B,OAAO,KAAK;GACZ,iBAAiB,KAAK;GACtB,YAAY,KAAK;GACjB,YAAY,KAAK;GAClB,CAAC;;CAEL,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import "./stdout-guard-sJHFWVem.js";
2
- import { C as translateMissing } from "./operations-BCJd7in3.js";
3
- import "./providers-BAwPjeCy.js";
4
- import "./php-reader-DuZ0Hyl_.js";
5
- import { a as resolveProviderTranslateFn, i as providerArgs, o as splitList, t as createCommand } from "./_shared-Dv1EW2gy.js";
2
+ import { M as translateMissing } from "./operations-CTo44gPu.js";
3
+ import "./providers-CHE2ffi6.js";
4
+ import "./php-reader-3Fgw80zK.js";
5
+ import { a as resolveProviderTranslateFn, i as providerArgs, o as splitList, t as createCommand } from "./_shared-DFyy3sh8.js";
6
6
  //#region src/commands/translate.ts
7
7
  /**
8
8
  * Locales that lost at least one key, sorted for a stable message.
@@ -87,6 +87,6 @@ var translate_default = createCommand({
87
87
  }
88
88
  });
89
89
  //#endregion
90
- export { translate_default as default };
90
+ export { translate_default as default, localesWithFailures };
91
91
 
92
- //# sourceMappingURL=translate-CnXvBi5O.js.map
92
+ //# sourceMappingURL=translate-cCb2Z57w.js.map