i18next-cli 1.64.0 → 1.64.2

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
@@ -37,7 +37,7 @@ const program = new commander.Command();
37
37
  program
38
38
  .name('i18next-cli')
39
39
  .description('A unified, high-performance i18next CLI.')
40
- .version('1.64.0'); // This string is replaced with the actual version at build time by rollup
40
+ .version('1.64.2'); // This string is replaced with the actual version at build time by rollup
41
41
  // new: global config override option
42
42
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
43
43
  program
@@ -394,8 +394,11 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
394
394
  if (shouldFilterKey(key)) {
395
395
  return false;
396
396
  }
397
- if (!hasCount) {
398
- // Non-plural keys are always included
397
+ if (!hasCount || config.extract.disablePlurals) {
398
+ // Non-plural keys are always included. Under `disablePlurals`, count keys
399
+ // are emitted as plain keys (no plural expansion) but still carry
400
+ // `hasCount` so `status` can recognise them — they must bypass the
401
+ // plural-form filtering below and always be included, exactly as before.
399
402
  return true;
400
403
  }
401
404
  // For plural keys, check if this specific plural form is needed for the target language
@@ -337,12 +337,17 @@ class CallExpressionHandler {
337
337
  // Check if plurals are disabled FIRST, before any plural optimization paths
338
338
  if (this.config.extract.disablePlurals) {
339
339
  // When plurals are disabled, treat count as a regular option (for interpolation only)
340
- // Still handle context normally
340
+ // Still handle context normally. We keep `hasCount`/`isOrdinal` on the
341
+ // emitted key so `status` knows it is count-driven and can accept the
342
+ // file's plural variants (or the bare key) instead of demanding the
343
+ // literal base key. File generation ignores these flags under
344
+ // disablePlurals (see translation-manager), so output is unchanged.
345
+ const countMeta = hasCount ? { hasCount: true, isOrdinal: isOrdinalByKey } : {};
341
346
  if (keysWithContext.length > 0) {
342
- keysWithContext.forEach(this.pluginContext.addKey);
347
+ keysWithContext.forEach(k => this.pluginContext.addKey({ ...k, ...countMeta }));
343
348
  }
344
349
  else {
345
- this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase });
350
+ this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase, ...countMeta });
346
351
  }
347
352
  continue; // This key is fully handled
348
353
  }
@@ -124,14 +124,18 @@ function extractKeysFromComments(code, pluginContext, config, scopeResolver) {
124
124
  ns = config.extract.defaultNS;
125
125
  // 5. Handle context and count combinations based on disablePlurals setting
126
126
  if (config.extract.disablePlurals) {
127
- // When plurals are disabled, ignore count for key generation
127
+ // When plurals are disabled, ignore count for key generation. We still
128
+ // tag the key with `hasCount` (when a count was present) so `status` can
129
+ // recognise it as count-driven; file generation ignores the flag under
130
+ // disablePlurals (see translation-manager).
131
+ const countMeta = count ? { hasCount: true, isOrdinal } : {};
128
132
  if (context) {
129
133
  // Only generate context variants (no base key when context is static)
130
- pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key });
134
+ pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key, ...countMeta });
131
135
  }
132
136
  else {
133
137
  // Simple key (ignore count)
134
- pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key });
138
+ pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key, ...countMeta });
135
139
  }
136
140
  }
