@the-i18n-kit/cli 5.0.0 → 7.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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -58
  3. package/dist/bin.js +1 -6
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{_shared-DFyy3sh8.js → cli-waEjbUM1.js} +157 -27
  6. package/dist/cli-waEjbUM1.js.map +1 -0
  7. package/dist/config/framework/stubs/{next-intl-routing-Cwaa3f4R.d.ts → next-intl-routing-i6Mlxwdt.d.ts} +1 -1
  8. package/dist/config/framework/stubs/{next-intl-routing-Cwaa3f4R.d.ts.map → next-intl-routing-i6Mlxwdt.d.ts.map} +1 -1
  9. package/dist/{define-config-BaZ1dZph.d.ts → define-config-ChY3jk6K.d.ts} +26 -4
  10. package/dist/define-config-ChY3jk6K.d.ts.map +1 -0
  11. package/dist/{define-config-CLgKN-7N.d.ts → define-config-e0B0VHq7.d.ts} +1 -1
  12. package/dist/define-config.d.ts +1 -1
  13. package/dist/descriptors-10sgyqEs.js +1225 -0
  14. package/dist/descriptors-10sgyqEs.js.map +1 -0
  15. package/dist/detector-BJTkyhQ0.js +5 -0
  16. package/dist/detector-DtFY4qm3.js +2189 -0
  17. package/dist/detector-DtFY4qm3.js.map +1 -0
  18. package/dist/{index-DCG3dOdC.d.ts → index-CdjJ71Ag.d.ts} +665 -250
  19. package/dist/index-CdjJ71Ag.d.ts.map +1 -0
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +8 -5
  22. package/dist/json-writer-D0b6vEax.js +2 -0
  23. package/dist/json-writer-ek8yEI5R.js +342 -0
  24. package/dist/json-writer-ek8yEI5R.js.map +1 -0
  25. package/dist/{operations-CTo44gPu.js → operations-CNqEBD__.js} +1505 -3285
  26. package/dist/operations-CNqEBD__.js.map +1 -0
  27. package/dist/operations-D7IMu_xY.js +8 -0
  28. package/dist/php-reader-BGx6Ii2d.js +2 -0
  29. package/dist/{php-reader-3Fgw80zK.js → php-reader-DRpcRVzv.js} +6 -3
  30. package/dist/{php-reader-3Fgw80zK.js.map → php-reader-DRpcRVzv.js.map} +1 -1
  31. package/dist/{providers-CHE2ffi6.js → project-config-C3ao4Uii.js} +24 -214
  32. package/dist/project-config-C3ao4Uii.js.map +1 -0
  33. package/dist/providers-BbVPelvp.js +406 -0
  34. package/dist/providers-BbVPelvp.js.map +1 -0
  35. package/dist/report-By1-5JtO.js +37 -0
  36. package/dist/report-By1-5JtO.js.map +1 -0
  37. package/dist/report-CHZgH9Wy.js +2 -0
  38. package/package.json +11 -23
  39. package/dist/_shared-DFyy3sh8.js.map +0 -1
  40. package/dist/add-C-qs8iBa.js +0 -40
  41. package/dist/add-C-qs8iBa.js.map +0 -1
  42. package/dist/check-BI1orjXb.js +0 -41
  43. package/dist/check-BI1orjXb.js.map +0 -1
  44. package/dist/cli-DsFuVJNP.js +0 -74
  45. package/dist/cli-DsFuVJNP.js.map +0 -1
  46. package/dist/config/framework/stubs/unplugin-vue-i18n-1ud7-Ly8.d.ts +0 -25
  47. package/dist/config/framework/stubs/unplugin-vue-i18n-1ud7-Ly8.d.ts.map +0 -1
  48. package/dist/config/framework/stubs/unplugin-vue-i18n.js +0 -26
  49. package/dist/config/framework/stubs/unplugin-vue-i18n.js.map +0 -1
  50. package/dist/define-config-BaZ1dZph.d.ts.map +0 -1
  51. package/dist/detect-7cVfsNJP.js +0 -17
  52. package/dist/detect-7cVfsNJP.js.map +0 -1
  53. package/dist/empty-DB8Hl_8u.js +0 -36
  54. package/dist/empty-DB8Hl_8u.js.map +0 -1
  55. package/dist/find-duplicates-CmEqBfby.js +0 -42
  56. package/dist/find-duplicates-CmEqBfby.js.map +0 -1
  57. package/dist/get-BE21Iy6N.js +0 -41
  58. package/dist/get-BE21Iy6N.js.map +0 -1
  59. package/dist/index-DCG3dOdC.d.ts.map +0 -1
  60. package/dist/init-BtzmhsOM.js +0 -33
  61. package/dist/init-BtzmhsOM.js.map +0 -1
  62. package/dist/list-dirs-DUQbwvlE.js +0 -17
  63. package/dist/list-dirs-DUQbwvlE.js.map +0 -1
  64. package/dist/missing-CcYrgd54.js +0 -51
  65. package/dist/missing-CcYrgd54.js.map +0 -1
  66. package/dist/move-B2px6Ck-.js +0 -50
  67. package/dist/move-B2px6Ck-.js.map +0 -1
  68. package/dist/operations-CTo44gPu.js.map +0 -1
  69. package/dist/php-reader-CpnaPSpZ.js +0 -2
  70. package/dist/providers-CHE2ffi6.js.map +0 -1
  71. package/dist/remove-DTbqM4bR.js +0 -41
  72. package/dist/remove-DTbqM4bR.js.map +0 -1
  73. package/dist/remove-orphans-Dfzu7hRz.js +0 -57
  74. package/dist/remove-orphans-Dfzu7hRz.js.map +0 -1
  75. package/dist/rename-C_rFKkU4.js +0 -45
  76. package/dist/rename-C_rFKkU4.js.map +0 -1
  77. package/dist/rename-notice-BV8HNX3O.js +0 -25
  78. package/dist/rename-notice-BV8HNX3O.js.map +0 -1
  79. package/dist/rename-notice-Cx1vuTRz.js +0 -2
  80. package/dist/scaffold-CUimlNqR.js +0 -39
  81. package/dist/scaffold-CUimlNqR.js.map +0 -1
  82. package/dist/scan-BkOIYhjZ.js +0 -31
  83. package/dist/scan-BkOIYhjZ.js.map +0 -1
  84. package/dist/search-0io1BcsY.js +0 -49
  85. package/dist/search-0io1BcsY.js.map +0 -1
  86. package/dist/status-Bp-SzjTN.js +0 -45
  87. package/dist/status-Bp-SzjTN.js.map +0 -1
  88. package/dist/translate-cCb2Z57w.js +0 -92
  89. package/dist/translate-cCb2Z57w.js.map +0 -1
  90. package/dist/translate-key-DHLWEmrA.js +0 -73
  91. package/dist/translate-key-DHLWEmrA.js.map +0 -1
  92. package/dist/update-DV4ijRVB.js +0 -40
  93. package/dist/update-DV4ijRVB.js.map +0 -1
  94. package/dist/write-Czzm_Nb0.js +0 -47
  95. package/dist/write-Czzm_Nb0.js.map +0 -1
  96. /package/dist/{bin-DrKRgnr9.d.ts → bin-NyzIHE2F.d.ts} +0 -0
