@the-i18n-kit/cli 6.0.0 → 8.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 (91) hide show
  1. package/README.md +7 -57
  2. package/dist/bin.js +1 -1
  3. package/dist/{_shared-CkMAydRl.js → cli-BTMcWXGs.js} +157 -27
  4. package/dist/cli-BTMcWXGs.js.map +1 -0
  5. package/dist/config/framework/stubs/{next-intl-routing-lExp4Q3r.d.ts → next-intl-routing-i6Mlxwdt.d.ts} +1 -1
  6. package/dist/config/framework/stubs/{next-intl-routing-lExp4Q3r.d.ts.map → next-intl-routing-i6Mlxwdt.d.ts.map} +1 -1
  7. package/dist/{define-config-BeHvSYBP.d.ts → define-config-BpdVaEVR.d.ts} +1 -1
  8. package/dist/{define-config-DW-rgsU6.d.ts → define-config-ZRZw5PWy.d.ts} +40 -6
  9. package/dist/define-config-ZRZw5PWy.d.ts.map +1 -0
  10. package/dist/define-config.d.ts +1 -1
  11. package/dist/descriptors-Bo5UM031.js +1242 -0
  12. package/dist/descriptors-Bo5UM031.js.map +1 -0
  13. package/dist/detector-B4z_RZde.js +2189 -0
  14. package/dist/detector-B4z_RZde.js.map +1 -0
  15. package/dist/detector-DTfFbwSU.js +5 -0
  16. package/dist/{index-w-EQZEZF.d.ts → index-CWFWYHf5.d.ts} +728 -268
  17. package/dist/index-CWFWYHf5.d.ts.map +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +8 -4
  20. package/dist/json-writer-D0b6vEax.js +2 -0
  21. package/dist/json-writer-ek8yEI5R.js +342 -0
  22. package/dist/json-writer-ek8yEI5R.js.map +1 -0
  23. package/dist/operations-CKHEmLNJ.js +8 -0
  24. package/dist/{operations-PfRSTepi.js → operations-Daf2Ommm.js} +1655 -3298
  25. package/dist/operations-Daf2Ommm.js.map +1 -0
  26. package/dist/php-reader-BGx6Ii2d.js +2 -0
  27. package/dist/{php-reader-3Fgw80zK.js → php-reader-DRpcRVzv.js} +6 -3
  28. package/dist/{php-reader-3Fgw80zK.js.map → php-reader-DRpcRVzv.js.map} +1 -1
  29. package/dist/{providers-CAsU_aV1.js → project-config-eLR0J377.js} +24 -209
  30. package/dist/project-config-eLR0J377.js.map +1 -0
  31. package/dist/providers-BbVPelvp.js +406 -0
  32. package/dist/providers-BbVPelvp.js.map +1 -0
  33. package/dist/report-By1-5JtO.js +37 -0
  34. package/dist/report-By1-5JtO.js.map +1 -0
  35. package/dist/report-CHZgH9Wy.js +2 -0
  36. package/package.json +4 -16
  37. package/dist/_shared-CkMAydRl.js.map +0 -1
  38. package/dist/add-BBoVUNEq.js +0 -40
  39. package/dist/add-BBoVUNEq.js.map +0 -1
  40. package/dist/check-IosaN_EM.js +0 -41
  41. package/dist/check-IosaN_EM.js.map +0 -1
  42. package/dist/cli-CnZNGUGu.js +0 -70
  43. package/dist/cli-CnZNGUGu.js.map +0 -1
  44. package/dist/config/framework/stubs/unplugin-vue-i18n-mH7YdYqA.d.ts +0 -25
  45. package/dist/config/framework/stubs/unplugin-vue-i18n-mH7YdYqA.d.ts.map +0 -1
  46. package/dist/config/framework/stubs/unplugin-vue-i18n.js +0 -26
  47. package/dist/config/framework/stubs/unplugin-vue-i18n.js.map +0 -1
  48. package/dist/define-config-DW-rgsU6.d.ts.map +0 -1
  49. package/dist/detect-3jjcQJs1.js +0 -17
  50. package/dist/detect-3jjcQJs1.js.map +0 -1
  51. package/dist/empty-D3La0enO.js +0 -36
  52. package/dist/empty-D3La0enO.js.map +0 -1
  53. package/dist/find-duplicates-BlQoJDgu.js +0 -42
  54. package/dist/find-duplicates-BlQoJDgu.js.map +0 -1
  55. package/dist/get-Cr5O5zJX.js +0 -41
  56. package/dist/get-Cr5O5zJX.js.map +0 -1
  57. package/dist/index-w-EQZEZF.d.ts.map +0 -1
  58. package/dist/init-BRBMkcI0.js +0 -33
  59. package/dist/init-BRBMkcI0.js.map +0 -1
  60. package/dist/list-dirs-BMuByyuX.js +0 -17
  61. package/dist/list-dirs-BMuByyuX.js.map +0 -1
  62. package/dist/missing-CBOk4ZgD.js +0 -51
  63. package/dist/missing-CBOk4ZgD.js.map +0 -1
  64. package/dist/move-DOdPKnOV.js +0 -50
  65. package/dist/move-DOdPKnOV.js.map +0 -1
  66. package/dist/operations-PfRSTepi.js.map +0 -1
  67. package/dist/php-reader-CpnaPSpZ.js +0 -2
  68. package/dist/providers-CAsU_aV1.js.map +0 -1
  69. package/dist/remove-CVBjJV9h.js +0 -41
  70. package/dist/remove-CVBjJV9h.js.map +0 -1
  71. package/dist/remove-orphans-BaKEISxA.js +0 -57
  72. package/dist/remove-orphans-BaKEISxA.js.map +0 -1
  73. package/dist/rename-CN2ajUeb.js +0 -45
  74. package/dist/rename-CN2ajUeb.js.map +0 -1
  75. package/dist/scaffold-CUKUFuEy.js +0 -39
  76. package/dist/scaffold-CUKUFuEy.js.map +0 -1
  77. package/dist/scan-Bhv0Nmy3.js +0 -31
  78. package/dist/scan-Bhv0Nmy3.js.map +0 -1
  79. package/dist/search-CrkLgz3m.js +0 -49
  80. package/dist/search-CrkLgz3m.js.map +0 -1
  81. package/dist/status-DprS-qDC.js +0 -45
  82. package/dist/status-DprS-qDC.js.map +0 -1
  83. package/dist/translate-Cs6EdbzA.js +0 -92
  84. package/dist/translate-Cs6EdbzA.js.map +0 -1
  85. package/dist/translate-key-BBZpYQjL.js +0 -73
  86. package/dist/translate-key-BBZpYQjL.js.map +0 -1
  87. package/dist/update-BQuGxSSy.js +0 -40
  88. package/dist/update-BQuGxSSy.js.map +0 -1
  89. package/dist/write-B8z6I5Vf.js +0 -47
  90. package/dist/write-B8z6I5Vf.js.map +0 -1
  91. /package/dist/{bin-g05vSfAz.d.ts → bin-NyzIHE2F.d.ts} +0 -0
@@ -1,7 +1,127 @@
1
- import { a as LocaleDir, i as LocaleDefinition, n as defineI18nKitConfig, o as ProjectConfig, r as I18nConfig, s as LocaleFileFormat, t as I18nKitConfig } from "./define-config-DW-rgsU6.js";
1
+ import { a as LocaleDir, i as LocaleDefinition, n as defineI18nKitConfig, o as ProjectConfig, r as I18nConfig, t as I18nKitConfig } from "./define-config-ZRZw5PWy.js";
2
2
 
3
- //#region src/core/types.d.ts
3
+ //#region src/config/layer-graph.d.ts
4
4
 