137
141
  else {
@@ -369,12 +369,16 @@ class JSXHandler {
369
369
  else if (hasCount) {
370
370
  // Check if plurals are disabled
371
371
  if (this.config.extract.disablePlurals) {
372
- // When plurals are disabled, just add the base keys (no plural forms)
372
+ // When plurals are disabled, just add the base keys (no plural forms).
373
+ // We keep `hasCount` so `status` recognises the key as count-driven and
374
+ // accepts the file's plural variants (or bare key); file generation
375
+ // ignores it under disablePlurals (see translation-manager).
373
376
  extractedKeys.forEach(extractedKey => {
374
377
  this.pluginContext.addKey({
375
378
  key: extractedKey.key,
376
379
  ns: extractedKey.ns,
377
380
  defaultValue: extractedKey.defaultValue,
381
+ hasCount: true,
378
382
  locations: extractedKey.locations
379
383
  });
380
384
  });
@@ -26,6 +26,53 @@ function classifyValue(value) {
26
26
  return 'empty';
27
27
  return 'translated';
28
28
  }
29
+ /**
30
+ * Representative counts used to decide which CLDR plural categories a locale can
31
+ * actually reach in normal usage: small integers (the common case), a handful
32
+ * of larger integers, and common decimals (so categories that only fire for
33
+ * fractional display values — e.g. Polish/Russian `other` — stay required).
34
+ *
35
+ * Any category NOT produced by these counts is treated as "optional". The prime
36
+ * example is French `many`, which `Intl.PluralRules` only selects for values
37
+ * ≥ 1,000,000 — the i18next runtime can technically request it, but real apps
38
+ * almost never hit those values (and the runtime falls back to the base key
39
+ * when the variant is missing), so a missing `_many` should be a soft note
40
+ * rather than a hard "missing key" failure.
41
+ */
42
+ const REPRESENTATIVE_COUNTS = (() => {
43
+ const counts = [];
44
+ for (let n = 0; n <= 20; n++)
45
+ counts.push(n);
46
+ counts.push(100, 101, 1000, 1001, 10000);
47
+ counts.push(0.5, 1.1, 1.5, 2.5, 3.5);
48
+ return counts;
49
+ })();
50
+ const optionalCategoriesCache = new Map();
51
+ /**
52
+ * Returns the CLDR plural categories for a locale that are NOT reachable by any
53
+ * representative count (see {@link REPRESENTATIVE_COUNTS}). These are reported
54
+ * by `status` as optional: a missing variant is a soft note instead of a hard
55
+ * absence, mirroring how the i18next runtime resolves such forms.
56
+ */
57
+ function getOptionalPluralCategories(locale, isOrdinal) {
58
+ const type = isOrdinal ? 'ordinal' : 'cardinal';
59
+ const cacheKey = `${locale}|${type}`;
60
+ const cached = optionalCategoriesCache.get(cacheKey);
61
+ if (cached)
62
+ return cached;
63
+ let optional;
64
+ try {
65
+ const rules = pluralRules.safePluralRules(locale, { type });
66
+ const all = rules.resolvedOptions().pluralCategories;
67
+ const reachable = new Set(REPRESENTATIVE_COUNTS.map(n => rules.select(n)));
68
+ optional = new Set(all.filter(c => !reachable.has(c)));
69
+ }
70
+ catch {
71
+ optional = new Set();
72
+ }
73
+ optionalCategoriesCache.set(cacheKey, optional);
74
+ return optional;
75
+ }
29
76
  /**
30
77
  * Runs a health check on the project's i18next translations and displays a status report.
31
78
  *
@@ -53,20 +100,44 @@ async function runStatus(config, options = {}) {
53
100
  const report = await generateStatusReport(config);
54
101
  spinner.succeed('Analysis complete.');
55
102
  await displayStatusReport(report, config, options);
103
+ // When a specific locale is requested (`status <locale>`), the pass/fail
104
+ // result must reflect THAT locale only — otherwise the displayed summary
105
+ // (which is scoped to the requested locale) can contradict the exit code
106
+ // by failing on an unrelated secondary language. See issue #271.
107
+ const scopedLocale = options.detail && config.locales.includes(options.detail)
108
+ ? options.detail
109
+ : undefined;
56
110
  let hasMissing = false;
57
- for (const [, localeData] of report.locales.entries()) {
58
- if (localeData.totalTranslated < localeData.totalKeys) {
59
- hasMissing = true;
60
- break;
111
+ if (scopedLocale) {
112
+ if (scopedLocale === config.extract.primaryLanguage) {
113
+ // The primary language fails only on absent keys (used in code but
114
+ // missing from the file); empty placeholders are deliberate.
115
+ hasMissing = !!report.primary && report.primary.totalAbsent > 0;
116
+ }
117
+ else {
118
+ const localeData = report.locales.get(scopedLocale);
119
+ hasMissing = !!localeData && localeData.totalTranslated < localeData.totalKeys;
61
120
  }
62
121
  }
63
- // The primary language fails the check only on absent keys (used in code but
64
- // missing from the translation file); empty placeholders are tolerated.
65
- if (!hasMissing && report.primary && report.primary.totalAbsent > 0) {
66
- hasMissing = true;
122
+ else {
123
+ for (const [, localeData] of report.locales.entries()) {
124
+ if (localeData.totalTranslated < localeData.totalKeys) {
125
+ hasMissing = true;
126
+ break;
127
+ }
128
+ }
129
+ // The primary language fails the check only on absent keys (used in code but
130
+ // missing from the translation file); empty placeholders are tolerated.
131
+ if (!hasMissing && report.primary && report.primary.totalAbsent > 0) {
132
+ hasMissing = true;
133
+ }
67
134
  }
68
135
  if (hasMissing) {
69
- spinner.fail('Error: Incomplete translations detected.');
136
+ // Name the locale when the check is scoped so the failure reason is clear
137
+ // (the displayed summary already explains what is missing for it).
138
+ spinner.fail(scopedLocale
139
+ ? `Error: Incomplete translations detected for "${scopedLocale}".`
140
+ : 'Error: Incomplete translations detected.');
70
141
  process.exit(1);
71
142
  }
72
143
  }
@@ -222,6 +293,7 @@ async function generateStatusReport(config) {
222
293
  let totalTranslatedForLocale = 0;
223
294
  let totalEmptyForLocale = 0;
224
295
  let totalAbsentForLocale = 0;
296
+ let totalOptionalForLocale = 0;
225
297
  let totalKeysForLocale = 0;
226
298
  const namespaces = new Map();
227
299
  const mergedTranslations = mergeNamespaces
@@ -250,6 +322,7 @@ async function generateStatusReport(config) {
250
322
  let translatedInNs = 0;
251
323
  let emptyInNs = 0;
252
324
  let absentInNs = 0;
325
+ let optionalInNs = 0;
253
326
  let totalInNs = 0;
254
327
  const keyDetails = [];
255
328
  // Get the plural categories for THIS specific locale
@@ -314,37 +387,85 @@ async function generateStatusReport(config) {
314
387
  // Only count this key if it's a plural form used by this locale
315
388
  if (localePluralCategories.includes(category) && !processedKeys.has(baseKey)) {
316
389
  processedKeys.add(baseKey);
317
- totalInNs++;
318
390
  const state = resolveAndClassify(baseKey);
319
- if (state === 'translated')
320
- translatedInNs++;
321
- else if (state === 'empty')
322
- emptyInNs++;
323
- else
324
- absentInNs++;
325
- keyDetails.push({ key: baseKey, state });
391
+ const optionalCategories = getOptionalPluralCategories(locale, isOrdinalVariant);
392
+ // An optional category (e.g. French `_many`) is a soft note whenever
393
+ // it isn't translated — whether absent or an empty placeholder that
394
+ // `extract` wrote. It is never a hard failure. See #270 and
395
+ // getOptionalPluralCategories.
396
+ if (state !== 'translated' && optionalCategories.has(category)) {
397
+ optionalInNs++;
398
+ keyDetails.push({ key: baseKey, state: 'optional' });
399
+ }
400
+ else {
401
+ totalInNs++;
402
+ if (state === 'translated')
403
+ translatedInNs++;
404
+ else if (state === 'empty')
405
+ emptyInNs++;
406
+ else
407
+ absentInNs++;
408
+ keyDetails.push({ key: baseKey, state });
409
+ }
326
410
  }
327
411
  }
328
412
  else {
329
- // This is a base plural key without expanded variants
330
- // Expand it according to THIS locale's plural rules
413
+ // This is a base plural key without expanded variants. Mirror the
414
+ // i18next runtime, where t(key, { count }) resolves `key + suffix`
415
+ // and falls back to the bare `key`. A family is therefore satisfied
416
+ // either by its plural variants OR by a bare key (the convention
417
+ // used when `disablePlurals` is enabled and no variants are written).
331
418
  const localePluralCategories = getLocalePluralCategories(locale, isOrdinal || false);
332
- for (const category of localePluralCategories) {
333
- const pluralKey = isOrdinal
419
+ const optionalCategories = getOptionalPluralCategories(locale, isOrdinal || false);
420
+ const variants = localePluralCategories.map(category => ({
421
+ category,
422
+ pluralKey: isOrdinal
334
423
  ? `${baseKey}${pluralSeparator}ordinal${pluralSeparator}${category}`
335
- : `${baseKey}${pluralSeparator}${category}`;
336
- if (processedKeys.has(pluralKey))
337
- continue;
338
- processedKeys.add(pluralKey);
424
+ : `${baseKey}${pluralSeparator}${category}`,
425
+ }));
426
+ const anyVariantPresent = variants.some(({ pluralKey }) => resolveAndClassify(pluralKey) !== 'absent');
427
+ const bareState = resolveAndClassify(baseKey);
428
+ if (!anyVariantPresent && bareState !== 'absent' && !processedKeys.has(baseKey)) {
429
+ // Convention (a): only the bare key exists (typical under
430
+ // disablePlurals, or single-"other" languages). The runtime
431
+ // resolves count via the bare key, so it satisfies the family on
432
+ // its own — don't demand plural variants that were never written.
433
+ processedKeys.add(baseKey);
339
434
  totalInNs++;
340
- const state = resolveAndClassify(pluralKey);
341
- if (state === 'translated')
435
+ if (bareState === 'translated')
342
436
  translatedInNs++;
343
- else if (state === 'empty')
437
+ else if (bareState === 'empty')
344
438
  emptyInNs++;
345
439
  else
346
440
  absentInNs++;
347
- keyDetails.push({ key: pluralKey, state });
441
+ keyDetails.push({ key: baseKey, state: bareState });
442
+ }
443
+ else {
444
+ // Convention (b): plural variants exist (or the family is missing
445
+ // entirely). Evaluate each CLDR category — a missing variant is a
446
+ // hard absence only when the category is required for this locale;
447
+ // optional categories (e.g. French `_many`) downgrade to a soft note.
448
+ for (const { category, pluralKey } of variants) {
449
+ if (processedKeys.has(pluralKey))
450
+ continue;
451
+ processedKeys.add(pluralKey);
452
+ const state = resolveAndClassify(pluralKey);
453
+ // An optional category (e.g. French `_many`) is a soft note
454
+ // whenever it isn't translated — never a hard failure. See #270.
455
+ if (state !== 'translated' && optionalCategories.has(category)) {
456
+ optionalInNs++;
457
+ keyDetails.push({ key: pluralKey, state: 'optional' });
458
+ continue;
459
+ }
460
+ totalInNs++;
461
+ if (state === 'translated')
462
+ translatedInNs++;
463
+ else if (state === 'empty')
464
+ emptyInNs++;
465
+ else
466
+ absentInNs++;
467
+ keyDetails.push({ key: pluralKey, state });
468
+ }
348
469
  }
349
470
  }
350
471
  }
@@ -380,10 +501,11 @@ async function generateStatusReport(config) {
380
501
  absentInNs++;
381
502
  keyDetails.push({ key: variantKey, state });
382
503
  }
383
- namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, keyDetails });
504
+ namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, optionalKeys: optionalInNs, keyDetails });
384
505
  totalTranslatedForLocale += translatedInNs;
385
506
  totalEmptyForLocale += emptyInNs;
386
507
  totalAbsentForLocale += absentInNs;
508
+ totalOptionalForLocale += optionalInNs;
387
509
  totalKeysForLocale += totalInNs;
388
510
  }
389
511
  const localeStatus = {
@@ -391,6 +513,7 @@ async function generateStatusReport(config) {
391
513
  totalTranslated: totalTranslatedForLocale,
392
514
  totalEmpty: totalEmptyForLocale,
393
515
  totalAbsent: totalAbsentForLocale,
516
+ totalOptional: totalOptionalForLocale,
394
517
  namespaces,
395
518
  };
396
519
  if (isPrimary) {
@@ -482,6 +605,9 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
482
605
  else if (state === 'empty') {
483
606
  console.log(` ${node_util.styleText('yellow', '~')} ${key} ${node_util.styleText('yellow', '(untranslated)')}`);
484
607
  }
608
+ else if (state === 'optional') {
609
+ console.log(` ${node_util.styleText('gray', '○')} ${key} ${node_util.styleText('gray', '(optional plural form)')}`);
610
+ }
485
611
  else {
486
612
  console.log(` ${node_util.styleText('red', '✗')} ${key} ${node_util.styleText('red', '(absent)')}`);
487
613
  }
package/dist/esm/cli.js CHANGED
@@ -31,7 +31,7 @@ const program = new Command();
31
31
  program
32
32
  .name('i18next-cli')
33
33
  .description('A unified, high-performance i18next CLI.')
34
- .version('1.64.0'); // This string is replaced with the actual version at build time by rollup
34
+ .version('1.64.2'); // This string is replaced with the actual version at build time by rollup
35
35
  // new: global config override option
36
36
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
37
37
  program
@@ -392,8 +392,11 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
392
392
  if (shouldFilterKey(key)) {
393
393
  return false;
394
394
  }
395
- if (!hasCount) {
396
- // Non-plural keys are always included
395
+ if (!hasCount || config.extract.disablePlurals) {
396
+ // Non-plural keys are always included. Under `disablePlurals`, count keys
397
+ // are emitted as plain keys (no plural expansion) but still carry
398
+ // `hasCount` so `status` can recognise them — they must bypass the
399
+ // plural-form filtering below and always be included, exactly as before.
397
400
  return true;
398
401
  }
399
402
  // For plural keys, check if this specific plural form is needed for the target language
@@ -335,12 +335,17 @@ class CallExpressionHandler {
335
335
  // Check if plurals are disabled FIRST, before any plural optimization paths
336
336
  if (this.config.extract.disablePlurals) {
337
337
  // When plurals are disabled, treat count as a regular option (for interpolation only)
338
- // Still handle context normally
338
+ // Still handle context normally. We keep `hasCount`/`isOrdinal` on the
339
+ // emitted key so `status` knows it is count-driven and can accept the
340
+ // file's plural variants (or the bare key) instead of demanding the
341
+ // literal base key. File generation ignores these flags under
342
+ // disablePlurals (see translation-manager), so output is unchanged.
343
+ const countMeta = hasCount ? { hasCount: true, isOrdinal: isOrdinalByKey } : {};
339
344
  if (keysWithContext.length > 0) {
340
- keysWithContext.forEach(this.pluginContext.addKey);
345
+ keysWithContext.forEach(k => this.pluginContext.addKey({ ...k, ...countMeta }));
341
346
  }
342
347
  else {
343
- this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase });
348
+ this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase, ...countMeta });
344
349
  }
345
350
  continue; // This key is fully handled
346
351
  }
@@ -122,14 +122,18 @@ function extractKeysFromComments(code, pluginContext, config, scopeResolver) {
122
122
  ns = config.extract.defaultNS;
123
123
  // 5. Handle context and count combinations based on disablePlurals setting
124
124
  if (config.extract.disablePlurals) {
125
- // When plurals are disabled, ignore count for key generation
125
+ // When plurals are disabled, ignore count for key generation. We still
126
+ // tag the key with `hasCount` (when a count was present) so `status` can
127
+ // recognise it as count-driven; file generation ignores the flag under
128
+ // disablePlurals (see translation-manager).
129
+ const countMeta = count ? { hasCount: true, isOrdinal } : {};
126
130
  if (context) {
127
131
  // Only generate context variants (no base key when context is static)
128
- pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key });
132
+ pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key, ...countMeta });
129
133
  }
130
134
  else {
131
135
  // Simple key (ignore count)
132
- pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key });
136
+ pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key, ...countMeta });
133
137
  }
134
138
  }
135
139
  else {
@@ -367,12 +367,16 @@ class JSXHandler {
367
367
  else if (hasCount) {
368
368
  // Check if plurals are disabled
369
369
  if (this.config.extract.disablePlurals) {
370
- // When plurals are disabled, just add the base keys (no plural forms)
370
+ // When plurals are disabled, just add the base keys (no plural forms).
371
+ // We keep `hasCount` so `status` recognises the key as count-driven and
372
+ // accepts the file's plural variants (or bare key); file generation
373
+ // ignores it under disablePlurals (see translation-manager).
371
374
  extractedKeys.forEach(extractedKey => {
372
375
  this.pluginContext.addKey({
373
376
  key: extractedKey.key,
374
377
  ns: extractedKey.ns,
375
378
  defaultValue: extractedKey.defaultValue,
379
+ hasCount: true,
376
380
  locations: extractedKey.locations
377
381
  });
378
382
  });
@@ -20,6 +20,53 @@ function classifyValue(value) {
20
20
  return 'empty';
21
21
  return 'translated';
22
22
  }
23
+ /**
24
+ * Representative counts used to decide which CLDR plural categories a locale can
25
+ * actually reach in normal usage: small integers (the common case), a handful
26
+ * of larger integers, and common decimals (so categories that only fire for
27
+ * fractional display values — e.g. Polish/Russian `other` — stay required).
28
+ *
29
+ * Any category NOT produced by these counts is treated as "optional". The prime
30
+ * example is French `many`, which `Intl.PluralRules` only selects for values
31
+ * ≥ 1,000,000 — the i18next runtime can technically request it, but real apps
32
+ * almost never hit those values (and the runtime falls back to the base key
33
+ * when the variant is missing), so a missing `_many` should be a soft note
34
+ * rather than a hard "missing key" failure.
35
+ */
36
+ const REPRESENTATIVE_COUNTS = (() => {
37
+ const counts = [];
38
+ for (let n = 0; n <= 20; n++)
39
+ counts.push(n);
40
+ counts.push(100, 101, 1000, 1001, 10000);
41
+ counts.push(0.5, 1.1, 1.5, 2.5, 3.5);
42
+ return counts;
43
+ })();
44
+ const optionalCategoriesCache = new Map();
45
+ /**
46
+ * Returns the CLDR plural categories for a locale that are NOT reachable by any
47
+ * representative count (see {@link REPRESENTATIVE_COUNTS}). These are reported
48
+ * by `status` as optional: a missing variant is a soft note instead of a hard
49
+ * absence, mirroring how the i18next runtime resolves such forms.
50
+ */
51
+ function getOptionalPluralCategories(locale, isOrdinal) {
52
+ const type = isOrdinal ? 'ordinal' : 'cardinal';
53
+ const cacheKey = `${locale}|${type}`;
54
+ const cached = optionalCategoriesCache.get(cacheKey);
55
+ if (cached)
56
+ return cached;
57
+ let optional;
58
+ try {
59
+ const rules = safePluralRules(locale, { type });
60
+ const all = rules.resolvedOptions().pluralCategories;
61
+ const reachable = new Set(REPRESENTATIVE_COUNTS.map(n => rules.select(n)));
62
+ optional = new Set(all.filter(c => !reachable.has(c)));
63
+ }
64
+ catch {
65
+ optional = new Set();
66
+ }
67
+ optionalCategoriesCache.set(cacheKey, optional);
68
+ return optional;
69
+ }
23
70
  /**
24
71
  * Runs a health check on the project's i18next translations and displays a status report.
25
72
  *
@@ -47,20 +94,44 @@ async function runStatus(config, options = {}) {
47
94
  const report = await generateStatusReport(config);
48
95
  spinner.succeed('Analysis complete.');
49
96
  await displayStatusReport(report, config, options);
97
+ // When a specific locale is requested (`status <locale>`), the pass/fail
98
+ // result must reflect THAT locale only — otherwise the displayed summary
99
+ // (which is scoped to the requested locale) can contradict the exit code
100
+ // by failing on an unrelated secondary language. See issue #271.
101
+ const scopedLocale = options.detail && config.locales.includes(options.detail)
102
+ ? options.detail
103
+ : undefined;
50
104
  let hasMissing = false;
51
- for (const [, localeData] of report.locales.entries()) {
52
- if (localeData.totalTranslated < localeData.totalKeys) {
53
- hasMissing = true;
54
- break;
105
+ if (scopedLocale) {
106
+ if (scopedLocale === config.extract.primaryLanguage) {
107
+ // The primary language fails only on absent keys (used in code but
108
+ // missing from the file); empty placeholders are deliberate.
109
+ hasMissing = !!report.primary && report.primary.totalAbsent > 0;
110
+ }
111
+ else {
112
+ const localeData = report.locales.get(scopedLocale);
113
+ hasMissing = !!localeData && localeData.totalTranslated < localeData.totalKeys;
55
114
  }
56
115
  }
57
- // The primary language fails the check only on absent keys (used in code but
58
- // missing from the translation file); empty placeholders are tolerated.
59
- if (!hasMissing && report.primary && report.primary.totalAbsent > 0) {
60
- hasMissing = true;
116
+ else {
117
+ for (const [, localeData] of report.locales.entries()) {
118
+ if (localeData.totalTranslated < localeData.totalKeys) {
119
+ hasMissing = true;
120
+ break;
121
+ }
122
+ }
123
+ // The primary language fails the check only on absent keys (used in code but
124
+ // missing from the translation file); empty placeholders are tolerated.
125
+ if (!hasMissing && report.primary && report.primary.totalAbsent > 0) {
126
+ hasMissing = true;
127
+ }
61
128
  }
62
129
  if (hasMissing) {
63
- spinner.fail('Error: Incomplete translations detected.');
130
+ // Name the locale when the check is scoped so the failure reason is clear
131
+ // (the displayed summary already explains what is missing for it).
132
+ spinner.fail(scopedLocale
133
+ ? `Error: Incomplete translations detected for "${scopedLocale}".`
134
+ : 'Error: Incomplete translations detected.');
64
135
  process.exit(1);
65
136
  }
66
137
  }
@@ -216,6 +287,7 @@ async function generateStatusReport(config) {
216
287
  let totalTranslatedForLocale = 0;
217
288
  let totalEmptyForLocale = 0;
218
289
  let totalAbsentForLocale = 0;
290
+ let totalOptionalForLocale = 0;
219
291
  let totalKeysForLocale = 0;
220
292
  const namespaces = new Map();
221
293
  const mergedTranslations = mergeNamespaces
@@ -244,6 +316,7 @@ async function generateStatusReport(config) {
244
316
  let translatedInNs = 0;
245
317
  let emptyInNs = 0;
246
318
  let absentInNs = 0;
319
+ let optionalInNs = 0;
247
320
  let totalInNs = 0;
248
321
  const keyDetails = [];
249
322
  // Get the plural categories for THIS specific locale
@@ -308,37 +381,85 @@ async function generateStatusReport(config) {
308
381
  // Only count this key if it's a plural form used by this locale
309
382
  if (localePluralCategories.includes(category) && !processedKeys.has(baseKey)) {
310
383
  processedKeys.add(baseKey);
311
- totalInNs++;
312
384
  const state = resolveAndClassify(baseKey);
313
- if (state === 'translated')
314
- translatedInNs++;
315
- else if (state === 'empty')
316
- emptyInNs++;
317
- else
318
- absentInNs++;
319
- keyDetails.push({ key: baseKey, state });
385
+ const optionalCategories = getOptionalPluralCategories(locale, isOrdinalVariant);
386
+ // An optional category (e.g. French `_many`) is a soft note whenever
387
+ // it isn't translated — whether absent or an empty placeholder that
388
+ // `extract` wrote. It is never a hard failure. See #270 and
389
+ // getOptionalPluralCategories.
390
+ if (state !== 'translated' && optionalCategories.has(category)) {
391
+ optionalInNs++;
392
+ keyDetails.push({ key: baseKey, state: 'optional' });
393
+ }
394
+ else {
395
+ totalInNs++;
396
+ if (state === 'translated')
397
+ translatedInNs++;
398
+ else if (state === 'empty')
399
+ emptyInNs++;
400
+ else
401
+ absentInNs++;
402
+ keyDetails.push({ key: baseKey, state });
403
+ }
320
404
  }
321
405
  }
322
406
  else {
323
- // This is a base plural key without expanded variants
324
- // Expand it according to THIS locale's plural rules
407
+ // This is a base plural key without expanded variants. Mirror the
408
+ // i18next runtime, where t(key, { count }) resolves `key + suffix`
409
+ // and falls back to the bare `key`. A family is therefore satisfied
410
+ // either by its plural variants OR by a bare key (the convention
411
+ // used when `disablePlurals` is enabled and no variants are written).
325
412
  const localePluralCategories = getLocalePluralCategories(locale, isOrdinal || false);
326
- for (const category of localePluralCategories) {
327
- const pluralKey = isOrdinal
413
+ const optionalCategories = getOptionalPluralCategories(locale, isOrdinal || false);
414
+ const variants = localePluralCategories.map(category => ({
415
+ category,
416
+ pluralKey: isOrdinal
328
417
  ? `${baseKey}${pluralSeparator}ordinal${pluralSeparator}${category}`
329
- : `${baseKey}${pluralSeparator}${category}`;
330
- if (processedKeys.has(pluralKey))
331
- continue;
332
- processedKeys.add(pluralKey);
418
+ : `${baseKey}${pluralSeparator}${category}`,
419
+ }));
420
+ const anyVariantPresent = variants.some(({ pluralKey }) => resolveAndClassify(pluralKey) !== 'absent');
421
+ const bareState = resolveAndClassify(baseKey);
422
+ if (!anyVariantPresent && bareState !== 'absent' && !processedKeys.has(baseKey)) {
423
+ // Convention (a): only the bare key exists (typical under
424
+ // disablePlurals, or single-"other" languages). The runtime
425
+ // resolves count via the bare key, so it satisfies the family on
426
+ // its own — don't demand plural variants that were never written.
427
+ processedKeys.add(baseKey);
333
428
  totalInNs++;
334
- const state = resolveAndClassify(pluralKey);
335
- if (state === 'translated')
429
+ if (bareState === 'translated')
336
430
  translatedInNs++;
337
- else if (state === 'empty')
431
+ else if (bareState === 'empty')
338
432
  emptyInNs++;
339
433
  else
340
434
  absentInNs++;
341
- keyDetails.push({ key: pluralKey, state });
435
+ keyDetails.push({ key: baseKey, state: bareState });
436
+ }
437
+ else {
438
+ // Convention (b): plural variants exist (or the family is missing
439
+ // entirely). Evaluate each CLDR category — a missing variant is a
440
+ // hard absence only when the category is required for this locale;
441
+ // optional categories (e.g. French `_many`) downgrade to a soft note.
442
+ for (const { category, pluralKey } of variants) {
443
+ if (processedKeys.has(pluralKey))
444
+ continue;
445
+ processedKeys.add(pluralKey);
446
+ const state = resolveAndClassify(pluralKey);
447
+ // An optional category (e.g. French `_many`) is a soft note
448
+ // whenever it isn't translated — never a hard failure. See #270.
449
+ if (state !== 'translated' && optionalCategories.has(category)) {
450
+ optionalInNs++;
451
+ keyDetails.push({ key: pluralKey, state: 'optional' });
452
+ continue;
453
+ }
454
+ totalInNs++;
455
+ if (state === 'translated')
456
+ translatedInNs++;
457
+ else if (state === 'empty')
458
+ emptyInNs++;
459
+ else
460
+ absentInNs++;
461
+ keyDetails.push({ key: pluralKey, state });
462
+ }
342
463
  }
343
464
  }
344
465
  }
@@ -374,10 +495,11 @@ async function generateStatusReport(config) {
374
495
  absentInNs++;
375
496
  keyDetails.push({ key: variantKey, state });
376
497
  }
377
- namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, keyDetails });
498
+ namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, optionalKeys: optionalInNs, keyDetails });
378
499
  totalTranslatedForLocale += translatedInNs;
379
500
  totalEmptyForLocale += emptyInNs;
380
501
  totalAbsentForLocale += absentInNs;
502
+ totalOptionalForLocale += optionalInNs;
381
503
  totalKeysForLocale += totalInNs;
382
504
  }
383
505
  const localeStatus = {
@@ -385,6 +507,7 @@ async function generateStatusReport(config) {
385
507
  totalTranslated: totalTranslatedForLocale,
386
508
  totalEmpty: totalEmptyForLocale,
387
509
  totalAbsent: totalAbsentForLocale,
510
+ totalOptional: totalOptionalForLocale,
388
511
  namespaces,
389
512
  };
390
513
  if (isPrimary) {
@@ -476,6 +599,9 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
476
599
  else if (state === 'empty') {
477
600
  console.log(` ${styleText('yellow', '~')} ${key} ${styleText('yellow', '(untranslated)')}`);
478
601
  }
602
+ else if (state === 'optional') {
603
+ console.log(` ${styleText('gray', '○')} ${key} ${styleText('gray', '(optional plural form)')}`);
604
+ }
479
605
  else {
480
606
  console.log(` ${styleText('red', '✗')} ${key} ${styleText('red', '(absent)')}`);
481
607
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.64.0",
3
+ "version": "1.64.2",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +1 @@
1
- {"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAujC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EACE,uBAA+B,EAC/B,OAAe,EACf,oBAA4B,EAC5B,MAA4B,EAC7B,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiK9B"}
1
+ {"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AA0jC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EACE,uBAA+B,EAC/B,OAAe,EACf,oBAA4B,EAC5B,MAA4B,EAC7B,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiK9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAc7D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;IACrC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,iBAAiB,CAAsC;gBAG7D,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM,EAC5B,iBAAiB,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAA2B;IAW3E;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IAyZxG;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAsDpC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,sBAAsB;IA2B9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;;;;;;;OAQG;IACH,OAAO,CAAC,iCAAiC;IAwFzC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IAyMxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAc7D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;IACrC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,iBAAiB,CAAsC;gBAG7D,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM,EAC5B,iBAAiB,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAA2B;IAW3E;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IA8ZxG;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAsDpC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,sBAAsB;IA2B9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;;;;;;;OAQG;IACH,OAAO,CAAC,iCAAiC;IAwFzC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IAyMxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAOzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,CAqJN"}
1
+ {"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAOzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,CAyJN"}
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,UAAU,EAAqC,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAS7D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iCAAiC;IAgCzC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;;OAQG;IACH,gBAAgB,CAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;IA2UjI;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,0BAA0B;IAkIlC;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;CAevB"}
1
+ {"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,UAAU,EAAqC,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAS7D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iCAAiC;IAgCzC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;;OAQG;IACH,gBAAgB,CAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;IA+UjI;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,0BAA0B;IAkIlC;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;CAevB"}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAgB,MAAM,YAAY,CAAA;AAOpE;;GAEG;AACH,UAAU,aAAa;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAiED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB,EAAE,OAAO,GAAE,aAAkB,iBA4BzF"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAgB,MAAM,YAAY,CAAA;AAOpE;;GAEG;AACH,UAAU,aAAa;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAyHD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB,EAAE,OAAO,GAAE,aAAkB,iBAoDzF"}