@@ -0,0 +1,8 @@
1
+ import { s as findLocaleImpl } from "./providers-BbVPelvp.js";
2
+ import { r as validateReportPath } from "./report-By1-5JtO.js";
3
+ import "./project-config-C3ao4Uii.js";
4
+ import "./json-writer-ek8yEI5R.js";
5
+ import "./php-reader-DRpcRVzv.js";
6
+ import "./detector-DtFY4qm3.js";
7
+ import { A as translateKey, C as findEmptyTranslations, D as listNamespaces, E as listLocaleDirs, F as buildTranslationSystemPrompt, I as buildTranslationUserMessage, L as validatePlaceholders, O as searchTranslations, P as extractJsonFromResponse, R as resolveProtectedLocales, S as detectConfig, T as getTranslations, a as scanCodeUsage, c as moveTranslationKey, d as scaffoldLocaleFiles, f as writeTranslations, i as removeOrphanKeys, j as translateMissing, k as computeProgressTotal, l as removeTranslations, n as findDuplicateKeys, o as getTranslationStatus, r as findOrphanKeys, s as initProjectConfig, t as checkUndefinedKeys, u as renameTranslationKey, w as getMissingTranslations, x as describeProject } from "./operations-CNqEBD__.js";
8
+ export { buildTranslationSystemPrompt, buildTranslationUserMessage, checkUndefinedKeys, computeProgressTotal, describeProject, detectConfig, extractJsonFromResponse, findDuplicateKeys, findEmptyTranslations, findLocaleImpl, findOrphanKeys, getMissingTranslations, getTranslationStatus, getTranslations, initProjectConfig, listLocaleDirs, listNamespaces, moveTranslationKey, removeOrphanKeys, removeTranslations, renameTranslationKey, resolveProtectedLocales, scaffoldLocaleFiles, scanCodeUsage, searchTranslations, translateKey, translateMissing, validatePlaceholders, validateReportPath, writeTranslations };
@@ -0,0 +1,2 @@
1
+ import { r as readPhpLocaleFile } from "./php-reader-DRpcRVzv.js";
2
+ export { readPhpLocaleFile };
@@ -1,8 +1,8 @@
1
1
  import { i as toErrorMessage, n as FileIOError } from "./errors-coI1dhw1.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname, isAbsolute, join } from "node:path";