5
+ /**
6
+ * Queryable view over the layer topology a resolved {@link I18nConfig}
7
+ * already carries: which locale dirs are canonical (alias-free), which
8
+ * layer owns an aliased dir, and which apps consume which layers.
9
+ *
10
+ * This is a pure derivation — no filesystem access, no config-shape
11
+ * changes. Cross-layer tooling (duplicate detection, scope-aware orphan
12
+ * scanning) builds on these queries instead of name-matching heuristics.
13
+ *
14
+ * ## Degenerate-case semantics
15
+ *
16
+ * These are load-bearing for consumers (scope-aware scanning must never
17
+ * wrongly narrow its scan scope):
18
+ *
19
+ * - **No app info** (`config.apps` empty or absent, e.g. hand-built
20
+ * configs): consumption edges are unknowable. `appsUsingLayer` and
21
+ * `layersOfApp` return `[]`, and `sharedLayers` conservatively contains
22
+ * *every* canonical layer — with no ownership information, every
23
+ * layer's keys must be treated as globally visible.
24
+ * - **Single-app config** (generic/Laravel/React adapters, or a Nuxt
25
+ * project with one app): the strict definition applies, so
26
+ * `sharedLayers` is empty (no layer is consumed by more than one app)
27
+ * and `appsUsingLayer` returns that one app for the layers it consumes.
28
+ * Per-layer scope then equals the whole project, which is correct.
29
+ * - **Canonical layer consumed by no app** (in a multi-app config):
30
+ * `appsUsingLayer` returns `[]` and the layer is not in `sharedLayers`.
31
+ * Callers should treat such layers conservatively (global scope).
32
+ */
33
+ interface LayerGraph {
34
+ /**
35
+ * Alias-free locale dirs, in `config.localeDirs` order. Aliased entries
36
+ * (e.g. `app-outlook` pointing at `app-shop`'s dir) are excluded.
37
+ */
38
+ canonicalLayers: LocaleDir[];
39
+ /**
40
+ * Resolve a layer name to the canonical layer that owns its locale dir.
41
+ * Follows chained `aliasOf` links (an alias may point at a layer that
42
+ * was itself demoted to an alias). Identity for canonical names and for
43
+ * names unknown to `localeDirs` (e.g. layers without locale dirs).
44
+ */
45
+ ownerOf: (layer: string) => string;
46
+ /**
47
+ * Names of apps whose consumed layers include the given layer. The
48
+ * queried name and each app's layer list are alias-resolved via
49
+ * {@link ownerOf} first, so querying an alias name yields the owner's
50
+ * consumers. Returns `[]` when no app info exists.
51
+ */
52
+ appsUsingLayer: (layer: string) => string[];
53
+ /**
54
+ * Canonical layers consumed by more than one app — e.g. a shared root
55
+ * layer in a multi-app monorepo, identified purely from consumption
56
+ * edges (no name matching). When no app info exists, contains every
57
+ * canonical layer (see degenerate-case semantics above).
58
+ */
59
+ sharedLayers: LocaleDir[];
60
+ /**
61
+ * Canonical layers the given app consumes (alias entries in the app's
62
+ * layer list resolve to their owners; layers without locale dirs are
63
+ * omitted). Returns `[]` for unknown apps or when no app info exists.
64
+ */
65
+ layersOfApp: (app: string) => LocaleDir[];
66
+ }
67
+ /**
68
+ * Build a {@link LayerGraph} from a resolved config's `localeDirs`
69
+ * (with their `aliasOf` markers) and `apps` (app → consumed-layers edges).
70
+ */
71
+ declare function buildLayerGraph(config: I18nConfig): LayerGraph;
72
+ /**
73
+ * The graph as plain data, for surfaces that can only carry JSON.
74
+ *
75
+ * {@link LayerGraph} is function-valued, so it cannot be serialised directly.
76
+ * This answers the question an agent actually has — *which layer does this key
77
+ * belong in* — which the flat layer list `discover` returned could not: a key
78
+ * used by more than one app belongs in a layer those apps share, and `shared`
79
+ * names those layers outright (#342).
80
+ *
81
+ * The degenerate cases documented on {@link LayerGraph} survive the flattening,
82
+ * because they are what keeps a consumer from wrongly narrowing scope:
83
+ * a config with no app info reports *every* canonical layer as shared, and a
84
+ * layer no app consumes appears in `consumers` with an empty array rather than
85
+ * being left out. Absent and "none" must not look alike here.
86
+ */
87
+ interface SerializedLayerGraph {
88
+ /** Alias-free layer names, in `config.localeDirs` order. */
89
+ canonical: string[];
90
+ /** Canonical layers consumed by more than one app — where shared keys belong. */
91
+ shared: string[];
92
+ /** Alias layer name → the canonical layer whose locale dir it points at. */
93
+ aliases: Record<string, string>;
94
+ /** Canonical layer name → the apps consuming it. Every canonical layer is a key. */
95
+ consumers: Record<string, string[]>;
96
+ }
97
+ /** Flatten {@link buildLayerGraph}'s view of `config` into plain JSON. */
98
+ declare function serializeLayerGraph(config: I18nConfig): SerializedLayerGraph;
99
+ //# sourceMappingURL=layer-graph.d.ts.map
100
+ //#endregion
101
+ //#region src/core/types.d.ts
102
+ /**
103
+ * The whole resolved project in one answer: the config, the locale dirs behind
104
+ * it, the topology those dirs form, and which locales are maintained by hand.
105
+ *
106
+ * A superset of `I18nConfig` rather than a wrapper around it, because every
107
+ * caller of the old three-call sequence merged the parts anyway and a nested
108
+ * `config` key would break each of them for nothing.
109
+ */
110
+ interface DescribeProjectResult extends I18nConfig {
111
+ /**
112
+ * Canonical codes of the locales the translate operations leave alone. The
113
+ * raw refs stay visible under `projectConfig.protectedLocales`.
114
+ */
115
+ protectedLocales: string[];
116
+ /** One entry per locale directory, with file counts and key namespaces. */
117
+ layers: LocaleDirInfo[];
118
+ /**
119
+ * Which layers are shared, which apps consume which layer, and what each
120
+ * alias points at — the topology behind the flat `layers` list, and what
121
+ * answers where a new key belongs.
122
+ */
123
+ layerGraph: SerializedLayerGraph;
124
+ }
5
125
  interface LocaleDirInfo {
6
126
  layer: string;
7
127
  path: string;
@@ -62,27 +182,6 @@ interface MutationResult {
62
182
  /** Present only when a ref matched several locales and precedence picked one. */
63
183
  ambiguousLocales?: LocaleRefAmbiguity[];
64
184
  }
65
- interface AddTranslationsResult {
66
- /** Present when dryRun=true */
67
- dryRun?: boolean;
68
- wouldAdd?: MutationPreview[];
69
- /** Present when dryRun=false */
70
- added?: string[];
71
- skipped: string[];
72
- filesWritten?: number;
73
- warnings?: string[];
74
- /** Present only when a locale ref resolved to nothing — see UnresolvedLocaleRef. */
75
- unresolvedLocales?: UnresolvedLocaleRef[];
76
- /** Present only when a locale ref matched several locales. */
77
- ambiguousLocales?: LocaleRefAmbiguity[];
78
- placeholderValidation?: PlaceholderValidationResult;
79
- summary?: {
80
- keysToAdd: number;
81
- keysSkipped: number;
82
- message: string;
83
- };
84
- skippedKeys?: string[];
85
- }
86
185
  interface WriteTranslationsResult {
87
186
  /** Present when dryRun=true */
88
187
  dryRun?: boolean;
@@ -104,26 +203,6 @@ interface WriteTranslationsResult {
104
203
  };
105
204
  skippedKeys?: string[];
106
205
  }
107
- interface UpdateTranslationsResult {
108
- /** Present when dryRun=true */
109
- dryRun?: boolean;
110
- wouldUpdate?: MutationPreview[];
111
- /** Present when dryRun=false */
112
- updated?: string[];
113
- skipped: string[];
114
- filesWritten?: number;
115
- /** Present only when a locale ref resolved to nothing — see UnresolvedLocaleRef. */
116
- unresolvedLocales?: UnresolvedLocaleRef[];
117
- /** Present only when a locale ref matched several locales. */
118
- ambiguousLocales?: LocaleRefAmbiguity[];
119
- placeholderValidation?: PlaceholderValidationResult;
120
- summary?: {
121
- keysToUpdate: number;
122
- keysSkipped: number;
123
- message: string;
124
- };
125
- skippedKeys?: string[];
126
- }
127
206
  /**
128
207
  * The config `init` emits. Only fields the matched adapter cannot derive:
129
208
  * writing a copy of what the framework already states creates a second source
@@ -173,16 +252,13 @@ interface InitProjectConfigResult {
173
252
  overwritten: boolean;
174
253
  }
175
254
  interface MissingTranslationsResult {
176
- /** Absent when the full report went to `reportFile` instead. */
177
- missing?: Record<string, Record<string, string[]>>;
255
+ missing: Record<string, Record<string, string[]>>;
178
256
  summary: {
179
257
  referenceLocale: string | LocaleRefInfo;
180
258
  targetLocales: Array<string | LocaleRefInfo>;
181
259
  layersScanned: string[];
182
260
  totalMissingKeys: number;
183
261
  };
184
- /** Present when reportOutput is configured */
185
- reportFile?: string;
186
262
  }
