i18next-cli 1.56.6 → 1.56.8

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
@@ -32,7 +32,7 @@ const program = new commander.Command();
32
32
  program
33
33
  .name('i18next-cli')
34
34
  .description('A unified, high-performance i18next CLI.')
35
- .version('1.56.6'); // This string is replaced with the actual version at build time by rollup
35
+ .version('1.56.8'); // This string is replaced with the actual version at build time by rollup
36
36
  // new: global config override option
37
37
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
38
38
  program
@@ -749,11 +749,23 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
749
749
  }
750
750
  }
751
751
  else {
752
+ // A key is a synthesized plural variant only when the extractor flagged it
753
+ // with `hasCount` AND its suffix matches a CLDR plural form. Relying purely
754
+ // on the presence of the separator misclassifies regular keys that happen to
755
+ // contain `_` (e.g. `abc_123`) — see issue #250. Context variants are caught
756
+ // by `isDerivedDefault` below (their synthesized default mirrors the base key).
757
+ const isVariantKey = (() => {
758
+ if (!hasCount)
759
+ return false;
760
+ const parts = key.split(pluralSeparator);
761
+ if (parts.length < 2)
762
+ return false;
763
+ return pluralForms.includes(parts[parts.length - 1]);
764
+ })();
752
765
  // Existing value exists - decide whether to preserve, sync primary, or clear other locales when requested
753
766
  if (locale === primaryLanguage && syncPrimaryWithDefaults) {
754
- // If this key looks like a plural/context variant and the default
755
- // wasn't explicitly provided in source code, preserve the existing value.
756
- const isVariantKey = key.includes(pluralSeparator) || key.includes(contextSeparator);
767
+ // If this key is a plural/context variant and the default wasn't explicitly
768
+ // provided in source code, preserve the existing value.
757
769
  if (isVariantKey && !explicitDefault) {
758
770
  valueToSet = existingValue;
759
771
  }
@@ -766,7 +778,6 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
766
778
  }
767
779
  else {
768
780
  // Non-primary locale behavior
769
- const isVariantKey = key.includes(pluralSeparator) || key.includes(contextSeparator);
770
781
  // A plural variant whose category exists in the current locale but not in the
771
782
  // primary language (e.g. French `_many` vs English `one`/`other`) will always be
772
783
  // absent from the primary file by CLDR design. Treat that absence as expected —
@@ -68,21 +68,6 @@ class CallExpressionHandler {
68
68
  return;
69
69
  // The scope lookup will only work for simple identifiers, which is okay for this fix.
70
70
  let scopeInfo = getScopeInfo(functionName);
71
- // For member expressions like `emailError.t`, also try the object part(s) as a
72
- // fallback. This lets custom hooks that return an object with a `t` method
73
- // (e.g. `const emailError = useTranslateKeyState('auth')`) propagate their
74
- // namespace/keyPrefix to the inner `t` call.
75
- if (!scopeInfo && functionName.includes('.')) {
76
- const parts = functionName.split('.');
77
- for (let i = parts.length - 1; i > 0; i--) {
78
- const candidate = parts.slice(0, i).join('.');
79
- const candidateScope = getScopeInfo(candidate);
80
- if (candidateScope) {
81
- scopeInfo = candidateScope;
82
- break;
83
- }
84
- }
85
- }
86
71
  const configuredFunctions = this.config.extract.functions || ['t', '*.t'];
87
72
  let isFunctionToParse = scopeInfo !== undefined; // A scoped variable (from useTranslation, etc.) is always parsed.
88
73
  if (!isFunctionToParse) {
@@ -105,6 +90,23 @@ class CallExpressionHandler {
105
90
  }
106
91
  if (!isFunctionToParse || node.arguments.length === 0)
107
92
  return;
93
+ // For member expressions like `emailError.t`, also try the object part(s) as a
94
+ // fallback. This lets custom hooks that return an object with a `t` method
95
+ // (e.g. `const emailError = useTranslateKeyState('auth')`) propagate their
96
+ // namespace/keyPrefix to the inner `t` call. This runs AFTER the pattern
97
+ // check so an in-scope prefix cannot by itself promote an arbitrary method
98
+ // call (e.g. `i18n.language.substring(...)`) into a translation call.
99
+ if (!scopeInfo && functionName.includes('.')) {
100
+ const parts = functionName.split('.');
101
+ for (let i = parts.length - 1; i > 0; i--) {
102
+ const candidate = parts.slice(0, i).join('.');
103
+ const candidateScope = getScopeInfo(candidate);
104
+ if (candidateScope) {
105
+ scopeInfo = candidateScope;
106
+ break;
107
+ }
108
+ }
109
+ }
108
110
  const { keysToProcess, isSelectorAPI } = this.handleCallExpressionArgument(node, 0);
109
111
  if (keysToProcess.length === 0)
110
112
  return;
package/dist/esm/cli.js CHANGED
@@ -30,7 +30,7 @@ const program = new Command();
30
30
  program
31
31
  .name('i18next-cli')
32
32
  .description('A unified, high-performance i18next CLI.')
33
- .version('1.56.6'); // This string is replaced with the actual version at build time by rollup
33
+ .version('1.56.8'); // This string is replaced with the actual version at build time by rollup
34
34
  // new: global config override option
35
35
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
36
36
  program
@@ -747,11 +747,23 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
747
747
  }
748
748
  }
749
749
  else {
750
+ // A key is a synthesized plural variant only when the extractor flagged it
751
+ // with `hasCount` AND its suffix matches a CLDR plural form. Relying purely
752
+ // on the presence of the separator misclassifies regular keys that happen to
753
+ // contain `_` (e.g. `abc_123`) — see issue #250. Context variants are caught
754
+ // by `isDerivedDefault` below (their synthesized default mirrors the base key).
755
+ const isVariantKey = (() => {
756
+ if (!hasCount)
757
+ return false;
758
+ const parts = key.split(pluralSeparator);
759
+ if (parts.length < 2)
760
+ return false;
761
+ return pluralForms.includes(parts[parts.length - 1]);
762
+ })();
750
763
  // Existing value exists - decide whether to preserve, sync primary, or clear other locales when requested
751
764
  if (locale === primaryLanguage && syncPrimaryWithDefaults) {
752
- // If this key looks like a plural/context variant and the default
753
- // wasn't explicitly provided in source code, preserve the existing value.
754
- const isVariantKey = key.includes(pluralSeparator) || key.includes(contextSeparator);
765
+ // If this key is a plural/context variant and the default wasn't explicitly
766
+ // provided in source code, preserve the existing value.
755
767
  if (isVariantKey && !explicitDefault) {
756
768
  valueToSet = existingValue;
757
769
  }
@@ -764,7 +776,6 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
764
776
  }
765
777
  else {
766
778
  // Non-primary locale behavior
767
- const isVariantKey = key.includes(pluralSeparator) || key.includes(contextSeparator);
768
779
  // A plural variant whose category exists in the current locale but not in the
769
780
  // primary language (e.g. French `_many` vs English `one`/`other`) will always be
770
781
  // absent from the primary file by CLDR design. Treat that absence as expected —
@@ -66,21 +66,6 @@ class CallExpressionHandler {
66
66
  return;
67
67
  // The scope lookup will only work for simple identifiers, which is okay for this fix.
68
68
  let scopeInfo = getScopeInfo(functionName);
69
- // For member expressions like `emailError.t`, also try the object part(s) as a
70
- // fallback. This lets custom hooks that return an object with a `t` method
71
- // (e.g. `const emailError = useTranslateKeyState('auth')`) propagate their
72
- // namespace/keyPrefix to the inner `t` call.
73
- if (!scopeInfo && functionName.includes('.')) {
74
- const parts = functionName.split('.');
75
- for (let i = parts.length - 1; i > 0; i--) {
76
- const candidate = parts.slice(0, i).join('.');
77
- const candidateScope = getScopeInfo(candidate);
78
- if (candidateScope) {
79
- scopeInfo = candidateScope;
80
- break;
81
- }
82
- }
83
- }
84
69
  const configuredFunctions = this.config.extract.functions || ['t', '*.t'];
85
70
  let isFunctionToParse = scopeInfo !== undefined; // A scoped variable (from useTranslation, etc.) is always parsed.
86
71
  if (!isFunctionToParse) {
@@ -103,6 +88,23 @@ class CallExpressionHandler {
103
88
  }
104
89
  if (!isFunctionToParse || node.arguments.length === 0)
105
90
  return;
91
+ // For member expressions like `emailError.t`, also try the object part(s) as a
92
+ // fallback. This lets custom hooks that return an object with a `t` method
93
+ // (e.g. `const emailError = useTranslateKeyState('auth')`) propagate their
94
+ // namespace/keyPrefix to the inner `t` call. This runs AFTER the pattern
95
+ // check so an in-scope prefix cannot by itself promote an arbitrary method
96
+ // call (e.g. `i18n.language.substring(...)`) into a translation call.
97
+ if (!scopeInfo && functionName.includes('.')) {
98
+ const parts = functionName.split('.');
99
+ for (let i = parts.length - 1; i > 0; i--) {
100
+ const candidate = parts.slice(0, i).join('.');
101
+ const candidateScope = getScopeInfo(candidate);
102
+ if (candidateScope) {
103
+ scopeInfo = candidateScope;
104
+ break;
105
+ }
106
+ }
107
+ }
106
108
  const { keysToProcess, isSelectorAPI } = this.handleCallExpressionArgument(node, 0);
107
109
  if (keysToProcess.length === 0)
108
110
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.56.6",
3
+ "version": "1.56.8",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -85,7 +85,7 @@
85
85
  "minimatch": "^10.2.5",
86
86
  "ora": "^9.3.0",
87
87
  "react": "^19.2.5",
88
- "react-i18next": "^17.0.4",
88
+ "react-i18next": "^17.0.6",
89
89
  "yaml": "^2.8.3"
90
90
  }
91
91
  }
@@ -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;AA8iC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;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 +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;AAa7D,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;IA0ZxG;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;;;;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;AAa7D,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;IA4ZxG;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;;;;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"}