i18next-cli 1.46.2 → 1.46.3

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
@@ -28,7 +28,7 @@ const program = new commander.Command();
28
28
  program
29
29
  .name('i18next-cli')
30
30
  .description('A unified, high-performance i18next CLI.')
31
- .version('1.46.2'); // This string is replaced with the actual version at build time by rollup
31
+ .version('1.46.3'); // This string is replaced with the actual version at build time by rollup
32
32
  // new: global config override option
33
33
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
34
34
  program
@@ -98,18 +98,13 @@ function hasEmptySegments(key, separator) {
98
98
  }
99
99
  /**
100
100
  * Detects a nesting conflict for `key` against the object being built.
101
+ * Returns the conflicting ancestor/descendant key path as a string when a
102
+ * conflict is found, or `null` when there is no conflict.
101
103
  *
102
- * Returns true when:
103
- * - A prefix of `key` already resolves to a *non-object* value in `obj`
104
- * (we cannot descend into a string to set a child).
105
- * - A parent of a segment in `key` would clobber an existing sub-tree
106
- * (the key itself resolves to an object but a new leaf is about to overwrite it
107
- * and the object already has children from a deeper extracted key).
108
- *
109
- * In both cases the caller should skip the conflicting key rather than producing
110
- * a silently-wrong flat fallback.
104
+ * The returned string lets callers produce an actionable error message
105
+ * pointing to the specific key that is already occupying the conflicting path.
111
106
  */