4
- import { existsSync } from "node:fs";
5
4
  import { readFile, stat } from "node:fs/promises";
5
+ import { existsSync } from "node:fs";
6
6
  //#region src/io/php-reader.ts
7
7
  /**
8
8
  * `php-array-reader` is an optional peer (#406): only Laravel projects carry
@@ -33,6 +33,9 @@ async function resolveFromString(fromFile) {
33
33
  }
34
34
  }
35
35
  const fileCache = /* @__PURE__ */ new Map();
36
+ function clearPhpFileCache() {
37
+ fileCache.clear();
38
+ }
36
39
  function clearPhpFileCacheEntry(filePath) {
37
40
  fileCache.delete(filePath);
38
41
  }
@@ -59,6 +62,6 @@ async function readPhpLocaleFile(filePath) {
59
62
  }
60
63
  }
61
64
  //#endregion
62
- export { readPhpLocaleFile as n, clearPhpFileCacheEntry as t };
65
+ export { clearPhpFileCacheEntry as n, readPhpLocaleFile as r, clearPhpFileCache as t };
63
66
 
64
- //# sourceMappingURL=php-reader-3Fgw80zK.js.map
67
+ //# sourceMappingURL=php-reader-DRpcRVzv.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"php-reader-DRpcRVzv.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;AAErF,SAAgB,oBAA0B;AACxC,WAAU,OAAO;;AAGnB,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"}
@@ -1,8 +1,8 @@
1
1
  import { i as toErrorMessage, t as ConfigError } from "./errors-coI1dhw1.js";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { createConsola } from "consola";
4
- import { existsSync } from "node:fs";
5
3
  import { readFile } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import { createConsola } from "consola";
6
6
  import { z } from "zod";
7
7
  import { fileURLToPath } from "node:url";
8
8
  //#region src/utils/logger.ts
@@ -36,9 +36,13 @@ const localeDirEntrySchema = z.union([nonEmptyString.describe("Relative path to
36
36
  path: nonEmptyString.describe("Relative path to a locale directory."),
37
37
  layer: nonEmptyString.describe("Layer name for this locale directory.")
38
38
  })]);
