i18next-cli 1.50.4 → 1.50.6

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
@@ -31,7 +31,7 @@ const program = new commander.Command();
31
31
  program
32
32
  .name('i18next-cli')
33
33
  .description('A unified, high-performance i18next CLI.')
34
- .version('1.50.4'); // This string is replaced with the actual version at build time by rollup
34
+ .version('1.50.6'); // 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
@@ -96,7 +96,7 @@ async function runExtractor(config, options = {}) {
96
96
  // Show the funnel message only if files were actually changed.
97
97
  // When new translation files are created (new namespace or first extraction),
98
98
  // always show the funnel regardless of cooldown.
99
- if (anyFileUpdated && !options.isDryRun)
99
+ if (anyFileUpdated && !options.isDryRun && !options.quiet)
100
100
  await printLocizeFunnel(options.logger, anyNewFile);
101
101
  return { anyFileUpdated, hasErrors: fileErrors.length > 0 };
102
102
  }
@@ -716,7 +716,32 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
716
716
  }
717
717
  nestedObject.setNestedValue(newTranslations, key, valueToSet, separator);
718
718
  }
719
- // 2. If sorting is enabled, recursively sort the entire object.
719
+ // 2a. When sort is disabled but removeUnusedKeys is on, the rebuild from `{}`
720
+ // lost the original key order. Reorder to match existingTranslations, with new keys at the end.
721
+ if (sort === false && removeUnusedKeys) {
722
+ const reorderToMatch = (newObj, refObj) => {
723
+ if (typeof newObj !== 'object' || newObj === null || typeof refObj !== 'object' || refObj === null)
724
+ return newObj;
725
+ const ordered = {};
726
+ // First: keys from refObj in original order
727
+ for (const key of Object.keys(refObj)) {
728
+ if (key in newObj) {
729
+ ordered[key] = (typeof newObj[key] === 'object' && newObj[key] !== null && typeof refObj[key] === 'object' && refObj[key] !== null)
730
+ ? reorderToMatch(newObj[key], refObj[key])
731
+ : newObj[key];
732
+ }
733
+ }
734
+ // Then: new keys not in refObj
735
+ for (const key of Object.keys(newObj)) {
736
+ if (!(key in ordered)) {
737
+ ordered[key] = newObj[key];
738
+ }
739
+ }
740
+ return ordered;
741
+ };
742
+ return reorderToMatch(newTranslations, existingTranslations);
743
+ }
744
+ // 2b. If sorting is enabled, recursively sort the entire object.
720
745
  // This correctly handles both top-level and nested keys.