187
263
  interface LocaleStatus extends LocaleRefInfo {
188
264
  total: number;
@@ -203,10 +279,21 @@ interface LayerStatus {
203
279
  missing: number;
204
280
  empty: number;
205
281
  completion: number;
282
+ /**
283
+ * Apps whose declared layers include this one. Empty means either no app
284
+ * information exists (hand-built configs) or nothing consumes the layer.
285
+ */
286
+ consumedBy: string[];
206
287
  }
207
288
  interface TranslationStatusSummary {
208
289
  referenceLocale: LocaleRefInfo;
209
290
  layersScanned: string[];
291
+ /**
292
+ * Scanned layers no app consumes — keys nothing can render. Stays empty
293
+ * unless the project declares more than one app, since a single-app project
294
+ * has no consumption edges worth reporting on.
295
+ */
296
+ unconsumedLayers: string[];
210
297
  localesChecked: number;
211
298
  protectedLocales: string[];
212
299
  totalKeys: number;
@@ -217,31 +304,76 @@ interface TranslationStatusSummary {
217
304
  completionPercent: number;
218
305
  }
219
306
  interface TranslationStatusResult {
220
- locales?: LocaleStatus[];
221
- layers?: LayerStatus[];
307
+ locales: LocaleStatus[];
308
+ layers: LayerStatus[];
309
+ /**
310
+ * Locale → layer → the keys `summary.emptyKeys` counts: empty in that target
311
+ * locale while the reference locale has a value. Present only when the caller
312
+ * asked to list them. Added to the result rather than replacing it, so asking
313
+ * which keys are empty still answers the coverage question that prompted it.
314
+ */
315
+ empty?: Record<string, Record<string, string[]>>;
316
+ /**
317
+ * Layer → keys whose value is empty in the reference locale itself. Nothing
318
+ * to translate from, so excluded from every count; listed so a deliberate
319
+ * blank and a forgotten one can be told apart. Present only with `empty`,
320
+ * and only when there are any.
321
+ */
322
+ emptyInReference?: Record<string, string[]>;
222
323
  summary: TranslationStatusSummary;
223
- /** Present when the full breakdown went to a file instead. */
224
- reportFile?: string;
225
324
  }
226
325
  interface EmptyTranslationsResult {
227
- /** Absent when the full report went to `reportFile` instead. */
228
- emptyKeys?: Record<string, Record<string, string[]>>;
326
+ emptyKeys: Record<string, Record<string, string[]>>;
229
327
  summary: {
230
328
  totalEmpty: number;
231
329
  localesChecked: string[];
232
330
  layersChecked: string[];
233
331
  };
234
- /** Present when reportOutput is configured */
235
- reportFile?: string;
236
332
  }
333
+ /**
334
+ * One key in one locale of one layer — the detail rows, returned when the
335
+ * caller asks for them.
336
+ */
237
337
  interface SearchMatch {
238
338
  layer: string;
239
339
  locale: string;
240
340
  key: string;
241
341
  value: unknown;
242
342
  }
343
+ /**
344
+ * One key, however many layers and locales define it — the row a search
345
+ * returns by default.
346
+ *
347
+ * A key that exists in seven layers and thirty locales used to come back as
348
+ * dozens of near-identical rows, which an agent pays for and then has to group
349
+ * itself before it can answer the question it asked: does a translation for
350
+ * this text already exist, and where. Grouped here instead, because `layers`
351
+ * is the answer to the second half — one layer means reuse it, several mean
352
+ * the key is already duplicated.
353
+ */
354
+ interface SearchKeyMatch {
355
+ key: string;
356
+ /** Every searched layer that defines the key, in layer order. */
357
+ layers: string[];
358
+ /** What `locale` holds for the key. */
359
+ value: unknown;
360
+ /**
361
+ * Which locale `value` was read from: the reference locale where it defines
362
+ * the key, otherwise the first searched locale that does.
363
+ */
364
+ locale: string;
365
+ /** How many of the searched locales define the key. */
366
+ localeCount: number;
367
+ }
368
+ /** How `query` is compared against a key path or a value. */
369
+ type SearchMatchMode = 'contains' | 'exact' | 'fuzzy';
243
370
  interface SearchTranslationsResult {
244
- matches: SearchMatch[];
371
+ /**
372
+ * One row per key by default; one row per key and locale when the caller
373
+ * passed `includeLocales`.
374
+ */
375
+ matches: SearchKeyMatch[] | SearchMatch[];
376
+ /** How many rows `matches` holds, whichever shape it is in. */
245
377
  totalMatches: number;
246
378
  }
247
379
  interface RemoveTranslationsPreview {
@@ -297,6 +429,12 @@ interface MoveTranslationKeyPlanEntry {
297
429
  */
298
430
  action: 'move' | 'deduplicate';
299
431
  }
432
+ /**
433
+ * What a move returns: a rename result when the key stayed in its layer, a move
434
+ * result when it changed layers. A union rather than one merged shape, so
435
+ * neither half carries fields that can never be set for the other.
436
+ */
437
+ type MoveTranslationKeyOutcome = MoveTranslationKeyResult | RenameTranslationKeyResult;
300
438
  interface MoveTranslationKeyResult {
301
439
  /** Present when dryRun=true */
302
440
  dryRun?: boolean;
@@ -326,6 +464,29 @@ type TranslateMode = 'provider' | 'agent' | 'dry-run';
326
464
  type TranslateFailReason = 'provider-error' | 'omitted-by-model' | 'placeholder-mismatch' | 'plural-mismatch' | 'write-error' | 'truncated';
327
465
  /** Why a key or locale was intentionally not attempted. */
328
466
  type TranslateSkipReason = 'no-provider' | 'already-translated' | 'protected-locale';
467
+ /** What translate_missing accepts. `layer` omitted means every layer at once. */
468
+ interface TranslateMissingOptions {
469
+ layer?: string;
470
+ referenceLocale?: string;
471
+ targetLocales?: string[];
472
+ locales?: string[];
473
+ keys?: string[];
474
+ batchSize?: number;
475
+ dryRun?: boolean;
476
+ compact?: boolean;
477
+ projectDir?: string;
478
+ /**
479
+ * Also re-translate keys whose target was written from source text that has
480
+ * changed since (translation memory only). Off by default: without it the
481
+ * operation still never touches an existing value, it only reports the stale
482
+ * ones in `stale`.
483
+ */
484
+ overwriteStale?: boolean;
485
+ translateFn?: TranslateFn;
486
+ progressFn?: ProgressFn;
487
+ /** Called once after the pre-scan with the computed total number of progress steps. */
488
+ onProgressTotal?: (total: number) => void;
489
+ }
329
490
  interface TranslateMissingLocaleResult {
330
491
  mode: TranslateMode;
331
492
  /** Number of missing keys found for this locale. Always equals
@@ -342,6 +503,14 @@ interface TranslateMissingLocaleResult {
342
503
  key: string;
343
504
  reason: TranslateSkipReason;
344
505
  }>;
506
+ /**
507
+ * Translation-memory only: keys whose target value was written from source
508
+ * text that has changed since, and which this run left untouched. A bucket of
509
+ * its own, not part of `missing` — these keys are translated, just outdated —
510
+ * so the invariant above still holds. Re-translating them needs
511
+ * `overwriteStale`, which counts them into `missing` instead.
512
+ */
513
+ stale?: string[];
345
514
  batches?: number;
346
515
  model?: string;
347
516
  writeError?: string;
@@ -356,6 +525,7 @@ interface TranslateMissingCompactEntry {
356
525
  failed: number;
357
526
  skipped: number;
358
527
  wouldTranslate?: number;
528
+ stale?: number;
359
529
  batches?: number;
360
530
  model?: string;
361
531
  writeError?: string;
@@ -372,6 +542,8 @@ interface TranslateMissingResult {
372
542
  totalFailed: number;
373
543
  totalSkipped: number;
374
544
  totalWouldTranslate?: number;
545
+ /** Translation-memory only: stale keys left untouched, across all locales. */
546
+ staleCount?: number;
375
547
  layer: string;
376
548
  referenceLocale: string | LocaleRefInfo;
377
549
  targetLocales: Array<string | LocaleRefInfo>;
@@ -393,6 +565,8 @@ interface TranslateAllLayersSummary {
393
565
  totalFailed: number;
394
566
  totalSkipped: number;
395
567
  totalWouldTranslate?: number;
568
+ /** Translation-memory only: stale keys left untouched, across all layers and locales. */
569
+ staleCount?: number;
396
570
  /** Layer names that were translated. */
397
571
  layers: string[];
398
572
  byLayer: TranslateLayerTotals[];
@@ -425,6 +599,18 @@ interface TranslateKeyLocaleIssue {
425
599
  reason: TranslateFailReason | 'read-error';
426
600
  detail?: string;
427
601
  }
602
+ /**
603
+ * One locale translate_key deliberately left alone. `reason` is a closed set;
604
+ * `stale` refines 'already-translated' rather than extending it, so a caller
605
+ * can tell an existing translation that still matches its source from one the
606
+ * source has since outgrown.
607
+ */
608
+ interface TranslateKeySkip {
609
+ locale: string;
610
+ reason: TranslateSkipReason;
611
+ /** Translation-memory only: whether the existing value is out of date. */
612
+ stale?: boolean;
613
+ }
428
614
  interface TranslateKeyResult {
429
615
  key: string;
430
616
  sourceLocale: LocaleRefInfo;
@@ -433,10 +619,7 @@ interface TranslateKeyResult {
433
619
  translated: string[];
434
620
  /** Dry-run only: locales that would be translated. */
435
621
  wouldTranslate?: string[];
436
- skipped: Array<{
437
- locale: string;
438
- reason: TranslateSkipReason;
439
- }>;
622
+ skipped: TranslateKeySkip[];
440
623
  failed: TranslateKeyLocaleIssue[];
441
624
  filesWritten: number;
442
625
  dryRun: boolean;
@@ -468,9 +651,22 @@ interface UnresolvedKeyWarningRef {
468
651
  callee: string;
469
652
  suggestedIgnorePattern?: string;
470
653
  }
654
+ /**
655
+ * One `declaredNamespaces` entry with the keys it answers for.
656
+ *
657
+ * `matchedKeys` is what makes a declaration auditable in both directions: the
658
+ * keys a reader would otherwise see in the orphan list, and — when it is empty
659
+ * — a declaration whose namespace no longer exists.
660
+ */
661
+ interface DeclaredNamespaceRef {
662
+ pattern: string;
663
+ /** What keeps these keys alive, as declared in the config. */
664
+ reason: string;
665
+ /** Keys of the checked layers this pattern covers. Empty means the declaration is stale. */
666
+ matchedKeys: string[];
667
+ }
471
668
  interface FindOrphanKeysResult {
472
- /** Absent when the full report went to `reportFile` instead. */
473
- orphanKeys?: Record<string, string[]>;
669
+ orphanKeys: Record<string, string[]>;
474
670
  uncertainKeys?: Record<string, string[]>;
475
671
  /**
476
672
  * Keys kept alive solely by the bare-candidate net — nothing a frontend
@@ -483,6 +679,9 @@ interface FindOrphanKeysResult {
483
679
  /** Keys used only from apps that do not consume the owning layer. */
484
680
  misplacedUsages?: MisplacedUsageRef[];
485
681
  misplacedUsageNote?: string;
682
+ /** Every declared namespace with the keys it covers. Present when any is declared. */
683
+ declaredNamespaces?: DeclaredNamespaceRef[];
684
+ declaredNamespaceNote?: string;
486
685
  summary: {
487
686
  totalKeys: number;
488
687
  orphanCount: number;
@@ -491,6 +690,8 @@ interface FindOrphanKeysResult {
491
690
  misplacedCount?: number;
492
691
  dynamicMatchedCount?: number;
493
692
  ignoredCount?: number;
693
+ /** Keys withheld from the orphan list by a declared namespace. */
694
+ declaredCount?: number;
494
695
  usedCount?: number;
495
696
  filesScanned: number;
496
697
  /** Files a syntax frontend declined; pattern matching read them instead. */
@@ -504,13 +705,10 @@ interface FindOrphanKeysResult {
504
705
  dynamicKeyWarning?: string;
505
706
  dynamicKeys?: DynamicKeyRef[];
506
707
  unresolvedKeyWarnings?: UnresolvedKeyWarningRef[];
507
- /** Present when reportOutput is configured */
508
- reportFile?: string;
509
708
  }
510
709
  /** Where each requested key is referenced in source. */
511
710
  interface CodeUsageResult {
512
- /** Absent when the full report went to `reportFile` instead. */
513
- usages?: Record<string, CodeUsageRef[]>;
711
+ usages: Record<string, CodeUsageRef[]>;
514
712
  /** Requested keys with no reference anywhere in the scanned source. */
515
713
  notFoundInCode?: string[];
516
714
  /** Dynamic expressions that could reach the requested keys. */
@@ -524,8 +722,6 @@ interface CodeUsageResult {
524
722
  dirsScanned?: string[];
525
723
  message?: string;
526
724
  };
527
- /** Present when the full report went to a file instead. */
528
- reportFile?: string;
529
725
  }
530
726
  interface CodeUsageRef {
531
727
  file: string;
@@ -544,8 +740,6 @@ interface ScanCodeUsageResult {
544
740
  };
545
741
  notFoundInCode?: string[];
546
742
  dynamicKeys?: DynamicKeyRef[];
547
- /** Present when reportOutput is configured */
548
- reportFile?: string;
549
743
  }
550
744
  interface RemoveOrphanKeysResult {
551
745
  orphanKeys?: Record<string, string[]>;
@@ -553,6 +747,9 @@ interface RemoveOrphanKeysResult {
553
747
  uncertainKeys?: Record<string, string[]>;
554
748
  misplacedUsages?: MisplacedUsageRef[];
555
749
  misplacedUsageNote?: string;
750
+ /** Every declared namespace with the keys it covers — the keys this run will not delete. */
751
+ declaredNamespaces?: DeclaredNamespaceRef[];
752
+ declaredNamespaceNote?: string;
556
753
  summary: {
557
754
  dryRun?: boolean;
558
755
  totalKeys: number;
@@ -562,6 +759,8 @@ interface RemoveOrphanKeysResult {
562
759
  misplacedCount?: number;
563
760
  dynamicMatchedCount?: number;
564
761
  ignoredCount?: number;
762
+ /** Keys withheld from the orphan list by a declared namespace. */
763
+ declaredCount?: number;
565
764
  usedCount?: number;
566
765
  remainingCount?: number;
567
766
  filesScanned?: number;
@@ -575,8 +774,6 @@ interface RemoveOrphanKeysResult {
575
774
  dynamicKeyWarning?: string;
576
775
  dynamicKeys?: DynamicKeyRef[];
577
776
  unresolvedKeyWarnings?: UnresolvedKeyWarningRef[];
578
- /** Present when reportOutput is configured */
579
- reportFile?: string;
580
777
  }
581
778
  interface ScaffoldLocaleFileInfo {
582
779
  locale: string;
@@ -625,7 +822,7 @@ declare function findLocaleImpl(config: I18nConfig, localeRef: string): LocaleDe
625
822
  * project default. Throws LOCALE_NOT_FOUND listing the available codes.
626
823
  */
627
824
  //#endregion
628
- //#region src/core/ops-translate.d.ts
825
+ //#region src/core/translate/targets.d.ts
629
826
  /**
630
827
  * Resolve the config's `protectedLocales` entries (any locale ref: code,
631
828
  * language tag, or file name) against the known locales. Entries that do not
@@ -633,6 +830,15 @@ declare function findLocaleImpl(config: I18nConfig, localeRef: string): LocaleDe
633
830
  * definitions, deduplicated by canonical code.
634
831
  */
635
832
  declare function resolveProtectedLocales(config: I18nConfig): LocaleDefinition[];
833
+ /**
834
+ * Resolve translate_missing target locales. Protected locales are excluded
835
+ * from the DEFAULT target set (returned separately so the caller can report
836
+ * them as skipped); naming one explicitly in targetLocales overrides the
837
+ * protection with a warning.
838
+ */
839
+
840
+ //#endregion
841
+ //#region src/core/translate/run.d.ts
636
842
  /**
637
843
  * Find keys missing in target locales and translate them.
638
844
  *
@@ -642,21 +848,7 @@ declare function resolveProtectedLocales(config: I18nConfig): LocaleDefinition[]
642
848
  * When `layer` is omitted, every canonical locale-backed layer is translated
643
849
  * in one run and the results are aggregated (see translateMissingAllLayers).
644
850
  */
645
- declare function translateMissing(opts: {
646
- layer?: string;
647
- referenceLocale?: string;
648
- targetLocales?: string[];
649
- locales?: string[];
650
- keys?: string[];
651
- batchSize?: number;
652
- dryRun?: boolean;
653
- compact?: boolean;
654
- projectDir?: string;
655
- translateFn?: TranslateFn;
656
- progressFn?: ProgressFn;
657
- /** Called once after the pre-scan with the computed total number of progress steps. */
658
- onProgressTotal?: (total: number) => void;
659
- }): Promise<TranslateMissingOutcome>;
851
+ declare function translateMissing(opts: TranslateMissingOptions): Promise<TranslateMissingOutcome>;
660
852
  /**
661
853
  * Translate one key from a source locale into target locales. Unlike
662
854
  * translate_missing, this can overwrite stale existing target values.
@@ -673,9 +865,23 @@ declare function translateKey(opts: {
673
865
  projectDir?: string;
674
866
  translateFn?: TranslateFn;
675
867
  }): Promise<TranslateKeyResult>;
676
- //# sourceMappingURL=ops-translate.d.ts.map
868
+ //# sourceMappingURL=run.d.ts.map
677
869
  //#endregion
678
870
  //#region src/core/ops-read.d.ts
871
+ /**
872
+ * Everything a caller needs to know about a project before touching it:
873
+ * resolved config, locale directories, the layer topology, and which locales
874
+ * are hand-maintained.
875
+ *
876
+ * This composition used to live in the MCP `discover` handler, so the terminal
877
+ * had no way to ask the question its own docs told people to ask — and the two
878
+ * surfaces would have had to be kept in step by hand once one of them grew a
879
+ * field. Callers add whatever is theirs alone (the MCP server adds the
880
+ * translation backend it resolved at startup); the project half is here.
881
+ */
882
+ declare function describeProject(opts?: {
883
+ projectDir?: string;
884
+ }): Promise<DescribeProjectResult>;
679
885
  /**
680
886
  * Detect the i18n configuration from the project, always bypassing the
681
887
  * config cache (clears it first).
@@ -704,7 +910,6 @@ declare function getMissingTranslations(opts: {
704
910
  targetLocales?: string[];
705
911
  locales?: string[];
706
912
  projectDir?: string;
707
- outputFile?: string;
708
913
  }): Promise<MissingTranslationsResult>;
709
914
  /**
710
915
  * Find translation keys that have empty string values in locale files.
@@ -713,27 +918,30 @@ declare function findEmptyTranslations(opts: {
713
918
  layer?: string;
714
919
  locale?: string;
715
920
  projectDir?: string;
716
- outputFile?: string;
717
921
  }): Promise<EmptyTranslationsResult>;
718
922
  /**
719
- * Search translation files by key pattern or value substring.
923
+ * The scan behind {@link findEmptyTranslations}, against a config the caller
924
+ * already has.
925
+ *
926
+ * Separate so `getTranslationStatus` can embed the listing under its own
927
+ * `--list-empty` flag without detecting the project a second time.
928
+ */
929
+
930
+ /**
931
+ * Search translation files by key path or value.
932
+ *
933
+ * Returns one row per key. The detail rows — one per key and locale — are what
934
+ * `includeLocales` asks for.
720
935
  */
721
936
  declare function searchTranslations(opts: {
722
937
  query: string;
723
938
  searchIn?: 'keys' | 'values' | 'both';
939
+ matchMode?: SearchMatchMode;
940
+ includeLocales?: boolean;
724
941
  layer?: string;
725
942
  locale?: string;
726
943
  projectDir?: string;
727
- outputFile?: string;
728
- }): Promise<{
729
- matches: SearchMatch[];
730
- totalMatches: number;
731
- } | {
732
- reportFile: string;
733
- summary: {
734
- totalMatches: number;
735
- };
736
- }>;
944
+ }): Promise<SearchTranslationsResult>;
737
945
  interface NamespaceNode {
738
946
  keyCount: number;
739
947
  children?: Record<string, NamespaceNode>;
@@ -770,28 +978,6 @@ declare function writeTranslations(opts: {
770
978
  dryRun?: boolean;
771
979
  projectDir?: string;
772
980
  }): Promise<WriteTranslationsResult>;
773
- /**
774
- * Add new translation keys to the specified layer.
775
- *
776
- * @deprecated Use writeTranslations with mode: 'add' instead.
777
- */
778
- declare function addTranslations(opts: {
779
- layer: string;
780
- translations: Record<string, Record<string, string>>;
781
- dryRun?: boolean;
782
- projectDir?: string;
783
- }): Promise<AddTranslationsResult>;
784
- /**
785
- * Update existing translation keys in the specified layer.
786
- *
787
- * @deprecated Use writeTranslations with mode: 'update' instead.
788
- */
789
- declare function updateTranslations(opts: {
790
- layer: string;
791
- translations: Record<string, Record<string, string>>;
792
- dryRun?: boolean;
793
- projectDir?: string;
794
- }): Promise<UpdateTranslationsResult>;
795
981
  /**
796
982
  * Remove one or more translation keys from ALL locale files in the specified layer.
797
983
  */
@@ -802,7 +988,12 @@ declare function removeTranslations(opts: {
802
988
  projectDir?: string;
803
989
  }): Promise<RemoveTranslationsResult>;
804
990
  /**
805
- * Rename/move a translation key across ALL locale files in a layer.
991
+ * Rename a translation key across ALL locale files in one layer.
992
+ *
993
+ * Reachable on both surfaces through {@link moveTranslationKey}, which routes a
994
+ * same-layer request here. Kept exported because renaming within a layer is a
995
+ * complete operation on its own, and a programmatic caller that means exactly
996
+ * that should not have to express it as a move to nowhere.
806
997
  */
807
998
  declare function renameTranslationKey(opts: {
808
999
  layer: string;
@@ -821,7 +1012,13 @@ declare function scaffoldLocaleFiles(opts: {
821
1012
  projectDir?: string;
822
1013
  }): Promise<ScaffoldLocaleResult>;
823
1014
  /**
824
- * Move a key from one layer to another, carrying every locale that defines it.
1015
+ * Move a key: to another layer, to another key path, or both.
1016
+ *
1017
+ * One entry point rather than two, because the caller's intent is "this key
1018
+ * belongs somewhere else" and whether that somewhere else is a different layer
1019
+ * is a detail of the project's shape, not a different operation. Omitting
1020
+ * `toLayer` (or naming the layer the key already lives in) is a rename within
1021
+ * the layer and routes to {@link renameTranslationKey}.
825
1022
  *
826
1023
  * Promoting an app-layer key to the shared layer once a second app needs it is
827
1024
  * a first-class operation in a layered monorepo, and composing it out of
@@ -842,13 +1039,15 @@ declare function scaffoldLocaleFiles(opts: {
842
1039
  * silently.
843
1040
  */
844
1041
  declare function moveTranslationKey(opts: {
845
- fromLayer: string;
846
- toLayer: string;
1042
+ /** Layer the key lives in today. */
1043
+ layer: string;
847
1044
  key: string;
1045
+ /** Layer to move it to. Omitted, or equal to `layer`, means a rename in place. */
1046
+ toLayer?: string;
848
1047
  newKey?: string;
849
1048
  dryRun?: boolean;
850
1049
  projectDir?: string;
851
- }): Promise<MoveTranslationKeyResult>;
1050
+ }): Promise<MoveTranslationKeyOutcome>;
852
1051
  //# sourceMappingURL=ops-write.d.ts.map
853
1052
  //#endregion
854
1053
  //#region src/core/ops-status.d.ts
@@ -863,8 +1062,13 @@ declare function moveTranslationKey(opts: {
863
1062
  declare function getTranslationStatus(opts: {
864
1063
  layer?: string;
865
1064
  referenceLocale?: string;
1065
+ /**
1066
+ * Also list the keys behind `summary.emptyKeys`, under `empty`. Off by
1067
+ * default: the count is what a health check reads, and the list grows with
1068
+ * the project.
1069
+ */
1070
+ listEmpty?: boolean;
866
1071
  projectDir?: string;
867
- outputFile?: string;
868
1072
  }): Promise<TranslationStatusResult>;
869
1073
  //# sourceMappingURL=ops-status.d.ts.map
870
1074
  //#endregion
@@ -909,13 +1113,9 @@ type CallArgument = /** A literal: `t('common.save')`. */
909
1113
  kind: 'unknown';
910
1114
  };
911
1115
  /** Everything one file yields. Unchanged from what the scanner already consumes. */
912
- interface FileEvidence {
913
- usages: KeyUsage[];
914
- dynamicKeys: DynamicKeyUsage[];
915
- bareStringCandidates: Set<string>;
916
- }
1116
+
917
1117
  interface LanguageFrontend {
918
- /** For diagnostics and for the differential harness. */
1118
+ /** Names the frontend in diagnostics, so a decline says which one declined. */
919
1119
  readonly name: string;
920
1120
  /** Whether this frontend reads that file. */
921
1121
  handles(filePath: string): boolean;
@@ -950,17 +1150,13 @@ interface ScanPatternSet {
950
1150
  * producing `${_}.header`-class candidates that suppress every key ending
951
1151
  * in those segments. Language-neutral shapes (dotted literals,
952
1152
  * trailing-dot prefixes) always run.
1153
+ *
1154
+ * Two-valued only because the two supported languages need two shape sets.
1155
+ * A third frontend makes this a language identifier rather than a family
1156
+ * flag — widen it then, and key it on the language the scanner is reading.
953
1157
  */
954
1158
  bareShapes?: 'js' | 'php';
955
1159
  }
956
- declare const VUE_NUXT_PATTERNS: ScanPatternSet;
957
- /**
958
- * Maps locale file format to the appropriate scan pattern set.
959
- * 'php-array' → Laravel (PHP translation helpers in Blade/PHP files).
960
- * 'json' / undefined → Vue/Nuxt ($t / t calls in Vue/TS/JS files).
961
- */
962
- declare function getPatternSet(format?: LocaleFileFormat): ScanPatternSet;
963
- //# sourceMappingURL=patterns.d.ts.map
964
1160
  //#endregion
965
1161
  //#region src/scanner/code-scanner.d.ts
966
1162
  interface KeyUsage {
@@ -1005,7 +1201,18 @@ interface ScanResult {
1005
1201
  * frontend reports call sites, the rules decide what they mean.
1006
1202
  */
1007
1203
 
1008
- declare function scanSourceFiles(rootDir: string, excludeDirs?: string[], patterns?: ScanPatternSet, frontends?: LanguageFrontend[]): Promise<ScanResult>;
1204
+ /** What a caller can hand a scan beyond its inputs. */
1205
+ interface ScanSourceFilesHooks {
1206
+ /** Files to read, relative to `rootDir` — a {@link globSourceFiles} result, reused instead of walking the tree twice. */
1207
+ files?: string[];
1208
+ /**
1209
+ * Called once per file, right after it was read. `done` counts completions,
1210
+ * so it fires in completion order while the scan result itself stays in
1211
+ * input order — a counter is all that depends on the timing.
1212
+ */
1213
+ onFile?: (done: number, total: number, file: string) => void;
1214
+ }
1215
+ declare function scanSourceFiles(rootDir: string, excludeDirs?: string[], patterns?: ScanPatternSet, frontends?: LanguageFrontend[], hooks?: ScanSourceFilesHooks): Promise<ScanResult>;
1009
1216
  //#endregion
1010
1217
  //#region src/core/ops-orphans.d.ts
1011
1218
  /**
@@ -1024,7 +1231,10 @@ declare function findOrphanKeys(opts: {
1024
1231
  scanDirs?: string[];
1025
1232
  excludeDirs?: string[];
1026
1233
  projectDir?: string;
1027
- outputFile?: string;
1234
+ /** Scanning every source file of every app takes seconds — a caller that asked for progress hears about each stride of files. */
1235
+ progressFn?: ProgressFn;
1236
+ /** Called once with the number of progress steps, before the first `progressFn` call. */
1237
+ onProgressTotal?: (total: number) => void;
1028
1238
  }): Promise<FindOrphanKeysResult>;
1029
1239
  /**
1030
1240
  * Scan Vue/TS source files to find where translation keys are referenced.
@@ -1034,7 +1244,6 @@ declare function scanCodeUsage(opts: {
1034
1244
  scanDirs?: string[];
1035
1245
  excludeDirs?: string[];
1036
1246
  projectDir?: string;
1037
- outputFile?: string;
1038
1247
  }): Promise<CodeUsageResult>;
1039
1248
  /**
1040
1249
  * Find translation keys not referenced in source code and remove them.
@@ -1047,9 +1256,10 @@ declare function removeOrphanKeys(opts: {
1047
1256
  excludeDirs?: string[];
1048
1257
  dryRun?: boolean;
1049
1258
  projectDir?: string;
1050
- outputFile?: string;
1051
- /** Also write the orphan findings as a GitLab Code Quality JSON array to this path. */
1052
- codequalityOutput?: string;
1259
+ /** Same scan as {@link findOrphanKeys}, same reporting. */
1260
+ progressFn?: ProgressFn;
1261
+ /** Called once with the number of progress steps, before the first `progressFn` call. */
1262
+ onProgressTotal?: (total: number) => void;
1053
1263
  }): Promise<RemoveOrphanKeysResult>;
1054
1264
  //# sourceMappingURL=ops-orphans.d.ts.map
1055
1265
  //#endregion
@@ -1114,7 +1324,6 @@ interface FindDuplicateKeysResult {
1114
1324
  declare function findDuplicateKeys(opts?: {
1115
1325
  locale?: string;
1116
1326
  projectDir?: string;
1117
- outputFile?: string;
1118
1327
  /**
1119
1328
  * Also group keys by the value they carry. Off by default: it reads every
1120
1329
  * canonical layer rather than only the paired ones, and the existing result
@@ -1123,10 +1332,7 @@ declare function findDuplicateKeys(opts?: {
1123
1332
  byValue?: boolean;
1124
1333
  /** Shortest value worth grouping. Below it, repetition is usually legitimate. */
1125
1334
  minValueLength?: number;
1126
- }): Promise<FindDuplicateKeysResult | {
1127
- reportFile: string;
1128
- summary: FindDuplicateKeysSummary;
1129
- }>;
1335
+ }): Promise<FindDuplicateKeysResult>;
1130
1336
  //# sourceMappingURL=ops-duplicates.d.ts.map
1131
1337
  //#endregion
1132
1338
  //#region src/core/ops-check.d.ts
@@ -1155,13 +1361,31 @@ interface UncertainKeyFinding extends UndefinedKeyFinding {
1155
1361
  /** Why this is not a hard finding. */
1156
1362
  reason: string;
1157
1363
  }
1364
+ /** The locale file `write` extracted the undefined keys into. */
1365
+ interface ExtractedUndefinedKeys {
1366
+ layer: string;
1367
+ /** The project's default locale — the source every other locale is filled from. */
1368
+ locale: string;
1369
+ /** The keys that reached the file, in alphabetical order. */
1370
+ keys: string[];
1371
+ }
1158
1372
  interface CheckUndefinedKeysSummary {
1159
1373
  /** Distinct statically referenced keys across all scan units. */
1160
1374
  usedKeysChecked: number;
1375
+ /**
1376
+ * Keys that render raw at runtime, which is what the gate reads. After a
1377
+ * `write` run this counts the ones still undefined: an extracted key now has
1378
+ * a definition — an empty one — so it resolves, and `status` reports it as an
1379
+ * empty translation instead.
1380
+ */
1161
1381
  undefinedCount: number;
1382
+ /** Keys extracted into a locale file. Present only alongside `written`. */
1383
+ writtenCount?: number;
1162
1384
  uncertainCount: number;
1163
1385
  /** Unresolvable keys excluded by orphanScan ignorePatterns. */
1164
1386
  ignoredCount: number;
1387
+ /** Unresolvable keys covered by a declaredNamespaces entry — defined by contract, never written. */
1388
+ declaredCount: number;
1165
1389
  filesScanned: number;
1166
1390
  /** Files a syntax frontend declined; pattern matching read them instead. */
1167
1391
  filesDeclined: number;
@@ -1171,9 +1395,19 @@ interface CheckUndefinedKeysSummary {
1171
1395
  message: string;
1172
1396
  }
1173
1397
  interface CheckUndefinedKeysResult {
1398
+ /**
1399
+ * The findings as the scan made them. A `write` run leaves them in place —
1400
+ * what was extracted is named in `written`, and the summary counts what is
1401
+ * left — because the call sites are what a reader has to visit either way.
1402
+ */
1174
1403
  undefinedKeys: UndefinedKeyFinding[];
1175
1404
  uncertainKeys: UncertainKeyFinding[];
1176
1405
  limitation: string;
1406
+ /**
1407
+ * Present only when `write` was asked for and the scan found something to
1408
+ * write. A clean scan reports nothing here, having written nothing.
1409
+ */
1410
+ written?: ExtractedUndefinedKeys;
1177
1411
  summary: CheckUndefinedKeysSummary;
1178
1412
  }
1179
1413
  /**
@@ -1186,6 +1420,9 @@ interface CheckUndefinedKeysResult {
1186
1420
  * scopeByLayer — a unit resolves exactly the layers it vouches for). The
1187
1421
  * graph's degenerate semantics carry over: with no app info every layer is
1188
1422
  * resolvable everywhere, so only keys defined in NO layer are flagged.
1423
+ *
1424
+ * With `write`, the findings are also extracted into a locale file as empty
1425
+ * translations, which is what turns a report into the first half of the fix.
1189
1426
  */
1190
1427
  declare function checkUndefinedKeys(opts?: {
1191
1428
  locale?: string;
@@ -1195,128 +1432,368 @@ declare function checkUndefinedKeys(opts?: {
1195
1432
  */
1196
1433
  scanDirs?: string[];
1197
1434
  excludeDirs?: string[];
1435
+ /** Extract the undefined keys into a locale file. See extractUndefinedKeys. */
1436
+ write?: boolean;
1437
+ /** The layer to extract into, where the findings alone do not decide. */
1438
+ layer?: string;
1198
1439
  projectDir?: string;
1199
- outputFile?: string;
1200
- /** Also write the findings as a GitLab Code Quality JSON array to this path. */
1201
- codequalityOutput?: string;
1202
- }): Promise<CheckUndefinedKeysResult | {
1203
- reportFile: string;
1204
- summary: CheckUndefinedKeysSummary;
1205
- }>;
1440
+ }): Promise<CheckUndefinedKeysResult>;
1206
1441
  //# sourceMappingURL=ops-check.d.ts.map
1207
1442
  //#endregion
1208
- //#region src/config/detector.d.ts
1209
- declare function detectI18nConfig(projectDir: string): Promise<I18nConfig>;
1210
- declare function clearConfigCache(): void;
1211
- declare function getCachedConfig(): I18nConfig | null;
1212
- //# sourceMappingURL=detector.d.ts.map
1213
-
1443
+ //#region src/core/codequality.d.ts
1444
+ interface CodeQualityIssue {
1445
+ description: string;
1446
+ check_name: string;
1447
+ fingerprint: string;
1448
+ severity: 'info' | 'minor' | 'major' | 'critical' | 'blocker';
1449
+ location: {
1450
+ path: string;
1451
+ lines: {
1452
+ begin: number;
1453
+ };
1454
+ };
1455
+ }
1456
+ /**
1457
+ * `check` findings → one issue per usage location. Uncertain findings are
1458
+ * not mapped: the widget has no "maybe" state, and a hard-looking finding
1459
+ * for an unverifiable key would train consumers to ignore the report.
1460
+ */
1214
1461
  //#endregion
1215
- //#region src/config/layer-graph.d.ts
1462
+ //#region src/surface/types.d.ts
1463
+ /** Which surface is invoking an operation. They differ only in the prose they own. */
1464
+ type Surface = 'cli' | 'mcp';
1216
1465
  /**
1217
- * Queryable view over the layer topology a resolved {@link I18nConfig}
1218
- * already carries: which locale dirs are canonical (alias-free), which
1219
- * layer owns an aliased dir, and which apps consume which layers.
1466
+ * What a parameter carries.
1220
1467
  *
1221
- * This is a pure derivation no filesystem access, no config-shape
1222
- * changes. Cross-layer tooling (duplicate detection, scope-aware orphan
1223
- * scanning) builds on these queries instead of name-matching heuristics.
1468
+ * `string[]` is a list on both surfaces: MCP passes a JSON array, the CLI a
1469
+ * comma-separated string it splits. `record` is the one nested shape on the
1470
+ * surface the key locale value map `write` takes — so the builders state
1471
+ * its inner levels once instead of every spec carrying a schema.
1472
+ */
1473
+ type ParamType = 'string' | 'boolean' | 'number' | 'string[]' | 'record';
1474
+ /** The value shape of a `record` parameter: dot-path key → locale ref → string. */
1475
+ type TranslationsRecord = Record<string, Record<string, string>>;
1476
+ interface CliParamOptions {
1477
+ /**
1478
+ * Additional spellings citty accepts for this flag. Single characters render
1479
+ * as `-d`; longer ones are the flag's previous name, kept working after the
1480
+ * descriptor took the name the MCP tool used.
1481
+ */
1482
+ alias?: string | readonly string[];
1483
+ /** Not exposed as a flag. Every use of this states why in a comment. */
1484
+ hidden?: boolean;
1485
+ }
1486
+ interface McpParamOptions {
1487
+ /** Not part of the tool's input schema. Every use of this states why in a comment. */
1488
+ hidden?: boolean;
1489
+ }
1490
+ /** One parameter of an operation, as both surfaces expose it. */
1491
+ interface ParamSpec {
1492
+ type: ParamType;
1493
+ /**
1494
+ * The one description. It reaches `--help`, the generated CLI page and the
1495
+ * JSON Schema an MCP host hands its model, so it is written for a reader who
1496
+ * knows neither surface: no `--flag` spellings, no "this tool".
1497
+ */
1498
+ description: string;
1499
+ required?: boolean;
1500
+ /**
1501
+ * Applied by the CLI so `--help` can state it. Deliberately not put into the
1502
+ * JSON Schema: the operations already apply their own defaults, and a schema
1503
+ * default would make a host send a value the caller never chose.
1504
+ */
1505
+ default?: unknown;
1506
+ /** The accepted values. The CLI validates against them and MCP emits an enum. */
1507
+ enum?: readonly string[];
1508
+ /** Numbers only: reject a fractional value. */
1509
+ integer?: boolean;
1510
+ /** Numbers only: reject anything below this. */
1511
+ min?: number;
1512
+ /** `string[]` parameters that also accept the literal "all". */
1513
+ allowAll?: boolean;
1514
+ cli?: CliParamOptions;
1515
+ mcp?: McpParamOptions;
1516
+ }
1517
+ type Params = Record<string, ParamSpec>;
1518
+ /**
1519
+ * A CI gate a command evaluates. `counter` is the field of `result.summary`
1520
+ * carrying the observed value.
1224
1521
  *
1225
- * ## Degenerate-case semantics
1522
+ * The two shapes are a union rather than one type with optional halves so the
1523
+ * invariants hold at compile time: a flagged gate always has a flag to read
1524
+ * its name and threshold from, and a flagless one always carries both itself.
1525
+ * Stated as options, `{ counter, threshold }` type-checks and then has no name
1526
+ * to report the gate under.
1527
+ */
1528
+ type GateSpec = FlaggedGateSpec | AlwaysOnGateSpec;
1529
+ interface GateSpecBase {
1530
+ counter: string;
1531
+ /** 'above' trips when observed > threshold (default); 'below' when observed < threshold. */
1532
+ direction?: 'above' | 'below';
1533
+ }
1534
+ /**
1535
+ * Requested by a flag, and evaluated only when that flag is passed. Omitting
1536
+ * `threshold` takes it from the flag's own value, so a boolean flag pairs with
1537
+ * `threshold: 0` and a numeric one (`--failUnder 90`) omits it.
1538
+ */
1539
+ interface FlaggedGateSpec extends GateSpecBase {
1540
+ flag: string;
1541
+ name?: never;
1542
+ threshold?: number;
1543
+ }
1544
+ /**
1545
+ * Always evaluated, for findings that are a defect rather than a threshold — a
1546
+ * key that renders raw in production is not something you opt into caring
1547
+ * about. It still reports as a gate: the run succeeded, and what it found is
1548
+ * what you are being told about.
1549
+ */
1550
+ interface AlwaysOnGateSpec extends GateSpecBase {
1551
+ flag?: never;
1552
+ name: string;
1553
+ threshold: number;
1554
+ }
1555
+ /**
1556
+ * What the surface hands a report builder beyond the result: the project the
1557
+ * operation ran against and the arguments it ran with.
1558
+ */
1559
+ interface ReportContext<A = Record<string, unknown>> {
1560
+ projectDir: string;
1561
+ /** The project's resolved i18n configuration, as the operation itself read it. */
1562
+ config: I18nConfig;
1563
+ /** The operation's arguments, as the surface resolved them. */
1564
+ args: A;
1565
+ }
1566
+ /**
1567
+ * How a result too large to hand back is diverted to a file.
1226
1568
  *
1227
- * These are load-bearing for consumers (scope-aware scanning must never
1228
- * wrongly narrow its scan scope):
1569
+ * An operation always returns its whole result. Whether that result is written
1570
+ * to disk and replaced by a compact stand-in is the surface's decision — it is
1571
+ * the surface that owns `outputFile`, the configured report directory and the
1572
+ * name the file is written under — so the operations know nothing about any of
1573
+ * it, and a descriptor declaring this is what gives an operation the parameters
1574
+ * that request it.
1575
+ */
1576
+ interface ReportSpec<R = unknown, A = Record<string, unknown>> {
1577
+ /**
1578
+ * The file's base name under the configured report directory, and the tool
1579
+ * name recorded inside the report. A function where one operation answers
1580
+ * different questions and writes each under its own name — pipelines archive
1581
+ * these paths, so they are part of the contract.
1582
+ */
1583
+ name: string | ((args: A) => string);
1584
+ /** The compact stand-in returned once the full result is on disk. */
1585
+ summary: (result: R) => unknown;
1586
+ /**
1587
+ * The `outputFile` parameter as this operation offers it. Declared here and
1588
+ * nowhere else, so an operation cannot advertise the parameter without the
1589
+ * plumbing behind it, nor grow the plumbing without the parameter.
1590
+ */
1591
+ outputFile: {
1592
+ /** The example path its description carries. */
1593
+ example: string;
1594
+ cli?: CliParamOptions;
1595
+ mcp?: McpParamOptions;
1596
+ };
1597
+ /** The same findings, as a GitLab Code Quality report the pipeline collects. */
1598
+ codequality?: {
1599
+ /** What the findings are called in the parameter's description. */
1600
+ findings: string;
1601
+ /**
1602
+ * Returning undefined writes nothing, which is not the same as writing an
1603
+ * empty array: the empty array is the baseline the merge-request widget
1604
+ * diffs against, so it is only right for a run that looked for these
1605
+ * findings and found none.
1606
+ */
1607
+ issues: (result: R, ctx: ReportContext<A>) => CodeQualityIssue[] | undefined;
1608
+ };
1609
+ }
1610
+ /**
1611
+ * A report spec with its result type erased, as the runner sees it.
1229
1612
  *
1230
- * - **No app info** (`config.apps` empty or absent, e.g. hand-built
1231
- * configs): consumption edges are unknowable. `appsUsingLayer` and
1232
- * `layersOfApp` return `[]`, and `sharedLayers` conservatively contains
1233
- * *every* canonical layer — with no ownership information, every
1234
- * layer's keys must be treated as globally visible.
1235
- * - **Single-app config** (generic/Laravel/Vue/React adapters, or a Nuxt
1236
- * project with one app): the strict definition applies, so
1237
- * `sharedLayers` is empty (no layer is consumed by more than one app)
1238
- * and `appsUsingLayer` returns that one app for the layers it consumes.
1239
- * Per-layer scope then equals the whole project, which is correct.
1240
- * - **Canonical layer consumed by no app** (in a multi-app config):
1241
- * `appsUsingLayer` returns `[]` and the layer is not in `sharedLayers`.
1242
- * Callers should treat such layers conservatively (global scope).
1613
+ * `any` rather than `unknown`: the runner holds a result whose type it cannot
1614
+ * know, while every declaration site is checked against its own operation's
1615
+ * result type.
1243
1616
  */
1244
- interface LayerGraph {
1617
+ type AnyReportSpec = ReportSpec<any>;
1618
+ /** How the MCP server advertises an operation as a tool. */
1619
+ interface McpToolSpec {
1620
+ name: string;
1621
+ /** The display title a host shows. */
1622
+ title: string;
1623
+ /** Behaviour hints a host reads. Only the translating tools declare any. */
1624
+ annotations?: {
1625
+ title?: string;
1626
+ readOnlyHint?: boolean;
1627
+ };
1628
+ }
1629
+ /** How the CLI exposes an operation as a command. */
1630
+ interface CliCommandSpec {
1631
+ name: string;
1632
+ }
1633
+ /**
1634
+ * What a surface hands an operation beyond its parameters.
1635
+ *
1636
+ * `surface` exists because the guidance an agent-mode result carries is the
1637
+ * caller's, not the operation's: a terminal is told to pass `--provider`, a
1638
+ * host is told to translate the fallback contexts inline. Keeping both strings
1639
+ * in one place is the point — they used to sit in two files and say different
1640
+ * things about the same state.
1641
+ */
1642
+ interface OperationContext {
1643
+ surface: Surface;
1644
+ /** Provider-backed translation, when the surface resolved one. */
1645
+ translateFn?: TranslateFn;
1245
1646
  /**
1246
- * Alias-free locale dirs, in `config.localeDirs` order. Aliased entries
1247
- * (e.g. `app-outlook` pointing at `app-shop`'s dir) are excluded.
1647
+ * Progress reporting, when the caller asked for it. Only MCP does — a
1648
+ * terminal is left with its exit code. The operations that take long enough
1649
+ * to report call it (translating, orphan scans); the rest never do.
1248
1650
  */
1249
- canonicalLayers: LocaleDir[];
1651
+ progressFn?: ProgressFn;
1250
1652
  /**
1251
- * Resolve a layer name to the canonical layer that owns its locale dir.
1252
- * Follows chained `aliasOf` links (an alias may point at a layer that
1253
- * was itself demoted to an alias). Identity for canonical names and for
1254
- * names unknown to `localeDirs` (e.g. layers without locale dirs).
1653
+ * The number of steps the operation is about to report, counted in whatever
1654
+ * unit it reports in. Called once, before the first `progressFn` call, so a
1655
+ * notification never goes out against an unknown total.
1255
1656
  */
1256
- ownerOf: (layer: string) => string;
1657
+ onProgressTotal?: (total: number) => void;
1658
+ }
1659
+ /**
1660
+ * The value of one parameter as the operation receives it: already split,
1661
+ * parsed and validated by whichever surface took it.
1662
+ */
1663
+ type ParamValue<S extends ParamSpec> = S extends {
1664
+ type: 'boolean';
1665
+ } ? boolean : S extends {
1666
+ type: 'number';
1667
+ } ? number : S extends {
1668
+ type: 'record';
1669
+ } ? TranslationsRecord : S extends {
1670
+ type: 'string[]';
1671
+ allowAll: true;
1672
+ } ? string[] | 'all' : S extends {
1673
+ type: 'string[]';
1674
+ } ? string[] : S extends {
1675
+ type: 'string';
1676
+ enum: readonly (infer E extends string)[];
1677
+ } ? E : S extends {
1678
+ type: 'string';
1679
+ } ? string : never;
1680
+ type RequiredParamNames<P extends Params> = { [K in keyof P]-?: P[K] extends {
1681
+ required: true;
1682
+ } ? K : never }[keyof P];
1683
+ /**
1684
+ * The arguments an operation's `run` receives. Required parameters are present,
1685
+ * the rest are optional, and `projectDir` is always available because both
1686
+ * surfaces resolve it themselves (the CLI from `--projectDir`, the server from
1687
+ * I18N_PROJECT_DIR) rather than making every operation declare it.
1688
+ */
1689
+ type OperationArgs<P extends Params = Params> = { [K in RequiredParamNames<P> & keyof P]: ParamValue<P[K]> } & { [K in Exclude<keyof P, RequiredParamNames<P>>]?: ParamValue<P[K]> } & {
1690
+ projectDir?: string;
1691
+ };
1692
+ /** One operation, as both surfaces read it. */
1693
+ interface OperationDescriptor<P extends Params = Params> {
1694
+ /** Stable identifier, independent of what either surface calls it. */
1695
+ id: string;
1696
+ /** Null for an operation the CLI does not expose. */
1697
+ cli: CliCommandSpec | null;
1698
+ /** Null for an operation the MCP server does not advertise. */
1699
+ mcp: McpToolSpec | null;
1700
+ /** One sentence, on both surfaces. */
1701
+ description: string;
1257
1702
  /**
1258
- * Names of apps whose consumed layers include the given layer. The
1259
- * queried name and each app's layer list are alias-resolved via
1260
- * {@link ownerOf} first, so querying an alias name yields the owner's
1261
- * consumers. Returns `[]` when no app info exists.
1703
+ * Further prose for a model deciding whether to call the tool: when to reach
1704
+ * for it, what the result carries, what it will not do. MCP only — a `--help`
1705
+ * line has to stay a line, and the generated CLI page says the same things in
1706
+ * its flag table.
1262
1707
  */
1263
- appsUsingLayer: (layer: string) => string[];
1708
+ longDescription?: string;
1709
+ params: P;
1710
+ /** CI gates the CLI evaluates. Exit codes are a CLI notion, so MCP ignores these. */
1711
+ gates?: GateSpec[];
1264
1712
  /**
1265
- * Canonical layers consumed by more than one app e.g. a shared root
1266
- * layer in a multi-app monorepo, identified purely from consumption
1267
- * edges (no name matching). When no app info exists, contains every
1268
- * canonical layer (see degenerate-case semantics above).
1713
+ * The operation translates, so the surface has to hand it a backend: the CLI
1714
+ * resolves one from the provider flags, the server from its startup
1715
+ * environment. Without one the operation returns contexts to translate by
1716
+ * hand rather than failing.
1269
1717
  */
1270
- sharedLayers: LocaleDir[];
1718
+ usesTranslateFn?: boolean;
1271
1719
  /**
1272
- * Canonical layers the given app consumes (alias entries in the app's
1273
- * layer list resolve to their owners; layers without locale dirs are
1274
- * omitted). Returns `[]` for unknown apps or when no app info exists.
1720
+ * The result is large enough to be worth writing to a file instead of
1721
+ * returning. Declaring this is what adds the parameters that request it and
1722
+ * what makes the configured report directory apply to the operation.
1723
+ *
1724
+ * The result type stays open: a `run` whose parameters are contextually typed
1725
+ * is not an inference site, so nothing here can see the operation's own
1726
+ * result type. Declaration sites annotate it on the builder instead, which is
1727
+ * what type-checks them.
1275
1728
  */
1276
- layersOfApp: (app: string) => LocaleDir[];
1729
+ report?: ReportSpec<any, OperationArgs<P>>;
1730
+ /**
1731
+ * Declared as a method so the table can hold descriptors with different
1732
+ * parameter maps: method parameters are compared bivariantly, which is what
1733
+ * lets `run` be typed against each operation's own arguments and still be
1734
+ * callable through the erased element type below.
1735
+ */
1736
+ run(args: OperationArgs<P>, ctx: OperationContext): Promise<unknown>;
1277
1737
  }
1278
1738
  /**
1279
- * Build a {@link LayerGraph} from a resolved config's `localeDirs`
1280
- * (with their `aliasOf` markers) and `apps` (app consumed-layers edges).
1739
+ * A descriptor as the registrars see it, with `run` erased to plain arguments.
1740
+ * They build those arguments from a schema at runtime and cannot know the
1741
+ * per-operation type, so the erasure happens once, in `defineOperation`,
1742
+ * instead of at every call site.
1281
1743
  */
1282
- declare function buildLayerGraph(config: I18nConfig): LayerGraph;
1744
+ type AnyOperationDescriptor = Omit<OperationDescriptor<Params>, 'run' | 'params' | 'report'> & {
1745
+ params: Params;
1746
+ report?: AnyReportSpec;
1747
+ run(args: Record<string, unknown>, ctx: OperationContext): Promise<unknown>;
1748
+ };
1283
1749
  /**
1284
- * The graph as plain data, for surfaces that can only carry JSON.
1750
+ * Declare one operation. The `const` type parameter is what keeps `required:
1751
+ * true` and `enum: [...]` literal, so `run` receives `layer: string` rather
1752
+ * than `string | undefined` for a parameter the surface guarantees.
1285
1753
  *
1286
- * {@link LayerGraph} is function-valued, so it cannot be serialised directly.
1287
- * This answers the question an agent actually has — *which layer does this key
1288
- * belong in* — which the flat layer list `discover` returned could not: a key
1289
- * used by more than one app belongs in a layer those apps share, and `shared`
1290
- * names those layers outright (#342).
1754
+ * An operation that declares a `report` gains the parameters that request one
1755
+ * here, so the two cannot drift apart.
1756
+ */
1757
+ //#endregion
1758
+ //#region src/surface/descriptors.d.ts
1759
+ /**
1760
+ * Registry order: it is the order `the-i18n-cli --help` lists commands in and
1761
+ * the order both generated reference overviews are written in.
1762
+ */
1763
+ declare const descriptors: readonly AnyOperationDescriptor[];
1764
+ /** The descriptors a surface exposes, in registry order. */
1765
+ declare function descriptorsFor(surface: 'cli' | 'mcp'): AnyOperationDescriptor[];
1766
+ /** The parameter names a surface exposes for one operation, in declaration order. */
1767
+ declare function visibleParams(descriptor: AnyOperationDescriptor, surface: 'cli' | 'mcp'): string[];
1768
+ //# sourceMappingURL=descriptors.d.ts.map
1769
+
1770
+ //#endregion
1771
+ //#region src/surface/report.d.ts
1772
+ /**
1773
+ * Write the result where the caller asked for it, and hand back the compact
1774
+ * stand-in — or hand back the result untouched when nothing asked for a file.
1291
1775
  *
1292
- * The degenerate cases documented on {@link LayerGraph} survive the flattening,
1293
- * because they are what keeps a consumer from wrongly narrowing scope:
1294
- * a config with no app info reports *every* canonical layer as shared, and a
1295
- * layer no app consumes appears in `consumers` with an empty array rather than
1296
- * being left out. Absent and "none" must not look alike here.
1776
+ * Called with the arguments the operation ran with, which is what lets an
1777
+ * operation answering several questions write each under its own report name.
1297
1778
  */
1298
- interface SerializedLayerGraph {
1299
- /** Alias-free layer names, in `config.localeDirs` order. */
1300
- canonical: string[];
1301
- /** Canonical layers consumed by more than one app — where shared keys belong. */
1302
- shared: string[];
1303
- /** Alias layer name the canonical layer whose locale dir it points at. */
1304
- aliases: Record<string, string>;
1305
- /** Canonical layer name → the apps consuming it. Every canonical layer is a key. */
1306
- consumers: Record<string, string[]>;
1307
- }
1308
- /** Flatten {@link buildLayerGraph}'s view of `config` into plain JSON. */
1309
- declare function serializeLayerGraph(config: I18nConfig): SerializedLayerGraph;
1310
- //# sourceMappingURL=layer-graph.d.ts.map
1779
+ declare function divertToReport(result: unknown, descriptor: AnyOperationDescriptor, args: Record<string, unknown>): Promise<unknown>;
1780
+ //# sourceMappingURL=report.d.ts.map
1781
+ //#endregion
1782
+ //#region src/config/cache.d.ts
1783
+ /** The most recently resolved config, or null when nothing has been resolved. */
1784
+ declare function getCachedConfig(): I18nConfig | null;
1785
+ declare function clearConfigCache(): void;
1786
+ //# sourceMappingURL=cache.d.ts.map
1311
1787
  //#endregion
1312
- //#region src/scanner/frontends/oxc.d.ts
1313
- declare function createOxcFrontend(): LanguageFrontend;
1314
- //# sourceMappingURL=oxc.d.ts.map
1788
+ //#region src/config/detector.d.ts
1789
+ declare function detectI18nConfig(projectDir: string): Promise<I18nConfig>;
1790
+ //# sourceMappingURL=detector.d.ts.map
1315
1791
 
1316
1792
  //#endregion
1317
1793
  //#region src/scanner/frontends/patterns.d.ts
1318
1794
  /**
1319
- * The regex path as a language frontend (#332).
1795
+ * Pattern matching as a language frontend, reached only for a file a syntax
1796
+ * frontend declined.
1320
1797
  *
1321
1798
  * Regexes frame text and report call sites; what a site means is decided once,
1322
1799
  * in the rules, the same as for every other frontend. Binding is always
@@ -1339,24 +1816,6 @@ declare function createPhpFrontend(): LanguageFrontend;
1339
1816
  * a key's fate must not depend on which file type referenced it (#332).
1340
1817
  */
1341
1818
 
1342
- //#endregion
1343
- //#region src/scanner/frontends/php/blade.d.ts
1344
- /**
1345
- * Blade, by lifting (#404, #332).
1346
- *
1347
- * No maintained Blade AST parser exists, and none is needed: every construct
1348
- * that can carry a translation key wraps a PHP expression. The lexical pass
1349
- * here finds those wrappers — echoes, `@lang`/`@choice`, `@php` blocks, raw
1350
- * PHP tags — and hands the expression inside to the same parser and the same
1351
- * site collection plain PHP uses. The regex frames text; it never decides
1352
- * what a key is.
1353
- *
1354
- * A lifted chunk the parser cannot read declines the whole file to the
1355
- * pattern fallback: partially-read templates would silently drop keys.
1356
- */
1357
- declare function createBladeFrontend(): LanguageFrontend;
1358
- //# sourceMappingURL=blade.d.ts.map
1359
-
1360
1819
  //#endregion
1361
1820
  //#region src/io/locale-data.d.ts
1362
1821
  declare function readLocaleData(config: I18nConfig, layer: string, locale: LocaleDefinition): Promise<Record<string, unknown>>;
@@ -1400,7 +1859,7 @@ declare class TranslateProviderError extends Error {
1400
1859
  constructor(message: string, kind: TranslateProviderErrorKind, status?: number);
1401
1860
  }
1402
1861
  /**
1403
- * Classify an SDK error into a TranslateProviderError:
1862
+ * Classify a provider error into a TranslateProviderError:
1404
1863
  * 401/403 → auth, 429 → rate-limit, anything else → provider.
1405
1864
  * Already-classified errors pass through unchanged.
1406
1865
  */
@@ -1432,8 +1891,9 @@ declare function resolveProviderBaseUrl(sources: {
1432
1891
  config?: string;
1433
1892
  }): string | undefined;
1434
1893
  /**
1435
- * Create a TranslateFn from an LLM provider config.
1436
- * Throws if the provider SDK is not installed or API key is missing.
1894
+ * Create a TranslateFn from an LLM provider config. Every provider is called
1895
+ * over plain HTTP, so nothing beyond the CLI has to be installed.
1896
+ * Throws if the API key is missing.
1437
1897
  */
1438
1898
  declare function createTranslateFn(config: LlmProviderConfig): Promise<TranslateFn>;
1439
1899
  //# sourceMappingURL=providers.d.ts.map
@@ -1455,5 +1915,5 @@ declare function loadProjectConfig(projectDir: string): Promise<ProjectConfig |
1455
1915
  //# sourceMappingURL=project-config.d.ts.map
1456
1916
 
1457
1917
  //#endregion
1458
- export { AddTranslationsResult, BASE_URL_ENV, type CallArgument, type CallSite, type CheckUndefinedKeysResult, type CheckUndefinedKeysSummary, CodeUsageRef, CodeUsageResult, type DuplicateKeyCollision, DynamicKeyRef, EmptyTranslationsResult, type FileEvidence, type FindDuplicateKeysResult, type FindDuplicateKeysSummary, FindOrphanKeysResult, GeneratedProjectConfig, type I18nConfig, type I18nKitConfig, InitProjectConfigResult, type KeyUsageLocation, LARAVEL_PATTERNS, type LanguageFrontend, type LayerGraph, LayerStatus, type LlmProvider, type LlmProviderConfig, type LocaleDefinition, type LocaleDir, LocaleDirInfo, type LocaleRefAmbiguity, LocaleRefInfo, LocaleStatus, MisplacedUsageRef, MissingTranslationsResult, MoveTranslationKeyPlanEntry, MoveTranslationKeyResult, MutationPreview, MutationResult, PlaceholderValidationIssue, PlaceholderValidationResult, ProgressFn, type ProjectConfig, RemoveOrphanKeysResult, RemoveTranslationsPreview, RemoveTranslationsResult, RenameTranslationKeyPreview, RenameTranslationKeyResult, ScaffoldLocaleFileInfo, ScaffoldLocaleResult, ScanCodeUsageResult, type ScanResult, SearchMatch, SearchTranslationsResult, type SerializedLayerGraph, ToolError, TranslateAllLayersResult, TranslateAllLayersSummary, TranslateFailReason, TranslateFn, TranslateKeyLocaleIssue, TranslateKeyResult, TranslateLayerTotals, TranslateMissingCompactEntry, TranslateMissingLocaleResult, TranslateMissingOutcome, TranslateMissingResult, TranslateMode, TranslateProviderError, type TranslateProviderErrorKind, TranslateRequest, TranslateResponse, TranslateSkipReason, TranslationStatusResult, TranslationStatusSummary, type UncertainKeyFinding, type UndefinedKeyFinding, UnresolvedKeyWarningRef, UnresolvedLocaleRef, UpdateTranslationsResult, VUE_NUXT_PATTERNS, WriteTranslationsResult, addTranslations, buildLayerGraph, checkUndefinedKeys, classifyProviderError, clearConfigCache, createBladeFrontend, createOxcFrontend, createPatternsFrontend, createPhpFrontend, createTranslateFn, defineI18nKitConfig, detectConfig, detectI18nConfig, findDuplicateKeys, findEmptyTranslations, findLocaleImpl, findOrphanKeys, getCachedConfig, getMissingTranslations, getPatternSet, getTranslationStatus, getTranslations, listLocaleDirs, listNamespaces, loadProjectConfig, moveTranslationKey, readLocaleData, removeOrphanKeys, removeTranslations, renameTranslationKey, resolveProtectedLocales, resolveProviderBaseUrl, scaffoldLocaleFiles, scanCodeUsage, scanSourceFiles, searchTranslations, serializeLayerGraph, toErrorMessage, translateKey, translateMissing, updateTranslations, writeTranslations };
1459
- //# sourceMappingURL=index-w-EQZEZF.d.ts.map
1918
+ export { type AnyOperationDescriptor, type AnyReportSpec, BASE_URL_ENV, type CheckUndefinedKeysResult, type CheckUndefinedKeysSummary, CodeUsageRef, CodeUsageResult, DeclaredNamespaceRef, DescribeProjectResult, type DuplicateKeyCollision, DynamicKeyRef, EmptyTranslationsResult, type FindDuplicateKeysResult, type FindDuplicateKeysSummary, FindOrphanKeysResult, GeneratedProjectConfig, type I18nConfig, type I18nKitConfig, InitProjectConfigResult, type KeyUsageLocation, LARAVEL_PATTERNS, type LanguageFrontend, type LayerGraph, LayerStatus, type LlmProvider, type LlmProviderConfig, type LocaleDefinition, type LocaleDir, LocaleDirInfo, type LocaleRefAmbiguity, LocaleRefInfo, LocaleStatus, MisplacedUsageRef, MissingTranslationsResult, MoveTranslationKeyOutcome, MoveTranslationKeyPlanEntry, MoveTranslationKeyResult, MutationPreview, MutationResult, type OperationContext, type OperationDescriptor, type ParamSpec, type ParamType, type Params, PlaceholderValidationIssue, PlaceholderValidationResult, ProgressFn, type ProjectConfig, RemoveOrphanKeysResult, RemoveTranslationsPreview, RemoveTranslationsResult, RenameTranslationKeyPreview, RenameTranslationKeyResult, type ReportContext, type ReportSpec, ScaffoldLocaleFileInfo, ScaffoldLocaleResult, ScanCodeUsageResult, type ScanResult, SearchKeyMatch, SearchMatch, SearchMatchMode, SearchTranslationsResult, type SerializedLayerGraph, type Surface, ToolError, TranslateAllLayersResult, TranslateAllLayersSummary, TranslateFailReason, TranslateFn, TranslateKeyLocaleIssue, TranslateKeyResult, TranslateKeySkip, TranslateLayerTotals, TranslateMissingCompactEntry, TranslateMissingLocaleResult, TranslateMissingOptions, TranslateMissingOutcome, TranslateMissingResult, TranslateMode, TranslateProviderError, type TranslateProviderErrorKind, TranslateRequest, TranslateResponse, TranslateSkipReason, TranslationStatusResult, TranslationStatusSummary, type TranslationsRecord, type UncertainKeyFinding, type UndefinedKeyFinding, UnresolvedKeyWarningRef, UnresolvedLocaleRef, WriteTranslationsResult, buildLayerGraph, checkUndefinedKeys, classifyProviderError, clearConfigCache, createPatternsFrontend, createPhpFrontend, createTranslateFn, defineI18nKitConfig, describeProject, descriptors, descriptorsFor, detectConfig, detectI18nConfig, divertToReport, findDuplicateKeys, findEmptyTranslations, findLocaleImpl, findOrphanKeys, getCachedConfig, getMissingTranslations, getTranslationStatus, getTranslations, listLocaleDirs, listNamespaces, loadProjectConfig, moveTranslationKey, readLocaleData, removeOrphanKeys, removeTranslations, renameTranslationKey, resolveProtectedLocales, resolveProviderBaseUrl, scaffoldLocaleFiles, scanCodeUsage, scanSourceFiles, searchTranslations, serializeLayerGraph, toErrorMessage, translateKey, translateMissing, visibleParams, writeTranslations };
1919
+ //# sourceMappingURL=index-CWFWYHf5.d.ts.map