112
- function hasNestingConflict(obj, key, separator) {
107
+ function findNestingConflict(obj, key, separator) {
113
108
  const parts = key.split(separator);
114
109
  let current = obj;
115
110
  for (let i = 0; i < parts.length - 1; i++) {
@@ -117,12 +112,12 @@ function hasNestingConflict(obj, key, separator) {
117
112
  const value = current[part];
118
113
  if (value === undefined || value === null) {
119
114
  // Path does not exist yet — no conflict
120
- return false;
115
+ return null;
121
116
  }
122
117
  if (typeof value !== 'object' || Array.isArray(value)) {
123
118
  // A non-object value already occupies an ancestor segment.
124
119
  // We cannot nest inside a string/number/array.
125
- return true;
120
+ return parts.slice(0, i + 1).join(separator);
126
121
  }
127
122
  current = value;
128
123
  }
@@ -132,9 +127,10 @@ function hasNestingConflict(obj, key, separator) {
132
127
  const leafPart = parts[parts.length - 1];
133
128
  const leafValue = current[leafPart];
134
129
  if (typeof leafValue === 'object' && leafValue !== null && !Array.isArray(leafValue) && Object.keys(leafValue).length > 0) {
135
- return true;
130
+ // The conflicting path is the key itself (it already has nested children)
131
+ return key;
136
132
  }
137
- return false;
133
+ return null;
138
134
  }
139
135
  /**
140
136
  * Recursively sorts the keys of an object.
@@ -698,14 +694,16 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
698
694
  // that was already written by a different extracted key, e.g.:
699
695
  // t("a.b") => sets a.b = string
700
696
  // t("a.b.c") => tries to descend into a.b which is already a string
701
- // In that situation we skip the conflicting key rather than producing a
702
- // silently-wrong top-level flat fallback like { "a.b.c": "a.b.c" }.
703
- if (separator && typeof separator === 'string' && hasNestingConflict(newTranslations, key, separator)) {
704
- // Log at most once per key so the output is not spammy
705
- // (the logger reference is captured from the outer scope via closure – it is not
706
- // directly available here, but we can at least avoid crashing silently).
707
- // Callers / tests can verify the key is simply absent from the output.
708
- continue;
697
+ // In that situation we skip the conflicting key and emit a console.error so
698
+ // developers see the problem immediately a skipped key becomes a missing
699
+ // translation at runtime.
700
+ if (separator && typeof separator === 'string') {
701
+ const conflictingPath = findNestingConflict(newTranslations, key, separator);
702
+ if (conflictingPath !== null) {
703
+ console.error(`[i18next-toolkit] Nesting conflict: key "${key}" conflicts with existing key "${conflictingPath}". ` +
704
+ `"${key}" will be skipped — fix the overlapping key paths in your source code to avoid missing translations at runtime.`);
705
+ continue;
706
+ }
709
707
  }
710
708
  nestedObject.setNestedValue(newTranslations, key, valueToSet, separator);
711
709
  }
package/dist/esm/cli.js CHANGED
@@ -26,7 +26,7 @@ const program = new Command();
26
26
  program
27
27
  .name('i18next-cli')
28
28
  .description('A unified, high-performance i18next CLI.')
29
- .version('1.46.2'); // This string is replaced with the actual version at build time by rollup
29
+ .version('1.46.3'); // This string is replaced with the actual version at build time by rollup
30
30
  // new: global config override option
31
31
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
32
32
  program
@@ -96,18 +96,13 @@ function hasEmptySegments(key, separator) {
96
96
  }
97
97
  /**
98
98
  * Detects a nesting conflict for `key` against the object being built.
99
+ * Returns the conflicting ancestor/descendant key path as a string when a
100
+ * conflict is found, or `null` when there is no conflict.
99
101
  *
100
- * Returns true when:
101
- * - A prefix of `key` already resolves to a *non-object* value in `obj`
102
- * (we cannot descend into a string to set a child).
103
- * - A parent of a segment in `key` would clobber an existing sub-tree
104
- * (the key itself resolves to an object but a new leaf is about to overwrite it
105
- * and the object already has children from a deeper extracted key).
106
- *
107
- * In both cases the caller should skip the conflicting key rather than producing
108
- * a silently-wrong flat fallback.
102
+ * The returned string lets callers produce an actionable error message
103
+ * pointing to the specific key that is already occupying the conflicting path.
109
104
  */
110
- function hasNestingConflict(obj, key, separator) {
105
+ function findNestingConflict(obj, key, separator) {
111
106
  const parts = key.split(separator);
112
107
  let current = obj;
113
108
  for (let i = 0; i < parts.length - 1; i++) {
@@ -115,12 +110,12 @@ function hasNestingConflict(obj, key, separator) {
115
110
  const value = current[part];
116
111
  if (value === undefined || value === null) {
117
112
  // Path does not exist yet — no conflict
118
- return false;
113
+ return null;
119
114
  }
120
115
  if (typeof value !== 'object' || Array.isArray(value)) {
121
116
  // A non-object value already occupies an ancestor segment.
122
117
  // We cannot nest inside a string/number/array.
123
- return true;
118
+ return parts.slice(0, i + 1).join(separator);
124
119
  }
125
120
  current = value;
126
121
  }
@@ -130,9 +125,10 @@ function hasNestingConflict(obj, key, separator) {
130
125
  const leafPart = parts[parts.length - 1];
131
126
  const leafValue = current[leafPart];
132
127
  if (typeof leafValue === 'object' && leafValue !== null && !Array.isArray(leafValue) && Object.keys(leafValue).length > 0) {
133
- return true;
128
+ // The conflicting path is the key itself (it already has nested children)
129
+ return key;
134
130
  }
135
- return false;
131
+ return null;
136
132
  }
137
133
  /**
138
134
  * Recursively sorts the keys of an object.
@@ -696,14 +692,16 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
696
692
  // that was already written by a different extracted key, e.g.:
697
693
  // t("a.b") => sets a.b = string
698
694
  // t("a.b.c") => tries to descend into a.b which is already a string
699
- // In that situation we skip the conflicting key rather than producing a
700
- // silently-wrong top-level flat fallback like { "a.b.c": "a.b.c" }.
701
- if (separator && typeof separator === 'string' && hasNestingConflict(newTranslations, key, separator)) {
702
- // Log at most once per key so the output is not spammy
703
- // (the logger reference is captured from the outer scope via closure – it is not
704
- // directly available here, but we can at least avoid crashing silently).
705
- // Callers / tests can verify the key is simply absent from the output.
706
- continue;
695
+ // In that situation we skip the conflicting key and emit a console.error so
696
+ // developers see the problem immediately a skipped key becomes a missing
697
+ // translation at runtime.
698
+ if (separator && typeof separator === 'string') {
699
+ const conflictingPath = findNestingConflict(newTranslations, key, separator);
700
+ if (conflictingPath !== null) {
701
+ console.error(`[i18next-toolkit] Nesting conflict: key "${key}" conflicts with existing key "${conflictingPath}". ` +
702
+ `"${key}" will be skipped — fix the overlapping key paths in your source code to avoid missing translations at runtime.`);
703
+ continue;
704
+ }
707
705
  }
708
706
  setNestedValue(newTranslations, key, valueToSet, separator);
709
707
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.46.2",
3
+ "version": "1.46.3",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {