i18next-cli 1.54.2 → 1.55.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.
package/dist/cjs/cli.js CHANGED
@@ -11,6 +11,7 @@ var heuristicConfig = require('./heuristic-config.js');
11
11
  var extractor = require('./extractor/core/extractor.js');
12
12
  require('./extractor/parsers/jsx-parser.js');
13
13
  require('node:path');
14
+ var nestedObject = require('./utils/nested-object.js');
14
15
  require('node:fs/promises');
15
16
  require('jiti');
16
17
  require('@croct/json5-parser');
@@ -31,7 +32,7 @@ const program = new commander.Command();
31
32
  program
32
33
  .name('i18next-cli')
33
34
  .description('A unified, high-performance i18next CLI.')
34
- .version('1.54.2'); // This string is replaced with the actual version at build time by rollup
35
+ .version('1.55.0'); // This string is replaced with the actual version at build time by rollup
35
36
  // new: global config override option
36
37
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
37
38
  program
@@ -51,7 +52,7 @@ program
51
52
  const runExtract = async () => {
52
53
  // --sync-all implies sync-primary behavior
53
54
  const syncPrimary = !!options.syncPrimary || !!options.syncAll;
54
- const { anyFileUpdated, hasErrors } = await extractor.runExtractor(config$1, {
55
+ const { anyFileUpdated, hasErrors, results } = await extractor.runExtractor(config$1, {
55
56
  isWatchMode: !!options.watch,
56
57
  isDryRun: !!options.dryRun,
57
58
  syncPrimaryWithDefaults: syncPrimary,
@@ -65,6 +66,7 @@ program
65
66
  }
66
67
  else if (options.ci && anyFileUpdated) {
67
68
  console.error('❌ Some files were updated. This should not happen in CI mode.');
69
+ printCiDiff(results, config$1);
68
70
  process.exit(1);
69
71
  }
70
72
  if (hasErrors && !options.watch) {
@@ -328,5 +330,67 @@ const expandGlobs = async (patterns = []) => {
328
330
  const sets = await Promise.all(arr.map(p => glob.glob(p || '', { nodir: true })));
329
331
  return Array.from(new Set(sets.flat()));
330
332
  };
333
+ function printCiDiff(results, config) {
334
+ const rawSep = config.extract.keySeparator;
335
+ const keySeparator = rawSep === false ? false : (rawSep ?? '.');
336
+ for (const result of results) {
337
+ if (!result.updated)
338
+ continue;
339
+ const existing = result.existingTranslations || {};
340
+ const next = result.newTranslations || {};
341
+ const oldKeys = new Set(nestedObject.getNestedKeys(existing, keySeparator));
342
+ const newKeys = new Set(nestedObject.getNestedKeys(next, keySeparator));
343
+ const added = [];
344
+ const removed = [];
345
+ const changed = [];
346
+ for (const k of newKeys) {
347
+ if (!oldKeys.has(k)) {
348
+ added.push(k);
349
+ }
350
+ else {
351
+ const oldVal = nestedObject.getNestedValue(existing, k, keySeparator);
352
+ const newVal = nestedObject.getNestedValue(next, k, keySeparator);
353
+ if (oldVal !== newVal)
354
+ changed.push(k);
355
+ }
356
+ }
357
+ for (const k of oldKeys) {
358
+ if (!newKeys.has(k))
359
+ removed.push(k);
360
+ }
361
+ const nsLabel = result.namespace
362
+ ? ` [${result.locale}/${result.namespace}]`
363
+ : ` [${result.locale}]`;
364
+ console.error(`\n ${result.path}${nsLabel}`);
365
+ if (added.length === 0 && removed.length === 0 && changed.length === 0) {
366
+ console.error(' (no key differences — only formatting or ordering changes)');
367
+ continue;
368
+ }
369
+ added.sort();
370
+ removed.sort();
371
+ changed.sort();
372
+ for (const k of added) {
373
+ const v = nestedObject.getNestedValue(next, k, keySeparator);
374
+ console.error(node_util.styleText('green', ` + ${k}: ${formatCiDiffValue(v)}`));
375
+ }
376
+ for (const k of removed) {
377
+ const v = nestedObject.getNestedValue(existing, k, keySeparator);
378
+ console.error(node_util.styleText('red', ` - ${k}: ${formatCiDiffValue(v)}`));
379
+ }
380
+ for (const k of changed) {
381
+ const oldV = nestedObject.getNestedValue(existing, k, keySeparator);
382
+ const newV = nestedObject.getNestedValue(next, k, keySeparator);
383
+ console.error(node_util.styleText('yellow', ` ~ ${k}: ${formatCiDiffValue(oldV)} → ${formatCiDiffValue(newV)}`));
384
+ }
385
+ }
386
+ }
387
+ function formatCiDiffValue(value) {
388
+ try {
389
+ return JSON.stringify(value);
390
+ }
391
+ catch {
392
+ return String(value);
393
+ }
394
+ }
331
395
 
332
396
  exports.program = program;
@@ -96,6 +96,16 @@ class ASTVisitors {
96
96
  this.scopeManager.handleVariableDeclarator(node);
97
97
  this.expressionResolver.captureVariableDeclarator(node);
98
98
  break;
99
+ case 'TSEnumDeclaration':
100
+ case 'TsEnumDeclaration':
101
+ case 'TsEnumDecl':
102
+ // Enums → ExpressionResolver.sharedEnumTable. Needed in pre-scan so
103
+ // that function bodies referencing enum members (e.g. `return
104
+ // OrganizationType.ROUTING`) can be resolved by the body-inference
105
+ // branch of captureFunctionDeclaration when we hit the function later
106
+ // in the same file.
107
+ this.expressionResolver.captureEnumDeclaration(node);
108
+ break;
99
109
  case 'TsTypeAliasDeclaration':
100
110
  case 'TSTypeAliasDeclaration':
101
111
  case 'TsTypeAliasDecl':
@@ -104,7 +114,7 @@ class ASTVisitors {
104
114
  break;
105
115
  case 'FunctionDeclaration':
106
116
  case 'FnDecl':
107
- // Return-type annotations for t(fn()) patterns
117
+ // Return-type annotations or inferred return values for t(fn()) patterns
108
118
  this.expressionResolver.captureFunctionDeclaration(node);
109
119
  break;
110
120
  }
@@ -99,7 +99,7 @@ async function runExtractor(config, options = {}) {
99
99
  // always show the funnel regardless of cooldown.
100
100
  if (anyFileUpdated && !options.isDryRun && !options.quiet)
101
101
  await printLocizeFunnel(options.logger, anyNewFile);
102
- return { anyFileUpdated, hasErrors: fileErrors.length > 0 };
102
+ return { anyFileUpdated, hasErrors: fileErrors.length > 0, results };
103
103
  }
104
104
  catch (error) {
105
105
  spinner.fail(node_util.styleText('red', 'Extraction failed.'));
@@ -18,6 +18,11 @@ class ExpressionResolver {
18
18
  // Shared (cross-file) table for type aliases — populated alongside typeAliasTable.
19
19
  // Persists across resetFileSymbols() so exported type aliases are visible to importers.
20
20
  sharedTypeAliasTable = new Map();
21
+ // Shared (cross-file) table for function return-value sets. Populated from
22
+ // both explicit return-type annotations and body-inferred return values so
23
+ // that `t(fn())` / `const x = fn(); t(\`...${x}...\`)` work across files.
24
+ // Persists across resetFileSymbols() just like the other shared tables.
25
+ sharedFunctionReturnTable = new Map();
21
26
  // Temporary per-scope variable overrides, used to inject .map() / .forEach()
22
27
  // callback parameters while the callback body is being walked.
23
28
  temporaryVariables = new Map();
@@ -168,15 +173,23 @@ class ExpressionResolver {
168
173
  return;
169
174
  }
170
175
  // pattern 3 (arrow function variant):
171
- // `const fn = (): 'a' | 'b' => ...` — capture the explicit return type annotation.
176
+ // `const fn = (): 'a' | 'b' => ...` — capture the explicit return type annotation,
177
+ // OR fall back to walking the body's return expressions / expression body
178
+ // when no annotation is present (mirrors TS's own return-type inference).
172
179
  if (unwrappedInit.type === 'ArrowFunctionExpression' || unwrappedInit.type === 'FunctionExpression') {
180
+ let returnVals = [];
173
181
  const rawReturnType = unwrappedInit.returnType ?? unwrappedInit.typeAnnotation;
174
182
  if (rawReturnType) {
183
+ // Explicit annotation — trust it even when it resolves to [].
175
184
  const tsType = rawReturnType.typeAnnotation ?? rawReturnType;
176
- const returnVals = this.resolvePossibleStringValuesFromType(tsType);
177
- if (returnVals.length > 0) {
178
- this.variableTable.set(name, returnVals);
179
- }
185
+ returnVals = this.resolvePossibleStringValuesFromType(tsType);
186
+ }
187
+ else {
188
+ returnVals = this.inferReturnValuesFromFunctionBody(unwrappedInit);
189
+ }
190
+ if (returnVals.length > 0) {
191
+ this.variableTable.set(name, returnVals);
192
+ this.sharedFunctionReturnTable.set(name, returnVals);
180
193
  }
181
194
  }
182
195
  }
@@ -231,19 +244,85 @@ class ExpressionResolver {
231
244
  // or directly in `.returnType` (FunctionExpression / ArrowFunctionExpression).
232
245
  const fn = node.function ?? node;
233
246
  const rawReturnType = fn.returnType ?? fn.typeAnnotation;
234
- if (!rawReturnType)
235
- return;
236
- // Unwrap TsTypeAnnotation wrapper if present
237
- const tsType = rawReturnType.typeAnnotation ?? rawReturnType;
238
- const vals = this.resolvePossibleStringValuesFromType(tsType);
247
+ let vals = [];
248
+ if (rawReturnType) {
249
+ // Unwrap TsTypeAnnotation wrapper if present. Explicit annotations are
250
+ // authoritative: if the author declared the return type we trust it,
251
+ // even when it resolves to [] (e.g. plain `string`). Falling back to
252
+ // body inference in that case would invent keys the author deliberately
253
+ // opted out of.
254
+ const tsType = rawReturnType.typeAnnotation ?? rawReturnType;
255
+ vals = this.resolvePossibleStringValuesFromType(tsType);
256
+ }
257
+ else {
258
+ // No annotation — infer from body. Mirrors TS's own return-type
259
+ // inference for functions like:
260
+ // function getCurrentAppType() {
261
+ // if (...) return OrganizationType.ROUTING;
262
+ // if (...) return OrganizationType.CONTRACTOR;
263
+ // }
264
+ vals = this.inferReturnValuesFromFunctionBody(fn);
265
+ }
239
266
  if (vals.length > 0) {
240
267
  this.variableTable.set(name, vals);
268
+ this.sharedFunctionReturnTable.set(name, vals);
241
269
  }
242
270
  }
243
271
  catch {
244
272
  // noop
245
273
  }
246
274
  }
275
+ /**
276
+ * Walk a function body's ReturnStatements and union the statically-resolvable
277
+ * string values of their argument expressions. Does NOT descend into nested
278
+ * function declarations (their returns belong to the inner function, not us).
279
+ *
280
+ * This is how we mirror TypeScript's implicit return-type inference for the
281
+ * purpose of extracting translation keys — we don't need exhaustiveness, just
282
+ * the set of string values any return statement could produce.
283
+ */
284
+ inferReturnValuesFromFunctionBody(fn) {
285
+ const body = fn?.body;
286
+ if (!body)
287
+ return [];
288
+ const collected = [];
289
+ const visit = (n) => {
290
+ if (!n || typeof n !== 'object')
291
+ return;
292
+ // Don't descend into nested function bodies — their returns aren't ours.
293
+ if (n !== body && (n.type === 'FunctionDeclaration' ||
294
+ n.type === 'FunctionExpression' ||
295
+ n.type === 'ArrowFunctionExpression'))
296
+ return;
297
+ if (n.type === 'ReturnStatement' && n.argument) {
298
+ const vals = this.resolvePossibleStringValuesFromExpression(n.argument);
299
+ if (vals.length > 0)
300
+ collected.push(...vals);
301
+ }
302
+ for (const key of Object.keys(n)) {
303
+ const child = n[key];
304
+ if (Array.isArray(child)) {
305
+ for (const item of child) {
306
+ if (item && typeof item === 'object')
307
+ visit(item);
308
+ }
309
+ }
310
+ else if (child && typeof child === 'object' && typeof child.type === 'string') {
311
+ visit(child);
312
+ }
313
+ }
314
+ };
315
+ // Arrow functions with an expression body (no BlockStatement) — `() => expr` —
316
+ // have their return expression directly as `body`.
317
+ if (body.type !== 'BlockStatement') {
318
+ const vals = this.resolvePossibleStringValuesFromExpression(body);
319
+ if (vals.length > 0)
320
+ return Array.from(new Set(vals));
321
+ return [];
322
+ }
323
+ visit(body);
324
+ return Array.from(new Set(collected));
325
+ }
247
326
  /**
248
327
  * Extract a raw TsType node from an identifier's type annotation.
249
328
  * SWC may wrap it in a `TsTypeAnnotation` node — this unwraps it.
@@ -503,7 +582,10 @@ class ExpressionResolver {
503
582
  catch { }
504
583
  }
505
584
  // pattern 3:
506
- // `t(fn())` — resolve to the function's known return-type union when captured.
585
+ // `t(fn())` — resolve to the function's known return-value set (either
586
+ // from an explicit annotation or inferred from the function body). Check
587
+ // the per-file variable table first (same-file capture) and fall back to
588
+ // the shared cross-file table populated during pre-scan.
507
589
  if (expression.type === 'CallExpression') {
508
590
  try {
509
591
  const callee = expression.callee;
@@ -511,6 +593,9 @@ class ExpressionResolver {
511
593
  const v = this.variableTable.get(callee.value);
512
594
  if (Array.isArray(v) && v.length > 0)
513
595
  return v;
596
+ const sv = this.sharedFunctionReturnTable.get(callee.value);
597
+ if (sv && sv.length > 0)
598
+ return sv;
514
599
  }
515
600
  }
516
601
  catch { }
package/dist/esm/cli.js CHANGED
@@ -9,6 +9,7 @@ import { detectConfig } from './heuristic-config.js';
9
9
  import { runExtractor } from './extractor/core/extractor.js';
10
10
  import './extractor/parsers/jsx-parser.js';
11
11
  import 'node:path';
12
+ import { getNestedKeys, getNestedValue } from './utils/nested-object.js';
12
13
  import 'node:fs/promises';
13
14
  import 'jiti';
14
15
  import '@croct/json5-parser';
@@ -29,7 +30,7 @@ const program = new Command();
29
30
  program
30
31
  .name('i18next-cli')
31
32
  .description('A unified, high-performance i18next CLI.')
32
- .version('1.54.2'); // This string is replaced with the actual version at build time by rollup
33
+ .version('1.55.0'); // This string is replaced with the actual version at build time by rollup
33
34
  // new: global config override option
34
35
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
35
36
  program
@@ -49,7 +50,7 @@ program
49
50
  const runExtract = async () => {
50
51
  // --sync-all implies sync-primary behavior
51
52
  const syncPrimary = !!options.syncPrimary || !!options.syncAll;
52
- const { anyFileUpdated, hasErrors } = await runExtractor(config, {
53
+ const { anyFileUpdated, hasErrors, results } = await runExtractor(config, {
53
54
  isWatchMode: !!options.watch,
54
55
  isDryRun: !!options.dryRun,
55
56
  syncPrimaryWithDefaults: syncPrimary,
@@ -63,6 +64,7 @@ program
63
64
  }
64
65
  else if (options.ci && anyFileUpdated) {
65
66
  console.error('❌ Some files were updated. This should not happen in CI mode.');
67
+ printCiDiff(results, config);
66
68
  process.exit(1);
67
69
  }
68
70
  if (hasErrors && !options.watch) {
@@ -326,5 +328,67 @@ const expandGlobs = async (patterns = []) => {
326
328
  const sets = await Promise.all(arr.map(p => glob(p || '', { nodir: true })));
327
329
  return Array.from(new Set(sets.flat()));
328
330
  };
331
+ function printCiDiff(results, config) {
332
+ const rawSep = config.extract.keySeparator;
333
+ const keySeparator = rawSep === false ? false : (rawSep ?? '.');
334
+ for (const result of results) {
335
+ if (!result.updated)
336
+ continue;
337
+ const existing = result.existingTranslations || {};
338
+ const next = result.newTranslations || {};
339
+ const oldKeys = new Set(getNestedKeys(existing, keySeparator));
340
+ const newKeys = new Set(getNestedKeys(next, keySeparator));
341
+ const added = [];
342
+ const removed = [];
343
+ const changed = [];
344
+ for (const k of newKeys) {
345
+ if (!oldKeys.has(k)) {
346
+ added.push(k);
347
+ }
348
+ else {
349
+ const oldVal = getNestedValue(existing, k, keySeparator);
350
+ const newVal = getNestedValue(next, k, keySeparator);
351
+ if (oldVal !== newVal)
352
+ changed.push(k);
353
+ }
354
+ }
355
+ for (const k of oldKeys) {
356
+ if (!newKeys.has(k))
357
+ removed.push(k);
358
+ }
359
+ const nsLabel = result.namespace
360
+ ? ` [${result.locale}/${result.namespace}]`
361
+ : ` [${result.locale}]`;
362
+ console.error(`\n ${result.path}${nsLabel}`);
363
+ if (added.length === 0 && removed.length === 0 && changed.length === 0) {
364
+ console.error(' (no key differences — only formatting or ordering changes)');
365
+ continue;
366
+ }
367
+ added.sort();
368
+ removed.sort();
369
+ changed.sort();
370
+ for (const k of added) {
371
+ const v = getNestedValue(next, k, keySeparator);
372
+ console.error(styleText('green', ` + ${k}: ${formatCiDiffValue(v)}`));
373
+ }
374
+ for (const k of removed) {
375
+ const v = getNestedValue(existing, k, keySeparator);
376
+ console.error(styleText('red', ` - ${k}: ${formatCiDiffValue(v)}`));
377
+ }
378
+ for (const k of changed) {
379
+ const oldV = getNestedValue(existing, k, keySeparator);
380
+ const newV = getNestedValue(next, k, keySeparator);
381
+ console.error(styleText('yellow', ` ~ ${k}: ${formatCiDiffValue(oldV)} → ${formatCiDiffValue(newV)}`));
382
+ }
383
+ }
384
+ }
385
+ function formatCiDiffValue(value) {
386
+ try {
387
+ return JSON.stringify(value);
388
+ }
389
+ catch {
390
+ return String(value);
391
+ }
392
+ }
329
393
 
330
394
  export { program };
@@ -94,6 +94,16 @@ class ASTVisitors {
94
94
  this.scopeManager.handleVariableDeclarator(node);
95
95
  this.expressionResolver.captureVariableDeclarator(node);
96
96
  break;
97
+ case 'TSEnumDeclaration':
98
+ case 'TsEnumDeclaration':
99
+ case 'TsEnumDecl':
100
+ // Enums → ExpressionResolver.sharedEnumTable. Needed in pre-scan so
101
+ // that function bodies referencing enum members (e.g. `return
102
+ // OrganizationType.ROUTING`) can be resolved by the body-inference
103
+ // branch of captureFunctionDeclaration when we hit the function later
104
+ // in the same file.
105
+ this.expressionResolver.captureEnumDeclaration(node);
106
+ break;
97
107
  case 'TsTypeAliasDeclaration':
98
108
  case 'TSTypeAliasDeclaration':
99
109
  case 'TsTypeAliasDecl':
@@ -102,7 +112,7 @@ class ASTVisitors {
102
112
  break;
103
113
  case 'FunctionDeclaration':
104
114
  case 'FnDecl':
105
- // Return-type annotations for t(fn()) patterns
115
+ // Return-type annotations or inferred return values for t(fn()) patterns
106
116
  this.expressionResolver.captureFunctionDeclaration(node);
107
117
  break;
108
118
  }
@@ -97,7 +97,7 @@ async function runExtractor(config, options = {}) {
97
97
  // always show the funnel regardless of cooldown.
98
98
  if (anyFileUpdated && !options.isDryRun && !options.quiet)
99
99
  await printLocizeFunnel(options.logger, anyNewFile);
100
- return { anyFileUpdated, hasErrors: fileErrors.length > 0 };
100
+ return { anyFileUpdated, hasErrors: fileErrors.length > 0, results };
101
101
  }
102
102
  catch (error) {
103
103
  spinner.fail(styleText('red', 'Extraction failed.'));
@@ -16,6 +16,11 @@ class ExpressionResolver {
16
16
  // Shared (cross-file) table for type aliases — populated alongside typeAliasTable.
17
17
  // Persists across resetFileSymbols() so exported type aliases are visible to importers.
18
18
  sharedTypeAliasTable = new Map();
19
+ // Shared (cross-file) table for function return-value sets. Populated from
20
+ // both explicit return-type annotations and body-inferred return values so
21
+ // that `t(fn())` / `const x = fn(); t(\`...${x}...\`)` work across files.
22
+ // Persists across resetFileSymbols() just like the other shared tables.
23
+ sharedFunctionReturnTable = new Map();
19
24
  // Temporary per-scope variable overrides, used to inject .map() / .forEach()
20
25
  // callback parameters while the callback body is being walked.
21
26
  temporaryVariables = new Map();
@@ -166,15 +171,23 @@ class ExpressionResolver {
166
171
  return;
167
172
  }
168
173
  // pattern 3 (arrow function variant):
169
- // `const fn = (): 'a' | 'b' => ...` — capture the explicit return type annotation.
174
+ // `const fn = (): 'a' | 'b' => ...` — capture the explicit return type annotation,
175
+ // OR fall back to walking the body's return expressions / expression body
176
+ // when no annotation is present (mirrors TS's own return-type inference).
170
177
  if (unwrappedInit.type === 'ArrowFunctionExpression' || unwrappedInit.type === 'FunctionExpression') {
178
+ let returnVals = [];
171
179
  const rawReturnType = unwrappedInit.returnType ?? unwrappedInit.typeAnnotation;
172
180
  if (rawReturnType) {
181
+ // Explicit annotation — trust it even when it resolves to [].
173
182
  const tsType = rawReturnType.typeAnnotation ?? rawReturnType;
174
- const returnVals = this.resolvePossibleStringValuesFromType(tsType);
175
- if (returnVals.length > 0) {
176
- this.variableTable.set(name, returnVals);
177
- }
183
+ returnVals = this.resolvePossibleStringValuesFromType(tsType);
184
+ }
185
+ else {
186
+ returnVals = this.inferReturnValuesFromFunctionBody(unwrappedInit);
187
+ }
188
+ if (returnVals.length > 0) {
189
+ this.variableTable.set(name, returnVals);
190
+ this.sharedFunctionReturnTable.set(name, returnVals);
178
191
  }
179
192
  }
180
193
  }
@@ -229,19 +242,85 @@ class ExpressionResolver {
229
242
  // or directly in `.returnType` (FunctionExpression / ArrowFunctionExpression).
230
243
  const fn = node.function ?? node;
231
244
  const rawReturnType = fn.returnType ?? fn.typeAnnotation;
232
- if (!rawReturnType)
233
- return;
234
- // Unwrap TsTypeAnnotation wrapper if present
235
- const tsType = rawReturnType.typeAnnotation ?? rawReturnType;
236
- const vals = this.resolvePossibleStringValuesFromType(tsType);
245
+ let vals = [];
246
+ if (rawReturnType) {
247
+ // Unwrap TsTypeAnnotation wrapper if present. Explicit annotations are
248
+ // authoritative: if the author declared the return type we trust it,
249
+ // even when it resolves to [] (e.g. plain `string`). Falling back to
250
+ // body inference in that case would invent keys the author deliberately
251
+ // opted out of.
252
+ const tsType = rawReturnType.typeAnnotation ?? rawReturnType;
253
+ vals = this.resolvePossibleStringValuesFromType(tsType);
254
+ }
255
+ else {
256
+ // No annotation — infer from body. Mirrors TS's own return-type
257
+ // inference for functions like:
258
+ // function getCurrentAppType() {
259
+ // if (...) return OrganizationType.ROUTING;
260
+ // if (...) return OrganizationType.CONTRACTOR;
261
+ // }
262
+ vals = this.inferReturnValuesFromFunctionBody(fn);
263
+ }
237
264
  if (vals.length > 0) {
238
265
  this.variableTable.set(name, vals);
266
+ this.sharedFunctionReturnTable.set(name, vals);
239
267
  }
240
268
  }
241
269
  catch {
242
270
  // noop
243
271
  }
244
272
  }
273
+ /**
274
+ * Walk a function body's ReturnStatements and union the statically-resolvable
275
+ * string values of their argument expressions. Does NOT descend into nested
276
+ * function declarations (their returns belong to the inner function, not us).
277
+ *
278
+ * This is how we mirror TypeScript's implicit return-type inference for the
279
+ * purpose of extracting translation keys — we don't need exhaustiveness, just
280
+ * the set of string values any return statement could produce.
281
+ */
282
+ inferReturnValuesFromFunctionBody(fn) {
283
+ const body = fn?.body;
284
+ if (!body)
285
+ return [];
286
+ const collected = [];
287
+ const visit = (n) => {
288
+ if (!n || typeof n !== 'object')
289
+ return;
290
+ // Don't descend into nested function bodies — their returns aren't ours.
291
+ if (n !== body && (n.type === 'FunctionDeclaration' ||
292
+ n.type === 'FunctionExpression' ||
293
+ n.type === 'ArrowFunctionExpression'))
294
+ return;
295
+ if (n.type === 'ReturnStatement' && n.argument) {
296
+ const vals = this.resolvePossibleStringValuesFromExpression(n.argument);
297
+ if (vals.length > 0)
298
+ collected.push(...vals);
299
+ }
300
+ for (const key of Object.keys(n)) {
301
+ const child = n[key];
302
+ if (Array.isArray(child)) {
303
+ for (const item of child) {
304
+ if (item && typeof item === 'object')
305
+ visit(item);
306
+ }
307
+ }
308
+ else if (child && typeof child === 'object' && typeof child.type === 'string') {
309
+ visit(child);
310
+ }
311
+ }
312
+ };
313
+ // Arrow functions with an expression body (no BlockStatement) — `() => expr` —
314
+ // have their return expression directly as `body`.
315
+ if (body.type !== 'BlockStatement') {
316
+ const vals = this.resolvePossibleStringValuesFromExpression(body);
317
+ if (vals.length > 0)
318
+ return Array.from(new Set(vals));
319
+ return [];
320
+ }
321
+ visit(body);
322
+ return Array.from(new Set(collected));
323
+ }
245
324
  /**
246
325
  * Extract a raw TsType node from an identifier's type annotation.
247
326
  * SWC may wrap it in a `TsTypeAnnotation` node — this unwraps it.
@@ -501,7 +580,10 @@ class ExpressionResolver {
501
580
  catch { }
502
581
  }
503
582
  // pattern 3:
504
- // `t(fn())` — resolve to the function's known return-type union when captured.
583
+ // `t(fn())` — resolve to the function's known return-value set (either
584
+ // from an explicit annotation or inferred from the function body). Check
585
+ // the per-file variable table first (same-file capture) and fall back to
586
+ // the shared cross-file table populated during pre-scan.
505
587
  if (expression.type === 'CallExpression') {
506
588
  try {
507
589
  const callee = expression.callee;
@@ -509,6 +591,9 @@ class ExpressionResolver {
509
591
  const v = this.variableTable.get(callee.value);
510
592
  if (Array.isArray(v) && v.length > 0)
511
593
  return v;
594
+ const sv = this.sharedFunctionReturnTable.get(callee.value);
595
+ if (sv && sv.length > 0)
596
+ return sv;
512
597
  }
513
598
  }
514
599
  catch { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.54.2",
3
+ "version": "1.55.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAmBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AA6U7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAoBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AAiZ7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAItE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,SAAgB,YAAY,EAAE,YAAY,CAAA;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAiCzC;;;;;;;;;;;;;OAaG;IACI,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAoXZ;;;;;;;;OAQG;IACH,OAAO,CAAC,gCAAgC;IAqDxC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5D;;OAEG;IACI,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxD;;;;;;OAMG;IACI,cAAc,IAAK,MAAM;IAIhC;;OAEG;IACI,cAAc,IAAK,MAAM;CAGjC"}
1
+ {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAItE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,SAAgB,YAAY,EAAE,YAAY,CAAA;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAiCzC;;;;;;;;;;;;;OAaG;IACI,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA8CzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAoXZ;;;;;;;;OAQG;IACH,OAAO,CAAC,gCAAgC;IAqDxC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5D;;OAEG;IACI,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxD;;;;;;OAMG;IACI,cAAc,IAAK,MAAM;IAIhC;;OAEG;IACI,cAAc,IAAK,MAAM;CAGjC"}
@@ -37,6 +37,7 @@ export declare function runExtractor(config: I18nextToolkitConfig, options?: {
37
37
  }): Promise<{
38
38
  anyFileUpdated: boolean;
39
39
  hasErrors: boolean;
40
+ results: TranslationResult[];
40
41
  }>;
41
42
  /**
42
43
  * Processes an individual source file for translation key extraction.
@@ -1 +1 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAO5G,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAK/C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CA8E1D;AAwBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,EACpC,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAoJf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,EACpC,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CA6Df;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAO1K"}
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAO5G,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAK/C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC,CA8ExF;AAwBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,EACpC,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAoJf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,EACpC,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CA6Df;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAO1K"}
@@ -7,6 +7,7 @@ export declare class ExpressionResolver {
7
7
  private typeAliasTable;
8
8
  private sharedVariableTable;
9
9
  private sharedTypeAliasTable;
10
+ private sharedFunctionReturnTable;
10
11
  private temporaryVariables;
11
12
  constructor(hooks: ASTVisitorHooks);
12
13
  /**
@@ -44,6 +45,16 @@ export declare class ExpressionResolver {
44
45
  * SWC node shapes: `FunctionDeclaration` / `FnDecl`
45
46
  */
46
47
  captureFunctionDeclaration(node: any): void;
48
+ /**
49
+ * Walk a function body's ReturnStatements and union the statically-resolvable
50
+ * string values of their argument expressions. Does NOT descend into nested
51
+ * function declarations (their returns belong to the inner function, not us).
52
+ *
53
+ * This is how we mirror TypeScript's implicit return-type inference for the
54
+ * purpose of extracting translation keys — we don't need exhaustiveness, just
55
+ * the set of string values any return statement could produce.
56
+ */
57
+ private inferReturnValuesFromFunctionBody;
47
58
  /**
48
59
  * Extract a raw TsType node from an identifier's type annotation.
49
60
  * SWC may wrap it in a `TsTypeAnnotation` node — this unwraps it.
@@ -1 +1 @@
1
- {"version":3,"file":"expression-resolver.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/expression-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAkD,MAAM,WAAW,CAAA;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAiB;IAK9B,OAAO,CAAC,aAAa,CAA4D;IAGjF,OAAO,CAAC,eAAe,CAAiD;IAIxE,OAAO,CAAC,cAAc,CAAmC;IAIzD,OAAO,CAAC,mBAAmB,CAAmC;IAI9D,OAAO,CAAC,oBAAoB,CAAmC;IAI/D,OAAO,CAAC,kBAAkB,CAAmC;gBAEhD,KAAK,EAAE,eAAe;IAInC;;OAEG;IACI,gBAAgB,IAAK,IAAI;IAMhC;;;;;;;;;OASG;IACH,yBAAyB,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAoJ3C;;;;;;;OAOG;IACH,2BAA2B,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAkB7C;;;;;;;;;OASG;IACH,0BAA0B,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAoB5C;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;;;OAIG;IACI,oBAAoB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IAIlE;;OAEG;IACI,uBAAuB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAInD;;;;OAIG;IACI,yBAAyB,CAAE,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE;IAQxD;;;OAGG;IACI,iBAAiB,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAQ7D;;;;OAIG;IACI,YAAY,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;IAQtE;;;;;OAKG;IACH,sBAAsB,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAwBxC;;;;;;;OAOG;IACH,kCAAkC,CAAE,UAAU,EAAE,UAAU,GAAG,MAAM,EAAE;IAKrE;;;;;;;OAOG;IACH,8BAA8B,CAAE,UAAU,EAAE,UAAU,GAAG,MAAM,EAAE;IAKjE;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,yCAAyC;IAsNjD,OAAO,CAAC,mCAAmC;IAiH3C;;;;;;OAMG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,kDAAkD;CAwB3D"}
1
+ {"version":3,"file":"expression-resolver.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/expression-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAkD,MAAM,WAAW,CAAA;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAiB;IAK9B,OAAO,CAAC,aAAa,CAA4D;IAGjF,OAAO,CAAC,eAAe,CAAiD;IAIxE,OAAO,CAAC,cAAc,CAAmC;IAIzD,OAAO,CAAC,mBAAmB,CAAmC;IAI9D,OAAO,CAAC,oBAAoB,CAAmC;IAM/D,OAAO,CAAC,yBAAyB,CAAmC;IAIpE,OAAO,CAAC,kBAAkB,CAAmC;gBAEhD,KAAK,EAAE,eAAe;IAInC;;OAEG;IACI,gBAAgB,IAAK,IAAI;IAMhC;;;;;;;;;OASG;IACH,yBAAyB,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IA2J3C;;;;;;;OAOG;IACH,2BAA2B,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAkB7C;;;;;;;;;OASG;IACH,0BAA0B,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAqC5C;;;;;;;;OAQG;IACH,OAAO,CAAC,iCAAiC;IA6CzC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;;;OAIG;IACI,oBAAoB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IAIlE;;OAEG;IACI,uBAAuB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAInD;;;;OAIG;IACI,yBAAyB,CAAE,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE;IAQxD;;;OAGG;IACI,iBAAiB,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAQ7D;;;;OAIG;IACI,YAAY,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;IAQtE;;;;;OAKG;IACH,sBAAsB,CAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAwBxC;;;;;;;OAOG;IACH,kCAAkC,CAAE,UAAU,EAAE,UAAU,GAAG,MAAM,EAAE;IAKrE;;;;;;;OAOG;IACH,8BAA8B,CAAE,UAAU,EAAE,UAAU,GAAG,MAAM,EAAE;IAKjE;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,yCAAyC;IA2NjD,OAAO,CAAC,mCAAmC;IAiH3C;;;;;;OAMG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,kDAAkD;CAwB3D"}