39
+ const appEntrySchema = z.object({
40
+ name: nonEmptyString.describe("App name, as it appears in orphan and status reports (e.g., 'shop', '@acme/admin')."),
41
+ layers: z.array(nonEmptyString).describe("Layer names this app can render — its own layer plus every shared layer it consumes.")
42
+ });
39
43
  const projectConfigSchema = z.object({
40
44
  $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(),
45
+ 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. 'vue' is accepted as an alias for 'generic', which resolves Vue projects: declare localeDirs when the locale files are not in a conventional directory.").optional(),
42
46
  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
47
  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
48
  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(),
@@ -51,11 +55,18 @@ const projectConfigSchema = z.object({
51
55
  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
56
  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
57
  defaultLocale: nonEmptyString.describe("Default locale code. Required for generic adapter activation.").optional(),
58
+ apps: z.array(appEntrySchema).describe("Which app consumes which layers — the consumer graph behind app-scoped orphan detection, misplaced-usage reports and unconsumed-layer warnings. Declaring it overrides both framework discovery and workspace inference.").optional(),
59
+ consumerGraph: z.enum(["auto", "off"]).describe("Whether to infer the consumer graph from the package manager workspace and package.json dependencies when the framework provides no app information: 'auto' (default) infers it, 'off' keeps every layer in one app.").optional(),
54
60
  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
61
  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(),
62
+ 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, find_undefined_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(),
63
+ localeFileFormat: z.enum([
64
+ "json",
65
+ "php-array",
66
+ "yaml"
67
+ ]).describe("Override the auto-detected locale file format. 'yaml' covers both .yaml and .yml files. Useful when several formats exist in one project or auto-detection picks wrong.").optional(),
68
+ translationMemory: z.boolean().describe("Write a translation memory to '.i18n-kit.lock.json' at the project root, recording per layer, key and target locale a hash of the source text each translation was made from, so later runs can tell targets that are still current from ones whose source has changed since. Off by default; no file is created until you enable it.").optional(),
69
+ 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.").optional(),
59
70
  samplingPreferences: z.unknown().meta({
60
71
  deprecated: true,
61
72
  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)."
@@ -178,10 +189,9 @@ async function loadTypedConfig(projectDir) {
178
189
  };
179
190
  }
180
191
  /**
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.
192
+ * The specifier the documentation tells you to import from. Configs written
193
+ * before the rename may still name the old one; it keeps resolving, but only
194
+ * the new one belongs in a message that tells someone what to write.
185
195
  */
186
196
  const CONFIG_SPECIFIER = "@the-i18n-kit/cli/config";
187
197
  const DEPRECATED_CONFIG_SPECIFIER = "the-i18n-cli/config";
@@ -196,8 +206,8 @@ const DEPRECATED_CONFIG_SPECIFIER = "the-i18n-cli/config";
196
206
  * ourselves costs nothing and is the same function either way: identity.
197
207
  *
198
208
  * 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.
209
+ * name the docs no longer recommend, and the deprecated one keeps working so
210
+ * existing configs do not break.
201
211
  *
202
212
  * Returns no alias when the entry cannot be located, which leaves normal
203
213
  * resolution to find the installed package.
@@ -303,206 +313,6 @@ async function loadJsonConfig(projectDir) {
303
313
  };
304
314
  }
305
315
  //#endregion
306
- //#region src/llm/providers.ts
307
- /**
308
- * A classified provider failure. `auth` errors are not retryable (the caller
309
- * should abort the run), `rate-limit` errors should be retried with backoff,
310
- * and `provider` covers everything else (server errors, network, …).
311
- * `config` marks an unusable provider setup and is raised while building the
312
- * TranslateFn, before any request exists — so it surfaces from the command
313
- * and never reaches the retry loop.
314
- */
315
- var TranslateProviderError = class extends Error {
316
- kind;
317
- status;
318
- constructor(message, kind, status) {
319
- super(message);
320
- this.name = "TranslateProviderError";
321
- this.kind = kind;
322
- this.status = status;
323
- }
324
- };
325
- /** Defensively extract an HTTP status code from an unknown SDK error shape. */
326
- function extractStatus(error) {
327
- if (error === null || typeof error !== "object") return void 0;
328
- const e = error;
329
- if (typeof e.status === "number") return e.status;
330
- if (e.response !== null && typeof e.response === "object") {
331
- const status = e.response.status;
332
- if (typeof status === "number") return status;
333
- }
334
- }
335
- /**
336
- * Classify an SDK error into a TranslateProviderError:
337
- * 401/403 → auth, 429 → rate-limit, anything else → provider.
338
- * Already-classified errors pass through unchanged.
339
- */
340
- function classifyProviderError(error) {
341
- if (error instanceof TranslateProviderError) return error;
342
- const status = extractStatus(error);
343
- return new TranslateProviderError(error instanceof Error ? error.message : String(error), status === 401 || status === 403 ? "auth" : status === 429 ? "rate-limit" : "provider", status);
344
- }
345
- /** Environment variable carrying the provider base URL override. */
346
- const BASE_URL_ENV = "I18N_BASE_URL";
347
- /**
348
- * Resolve the provider base URL from its three sources, highest precedence
349
- * first: an explicit flag, the I18N_BASE_URL env var, then the project
350
- * config's `providerBaseUrl`.
351
- *
352
- * Blank values count as unset. Shells produce them routinely — `--baseUrl
353
- * "$UNSET"` or an exported-but-empty variable — and a blank must not shadow a
354
- * real endpoint configured further down the chain. A blank in the config file
355
- * is a different case: it cannot arise by accident, so the strict schema
356
- * rejects it at load time rather than letting it reach this function.
357
- */
358
- function resolveProviderBaseUrl(sources) {
359
- for (const value of [
360
- sources.flag,
361
- sources.env,
362
- sources.config
363
- ]) if (typeof value === "string" && value.trim().length > 0) return value.trim();
364
- }
365
- const ENV_KEY_MAP = {
366
- openai: "OPENAI_API_KEY",
367
- anthropic: "ANTHROPIC_API_KEY",
368
- google: "GEMINI_API_KEY"
369
- };
370
- function resolveApiKey(provider, configKey) {
371
- if (configKey) return configKey;
372
- const envKey = ENV_KEY_MAP[provider];
373
- const key = process.env[envKey];
374
- if (!key) throw new Error(`No API key found for provider "${provider}". Set ${envKey} environment variable or pass apiKey in config.`);
375
- return key;
376
- }
377
- async function createOpenAiTranslateFn(config) {
378
- let OpenAI;
379
- try {
380
- OpenAI = (await import("openai")).default;
381
- } catch {
382
- throw new Error("Provider \"openai\" requires the \"openai\" package. Install it with:\n npm install openai\n pnpm add openai\n yarn add openai");
383
- }
384
- const apiKey = resolveApiKey("openai", config.apiKey);
385
- const client = new OpenAI({
386
- apiKey,
387
- baseURL: config.baseUrl
388
- });
389
- return async (opts) => {
390
- let response;
391
- try {
392
- response = await client.chat.completions.create({
393
- model: config.model,
394
- messages: [{
395
- role: "system",
396
- content: opts.systemPrompt
397
- }, {
398
- role: "user",
399
- content: opts.userMessage
400
- }],
401
- max_tokens: opts.maxTokens,
402
- temperature: 0,
403
- response_format: { type: "json_object" }
404
- });
405
- } catch (error) {
406
- throw classifyProviderError(error);
407
- }
408
- const choice = response.choices?.[0];
409
- const text = choice?.message?.content ?? "";
410
- const truncated = choice?.finish_reason === "length";
411
- return {
412
- text,
413
- model: response.model || config.model,
414
- ...truncated ? { truncated: true } : {}
415
- };
416
- };
417
- }
418
- async function createGoogleTranslateFn(config) {
419
- let GoogleGenAI;
420
- try {
421
- GoogleGenAI = (await import("@google/genai")).GoogleGenAI;
422
- } catch {
423
- throw new Error("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");
424
- }
425
- const apiKey = resolveApiKey("google", config.apiKey);
426
- const client = new GoogleGenAI({ apiKey });
427
- return async (opts) => {
428
- let response;
429
- try {
430
- response = await client.models.generateContent({
431
- model: config.model,
432
- contents: opts.userMessage,
433
- config: {
434
- systemInstruction: opts.systemPrompt,
435
- maxOutputTokens: opts.maxTokens,
436
- temperature: 0,
437
- responseMimeType: "application/json"
438
- }
439
- });
440
- } catch (error) {
441
- throw classifyProviderError(error);
442
- }
443
- const text = response.text ?? "";
444
- const truncated = response.candidates?.[0]?.finishReason === "MAX_TOKENS";
445
- return {
446
- text,
447
- model: response.modelVersion || config.model,
448
- ...truncated ? { truncated: true } : {}
449
- };
450
- };
451
- }
452
- async function createAnthropicTranslateFn(config) {
453
- let Anthropic;
454
- try {
455
- Anthropic = (await import("@anthropic-ai/sdk")).Anthropic;
456
- } catch {
457
- throw new Error("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");
458
- }
459
- const apiKey = resolveApiKey("anthropic", config.apiKey);
460
- const client = new Anthropic({
461
- apiKey,
462
- baseURL: config.baseUrl
463
- });
464
- return async (opts) => {
465
- let response;
466
- try {
467
- response = await client.messages.create({
468
- model: config.model,
469
- system: opts.systemPrompt,
470
- messages: [{
471
- role: "user",
472
- content: opts.userMessage
473
- }],
474
- max_tokens: opts.maxTokens,
475
- temperature: 0
476
- });
477
- } catch (error) {
478
- throw classifyProviderError(error);
479
- }
480
- const text = (response.content?.find((block) => block.type === "text"))?.text ?? "";
481
- const truncated = response.stop_reason === "max_tokens";
482
- return {
483
- text,
484
- model: response.model || config.model,
485
- ...truncated ? { truncated: true } : {}
486
- };
487
- };
488
- }
489
- /**
490
- * Create a TranslateFn from an LLM provider config.
491
- * Throws if the provider SDK is not installed or API key is missing.
492
- */
493
- async function createTranslateFn(config) {
494
- if (config.provider === "google" && config.baseUrl) throw new TranslateProviderError(`Provider "google" does not support a custom base URL. Remove --baseUrl / ${BASE_URL_ENV} / providerBaseUrl, or use an OpenAI-compatible provider.`, "config");
495
- switch (config.provider) {
496
- case "openai": return createOpenAiTranslateFn(config);
497
- case "anthropic": return createAnthropicTranslateFn(config);
498
- case "google": return createGoogleTranslateFn(config);
499
- default: {
500
- const _exhaustive = config.provider;
501
- throw new Error(`Unknown provider: ${_exhaustive}`);
502
- }
503
- }
504
- }
505
- //#endregion
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 };
316
+ export { log as i, loadProjectConfig as n, validateProjectConfig as r, CONFIG_FILENAME as t };
507
317
 
508
- //# sourceMappingURL=providers-CHE2ffi6.js.map
318
+ //# sourceMappingURL=project-config-C3ao4Uii.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-config-C3ao4Uii.js","names":[],"sources":["../src/utils/logger.ts","../src/config/schema.ts","../src/config/typed-config.ts","../src/config/project-config.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\nconst appEntrySchema = z.object({\n name: nonEmptyString\n .describe('App name, as it appears in orphan and status reports (e.g., \\'shop\\', \\'@acme/admin\\').'),\n layers: z.array(nonEmptyString)\n .describe('Layer names this app can render — its own layer plus every shared layer it consumes.'),\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. \\'vue\\' is accepted as an '\n + 'alias for \\'generic\\', which resolves Vue projects: declare localeDirs when '\n + 'the locale files are not in a conventional directory.',\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 apps: z.array(appEntrySchema)\n .describe(\n 'Which app consumes which layers — the consumer graph behind app-scoped orphan detection, '\n + 'misplaced-usage reports and unconsumed-layer warnings. Declaring it overrides both framework '\n + 'discovery and workspace inference.',\n )\n .optional(),\n consumerGraph: z.enum(['auto', 'off'])\n .describe(\n 'Whether to infer the consumer graph from the package manager workspace and package.json '\n + 'dependencies when the framework provides no app information: \\'auto\\' (default) infers it, '\n + '\\'off\\' keeps every layer in one app.',\n )\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, find_undefined_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', 'yaml'])\n .describe(\n 'Override the auto-detected locale file format. \\'yaml\\' covers both .yaml and .yml files. '\n + 'Useful when several formats exist in one project or auto-detection picks wrong.',\n )\n .optional(),\n translationMemory: z.boolean()\n .describe(\n 'Write a translation memory to \\'.i18n-kit.lock.json\\' at the project root, recording per '\n + 'layer, key and target locale a hash of the source text each translation was made from, so '\n + 'later runs can tell targets that are still current from ones whose source has changed since. '\n + 'Off by default; no file is created until you enable it.',\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 )\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. Configs written\n * before the rename may still name the old one; it keeps resolving, but only\n * the new one belongs in a message that 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 keeps working so\n * existing configs do not break.\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"],"mappings":";;;;;;;;AAIA,MAAa,MAAM,cAAc;CAC/B,QAAQ,QAAQ;CAChB,QAAQ,QAAQ;CACjB,CAAC,CAAC,QAAQ,eAAe;;;;;;;;;;;;;;;;;ACW1B,MAAM,iBAAiB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAO,MAAK,EAAE,MAAM,CAAC,SAAS,GAAG,uCAAuC;AAEjH,MAAM,kBAAkB,EAAE,OAAO;CAC/B,OAAO,EAAE,QAAQ,CACd,SAAS,8DAAsE;CAClF,aAAa,EAAE,QAAQ,CACpB,SAAS,gEAAgE;CAC5E,MAAM,EAAE,QAAQ,CACb,SAAS,uEAAuE;CACpF,CAAC;AAEF,MAAM,uBAAuB,EAAE,MAAM,CACnC,eACG,SAAS,yEAA2E,EACvF,EAAE,OAAO;CACP,MAAM,eAAe,SAAS,uCAAuC;CACrE,OAAO,eAAe,SAAS,wCAAwC;CACxE,CAAC,CACH,CAAC;AAEF,MAAM,iBAAiB,EAAE,OAAO;CAC9B,MAAM,eACH,SAAS,sFAA0F;CACtG,QAAQ,EAAE,MAAM,eAAe,CAC5B,SAAS,uFAAuF;CACpG,CAAC;AAEF,MAAa,sBAAsB,EAAE,OAAO;CAC1C,SAAS,EAAE,QAAQ,CAChB,SAAS,yDAAyD,CAClE,UAAU;CACb,WAAW,EAAE,QAAQ,CAClB,SACC,iWAKD,CACA,UAAU;CACb,SAAS,EAAE,QAAQ,CAChB,SACC,8IAED,CACA,UAAU;CACb,YAAY,EAAE,MAAM,gBAAgB,CACjC,SACC,yIAED,CACA,UAAU;CACb,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CACvC,SACC,8HAED,CACA,UAAU;CACb,mBAAmB,EAAE,QAAQ,CAC1B,SACC,2IAED,CACA,UAAU;CACb,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAC1C,SACC,yMAGD,CACA,UAAU;CACb,UAAU,EAAE,MAIV,EAAE,OAAO;EACP,KAAK,EAAE,QAAQ,CAAC,SAAS,wFAA0F;EACnH,MAAM,EAAE,QAAQ,CAAC,SAAS,sDAAsD,CAAC,UAAU;EAC5F,CAAC,CACC,SAAS,EAAE,QAAQ,CAAC,CACpB,SACC,6OAGD,CACJ,CACE,SACC,sIAED,CACA,UAAU;CACb,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,OAAO,EACxC,gBAAgB,EAAE,MAAM,EAAE,QAAQ,CAAC,CAChC,SACC,wKAED,CACA,UAAU,EAEd,CAAC,CAAC,aAAa,CAAC,CACd,SACC,yKAED,CACA,UAAU;CACb,YAAY,EAAE,MAAM,qBAAqB,CACtC,SACC,yJAED,CACA,UAAU;CACb,eAAe,eACZ,SAAS,gEAAgE,CACzE,UAAU;CACb,MAAM,EAAE,MAAM,eAAe,CAC1B,SACC,2NAGD,CACA,UAAU;CACb,eAAe,EAAE,KAAK,CAAC,QAAQ,MAAM,CAAC,CACnC,SACC,uNAGD,CACA,UAAU;CACb,SAAS,EAAE,MAAM,eAAe,CAC7B,SACC,0GAED,CACA,UAAU;CACb,kBAAkB,EAAE,MAAM,eAAe,CACtC,SACC,wSAID,CACA,UAAU;CACb,cAAc,EAAE,MAAM,CACpB,EAAE,QAAQ,KAAK,CACZ,SAAS,0EAA4E,EACxF,eACG,SAAS,8FAA8F,CAC3G,CAAC,CACC,SACC,2VAID,CACA,UAAU;CACb,kBAAkB,EAAE,KAAK;EAAC;EAAQ;EAAa;EAAO,CAAC,CACpD,SACC,0KAED,CACA,UAAU;CACb,mBAAmB,EAAE,SAAS,CAC3B,SACC,wUAID,CACA,UAAU;CACb,iBAAiB,eACd,SACC,8QAGD,CACA,UAAU;CAGb,qBAAqB,EAAE,SAAS,CAC7B,KAAK;EACJ,YAAY;EACZ,aACE;EAEH,CAAC,CACD,UAAU;CACd,CAAC,CAAC,QAAQ;;;;;;;AAQX,SAAgB,sBACd,OACkE;CAClE,MAAM,SAAS,oBAAoB,UAAU,MAAM;AACnD,KAAI,OAAO,QAAS,QAAO;EAAE,IAAI;EAAM,MAAM,OAAO;EAAuB;AAQ3E,QAAO;EAAE,IAAI;EAAO,OANN,OAAO,MAAM,OACxB,KAAK,UAAU;AAEd,UAAO,KADM,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,SAC3C,IAAI,MAAM;IAC3B,CACD,KAAK,KAAK;EACc;;;;;;;;;AAU7B,SAAgB,mBAAmB,QAAuB,QAA+B;CACvF,MAAM,OAAO;AACb,KAAI,yBAAyB,MAAM;AACjC,MAAI,KACF,GAAG,OAAO,6IAEX;AACD,SAAO,KAAK;;AAEd,QAAO;;;;;;;;;;;;;;;;;;AC7NT,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;AAQD,SAAgB,oBAAoB,UAAiC;CAInE,IAAI,MAAM,QAAQ,SAAS;AAC3B,QAAO,MAAM;EACX,MAAM,QAAQ,uBACX,KAAI,SAAQ,KAAK,KAAK,KAAK,CAAC,CAC5B,QAAO,SAAQ,WAAW,KAAK,CAAC;AAEnC,MAAI,MAAM,IAAI;AACZ,OAAI,MAAM,SAAS,EACjB,KAAI,KACF,+BAA+B,IAAI,IAAI,MAAM,KAAI,MAAK,EAAE,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,WAC/E,MAAM,GAAG,MAAM,IAAI,SAAS,EAAE,CAAC,+CAC3C;AAEH,UAAO,MAAM;;EAGf,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,QAAO;;;;;;;;;;;AAYT,eAAsB,gBACpB,YACyD;CACzD,MAAM,OAAO,oBAAoB,WAAW;AAC5C,KAAI,CAAC,MAAM;AACT,MAAI,MAAM,iCAAiC,WAAW,qCAAqC;AAC3F,SAAO;;AAGT,KAAI,KAAK,+BAA+B,OAAO;CAE/C,IAAI;AACJ,KAAI;EACF,MAAM,EAAE,eAAe,MAAM,OAAO;AASpC,WAAS,MAJI,WAAW,OAAO,KAAK,KAAK;GACvC,gBAAgB;GAChB,OAAO,WAAW;GACnB,CAAC,CACkB,OAAO,KAAK;UAE3B,OAAO;AACZ,QAAM,IAAI,YACR,kBAAkB,KAAK,IAAI,eAAe,MAAM,GACjD;;AAGH,KAAI,WAAW,QAAQ,OAAO,WAAW,SACvC,OAAM,IAAI,YACR,GAAG,KAAK,wGAET;CAOH,MAAM,WAAW,KAAK,SAAS,OAAO,IAAI,KAAK,SAAS,OAAO;AAC/D,KAAI,EAAE,aAAa,WAAW,CAAC,SAC7B,OAAM,IAAI,YACR,GAAG,KAAK,wGAET;CAGH,MAAM,WAAW,aAAa,SAAU,OAAgC,UAAU;AAElF,KAAI,aAAa,QAAQ,OAAO,aAAa,YAAY,MAAM,QAAQ,SAAS,CAC9E,OAAM,IAAI,YACR,GAAG,KAAK,2DAA2D,SAAS,SAAS,CAAC,2CAC1C,iBAAiB,IAC9D;CAGH,MAAM,SAAS,sBAAsB,SAAS;AAC9C,KAAI,CAAC,OAAO,GACV,OAAM,IAAI,YAAY,WAAW,KAAK,KAAK,OAAO,QAAQ;AAG5D,QAAO;EAAE;EAAM,QAAQ,mBAAmB,OAAO,MAAM,KAAK;EAAE;;;;;;;AAQhE,MAAM,mBAAmB;AACzB,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;AAmBpC,SAAS,YAAoC;AAG3C,MAAK,MAAM,aAAa;EAAC;EAAsB;EAAuB;EAAsB,EAAE;EAC5F,MAAM,OAAO,cAAc,IAAI,IAAI,WAAW,OAAO,KAAK,IAAI,CAAC;AAC/D,MAAI,WAAW,KAAK,CAClB,QAAO;IAAG,mBAAmB;IAAO,8BAA8B;GAAM;;AAG5E,QAAO,EAAE;;AAGX,SAAS,SAAS,OAAwB;AACxC,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO;AACjC,KAAI,OAAO,UAAU,WAAY,QAAO;AACxC,QAAO,OAAO;;;;AC3KhB,MAAa,kBAAkB;;;;;AAa/B,SAAgB,eAAe,UAAiC;CAC9D,IAAI,MAAM;AACV,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,KAAK,gBAAgB;AAC5C,MAAI,WAAW,UAAU,CACvB,QAAO;EAET,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,QAAO;;;;;;;;;;;;;;AAeT,eAAsB,kBAAkB,YAAmD;CACzF,MAAM,CAAC,MAAM,SAAS,MAAM,QAAQ,IAAI,CACtC,eAAe,WAAW,EAC1B,gBAAgB,WAAW,CAC5B,CAAC;AAEF,KAAI,CAAC,MAAO,QAAO,MAAM,UAAU;AACnC,KAAI,CAAC,KAAM,QAAO,MAAM;CAExB,MAAM,YAAY,gBAAgB,MAAM,QAAQ,KAAK,OAAO;AAC5D,KAAI,UAAU,SAAS,EACrB,OAAM,IAAI,YACR,GAAG,UAAU,KAAK,KAAK,CAAC,GAAG,UAAU,WAAW,IAAI,OAAO,MAAM,uBAC1D,MAAM,KAAK,MACX,KAAK,KAAK,qGAGlB;AAKH,QAAO;EAAE,GAAG,KAAK;EAAQ,GAAG,MAAM;EAAQ;;;AAI5C,SAAS,gBAAgB,OAAsB,MAA+B;CAC5E,MAAM,IAAI;CACV,MAAM,IAAI;AACV,QAAO,OAAO,KAAK,EAAE,CAAC,QAAO,QAAO,QAAQ,aAAa,EAAE,SAAS,KAAA,KAAa,EAAE,SAAS,KAAA,EAAU;;;;;;;AAQxG,eAAe,eACb,YACyD;AACzD,KAAI,MAAM,iBAAiB,gBAAgB,kBAAkB,aAAa;CAE1E,MAAM,aAAa,eAAe,WAAW;AAE7C,KAAI,CAAC,YAAY;AACf,MAAI,KAAK,MAAM,gBAAgB,YAAY,WAAW,qCAAqC;AAC3F,SAAO;;AAGT,KAAI,KAAK,yBAAyB,aAAa;CAE/C,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,YAAY,QAAQ;UAClC,OAAO;AACd,QAAM,IAAI,YACR,kBAAkB,gBAAgB,IAAI,eAAe,MAAM,GAC5D;;CAGH,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;UACjB,OAAO;AACd,QAAM,IAAI,YACR,mBAAmB,gBAAgB,IAAI,eAAe,MAAM,GAC7D;;CAGH,MAAM,SAAS,sBAAsB,OAAO;AAE5C,KAAI,CAAC,OAAO,GACV,OAAM,IAAI,YAAY,WAAW,gBAAgB,KAAK,OAAO,QAAQ;AAGvE,KAAI,MAAM,2CAA2C,aAAa;AAElE,QAAO;EAAE,MAAM;EAAY,QAAQ,mBAAmB,OAAO,MAAM,gBAAgB;EAAE"}