721
746
  if (sort === true) {
722
747
  return sortObject(newTranslations, config);
@@ -811,7 +836,7 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
811
836
  const keysByNS = new Map();
812
837
  for (const k of keys.values()) {
813
838
  const ns = k.ns;
814
- const nsKey = (k.nsIsImplicit && config.extract.defaultNS === false)
839
+ const nsKey = (k.nsIsImplicit && (config.extract.defaultNS === false || config.extract.nsSeparator === false))
815
840
  ? NO_NS_TOKEN
816
841
  : String(ns ?? (config.extract.defaultNS ?? 'translation'));
817
842
  if (!keysByNS.has(nsKey))
@@ -848,7 +873,7 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
848
873
  // (possibly as nested objects when keySeparator is '.'), which should NOT
849
874
  // be interpreted as "namespaced files". This avoids splitting a single
850
875
  // merged translations file into artificial namespace buckets on re-extract.
851
- const existingIsNamespaced = (config.extract.defaultNS !== false) && existingKeys.some(k => {
876
+ const existingIsNamespaced = (config.extract.defaultNS !== false) && (config.extract.nsSeparator !== false) && existingKeys.some(k => {
852
877
  const v = existingMergedFile[k];
853
878
  return typeof v === 'object' && v !== null && !Array.isArray(v);
854
879
  });
@@ -204,10 +204,10 @@ async function runSyncer(config, options = {}) {
204
204
  }
205
205
  spinner.succeed(node_util.styleText('bold', 'Synchronization complete!'));
206
206
  logMessages.forEach(msg => internalLogger.info ? internalLogger.info(msg) : console.log(msg));
207
- if (wasAnythingSynced) {
207
+ if (wasAnythingSynced && !options.quiet) {
208
208
  await printLocizeFunnel();
209
209
  }
210
- else {
210
+ else if (!wasAnythingSynced) {
211
211
  if (typeof internalLogger.info === 'function')
212
212
  internalLogger.info(node_util.styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
213
213
  else
package/dist/esm/cli.js CHANGED
@@ -29,7 +29,7 @@ const program = new Command();
29
29
  program
30
30
  .name('i18next-cli')
31
31
  .description('A unified, high-performance i18next CLI.')
32
- .version('1.50.4'); // This string is replaced with the actual version at build time by rollup
32
+ .version('1.50.6'); // This string is replaced with the actual version at build time by rollup
33
33
  // new: global config override option
34
34
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
35
35
  program
@@ -94,7 +94,7 @@ async function runExtractor(config, options = {}) {
94
94
  // Show the funnel message only if files were actually changed.
95
95
  // When new translation files are created (new namespace or first extraction),
96
96
  // always show the funnel regardless of cooldown.
97
- if (anyFileUpdated && !options.isDryRun)
97
+ if (anyFileUpdated && !options.isDryRun && !options.quiet)
98
98
  await printLocizeFunnel(options.logger, anyNewFile);
99
99
  return { anyFileUpdated, hasErrors: fileErrors.length > 0 };
100
100
  }
@@ -714,7 +714,32 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
714
714
  }
715
715
  setNestedValue(newTranslations, key, valueToSet, separator);
716
716
  }
717
- // 2. If sorting is enabled, recursively sort the entire object.
717
+ // 2a. When sort is disabled but removeUnusedKeys is on, the rebuild from `{}`
718
+ // lost the original key order. Reorder to match existingTranslations, with new keys at the end.
719
+ if (sort === false && removeUnusedKeys) {
720
+ const reorderToMatch = (newObj, refObj) => {
721
+ if (typeof newObj !== 'object' || newObj === null || typeof refObj !== 'object' || refObj === null)
722
+ return newObj;
723
+ const ordered = {};
724
+ // First: keys from refObj in original order
725
+ for (const key of Object.keys(refObj)) {
726
+ if (key in newObj) {
727
+ ordered[key] = (typeof newObj[key] === 'object' && newObj[key] !== null && typeof refObj[key] === 'object' && refObj[key] !== null)
728
+ ? reorderToMatch(newObj[key], refObj[key])
729
+ : newObj[key];
730
+ }
731
+ }
732
+ // Then: new keys not in refObj
733
+ for (const key of Object.keys(newObj)) {
734
+ if (!(key in ordered)) {
735
+ ordered[key] = newObj[key];
736
+ }
737
+ }
738
+ return ordered;
739
+ };
740
+ return reorderToMatch(newTranslations, existingTranslations);
741
+ }
742
+ // 2b. If sorting is enabled, recursively sort the entire object.
718
743
  // This correctly handles both top-level and nested keys.
719
744
  if (sort === true) {
720
745
  return sortObject(newTranslations, config);
@@ -809,7 +834,7 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
809
834
  const keysByNS = new Map();
810
835
  for (const k of keys.values()) {
811
836
  const ns = k.ns;
812
- const nsKey = (k.nsIsImplicit && config.extract.defaultNS === false)
837
+ const nsKey = (k.nsIsImplicit && (config.extract.defaultNS === false || config.extract.nsSeparator === false))
813
838
  ? NO_NS_TOKEN
814
839
  : String(ns ?? (config.extract.defaultNS ?? 'translation'));
815
840
  if (!keysByNS.has(nsKey))
@@ -846,7 +871,7 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
846
871
  // (possibly as nested objects when keySeparator is '.'), which should NOT
847
872
  // be interpreted as "namespaced files". This avoids splitting a single
848
873
  // merged translations file into artificial namespace buckets on re-extract.
849
- const existingIsNamespaced = (config.extract.defaultNS !== false) && existingKeys.some(k => {
874
+ const existingIsNamespaced = (config.extract.defaultNS !== false) && (config.extract.nsSeparator !== false) && existingKeys.some(k => {
850
875
  const v = existingMergedFile[k];
851
876
  return typeof v === 'object' && v !== null && !Array.isArray(v);
852
877
  });
@@ -202,10 +202,10 @@ async function runSyncer(config, options = {}) {
202
202
  }
203
203
  spinner.succeed(styleText('bold', 'Synchronization complete!'));
204
204
  logMessages.forEach(msg => internalLogger.info ? internalLogger.info(msg) : console.log(msg));
205
- if (wasAnythingSynced) {
205
+ if (wasAnythingSynced && !options.quiet) {
206
206
  await printLocizeFunnel();
207
207
  }
208
- else {
208
+ else if (!wasAnythingSynced) {
209
209
  if (typeof internalLogger.info === 'function')
210
210
  internalLogger.info(styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
211
211
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.50.4",
3
+ "version": "1.50.6",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,6 +54,7 @@
54
54
  "devDependencies": {
55
55
  "@rollup/plugin-replace": "^6.0.3",
56
56
  "@rollup/plugin-terser": "^1.0.0",
57
+ "@rollup/plugin-typescript": "^12.3.0",
57
58
  "@types/inquirer": "^9.0.9",
58
59
  "@types/node": "^25.5.0",
59
60
  "@types/react": "^19.2.14",
@@ -64,7 +65,7 @@
64
65
  "eslint-plugin-import": "^2.32.0",
65
66
  "memfs": "^4.57.1",
66
67
  "neostandard": "^0.13.0",
67
- "rollup-plugin-typescript2": "^0.36.0",
68
+ "rollup": "^4.60.0",
68
69
  "typescript": "^5.9.3",
69
70
  "unplugin-swc": "^1.5.9",
70
71
  "vitest": "^4.1.0"
@@ -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;AAy3B9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,MAA4B,EAC7B,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA6I9B"}
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;AAk5B9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,MAA4B,EAC7B,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA6I9B"}