i18next-cli 1.71.1 → 1.71.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.71.1'); // This string is replaced with the actual version at build time by rollup
40
+ .version('1.71.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
@@ -342,13 +342,31 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
342
342
  : nsKeys;
343
343
  // Prepare namespace pattern checking helpers
344
344
  const rawPreserve = config.extract.preservePatterns || [];
345
+ // Fast equivalent of matching a `${objectKey}.*` glob per object key: instead of
346
+ // testing O(objectKeys) regexes per key, walk the key's '.' boundaries and look
347
+ // each ancestor prefix up in the Set — O(key depth). See issue #286.
348
+ const isUnderObjectKey = (key) => {
349
+ if (objectKeys.size === 0)
350
+ return false;
351
+ let i = key.indexOf('.');
352
+ while (i !== -1) {
353
+ if (objectKeys.has(key.slice(0, i)))
354
+ return true;
355
+ i = key.indexOf('.', i + 1);
356
+ }
357
+ return false;
358
+ };
345
359
  // Helper to check if a key should be filtered out during extraction
346
360
  const shouldFilterKey = (key) => {
347
- // 1) regex based patterns (existing behavior)
361
+ // 1) keys nested under a returnObjects / selector-API base key
362
+ if (isUnderObjectKey(key)) {
363
+ return true;
364
+ }
365
+ // 2) regex based patterns (existing behavior)
348
366
  if (preservePatterns.some(re => re.test(key))) {
349
367
  return true;
350
368
  }
351
- // 2) namespace:* style patterns (respect nsSeparator)
369
+ // 3) namespace:* style patterns (respect nsSeparator)
352
370
  for (const rp of rawPreserve) {
353
371
  if (typeof rp !== 'string')
354
372
  continue;
@@ -365,11 +383,15 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
365
383
  };
366
384
  // Helper to check if an existing key should be preserved
367
385
  const shouldPreserveExistingKey = (key) => {
368
- // 1) regex-style patterns
386
+ // 1) keys nested under a returnObjects / selector-API base key
387
+ if (isUnderObjectKey(key)) {
388
+ return true;
389
+ }
390
+ // 2) regex-style patterns
369
391
  if (preservePatterns.some(re => re.test(key))) {
370
392
  return true;
371
393
  }
372
- // 2) namespace:key patterns - check if pattern matches this namespace:key combination
394
+ // 3) namespace:key patterns - check if pattern matches this namespace:key combination
373
395
  for (const rp of rawPreserve) {
374
396
  if (typeof rp !== 'string')
375
397
  continue;
@@ -575,6 +597,19 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
575
597
  }
576
598
  }
577
599
  }
600
+ // Precompute every proper ancestor prefix (up to each keySeparator boundary) of the
601
+ // extracted keys, so the per-key "is this a leaf?" check below is a single Set lookup
602
+ // instead of an O(keys) scan per key. See issue #286.
603
+ const parentPrefixesInNewKeys = new Set();
604
+ if (typeof keySeparator === 'string') {
605
+ for (const { key } of filteredKeys) {
606
+ let i = key.indexOf(keySeparator);
607
+ while (i !== -1 && i < key.length) {
608
+ parentPrefixesInNewKeys.add(key.slice(0, i));
609
+ i = key.indexOf(keySeparator, i + 1);
610
+ }
611
+ }
612
+ }
578
613
  // 1. Build the object first, without any sorting.
579
614
  for (const { key, defaultValue: defaultValue$1, explicitDefault, hasCount, isExpandedPlural, isOrdinal } of filteredKeys) {
580
615
  // If this is a base plural key (hasCount true but not an already-expanded variant)
@@ -717,7 +752,7 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
717
752
  // For flat keys there cannot be nested children, so treat them as leaves.
718
753
  const isLeafInNewKeys = keySeparator === false
719
754
  ? true
720
- : !filteredKeys.some(otherKey => otherKey.key !== key && otherKey.key.startsWith(`${key}${keySeparator}`));
755
+ : !parentPrefixesInNewKeys.has(key);
721
756
  const isDerivedDefault = isDerivedFromKey(key, defaultValue$1, explicitDefault);
722
757
  // Determine if we should preserve an existing object
723
758
  const shouldPreserveObject = typeof existingValue === 'object' && existingValue !== null && (objectKeys.has(key) || // Explicit returnObjects
@@ -1099,8 +1134,12 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
1099
1134
  const patternsToPreserve = [...(config.extract.preservePatterns || [])];
1100
1135
  const indentation = config.extract.indentation ?? 2;
1101
1136
  for (const key of objectKeys) {
1102
- // Convert the object key to a glob pattern to preserve all its children
1103
- patternsToPreserve.push(`${key}.*`);
1137
+ // Object keys are matched directly (and cheaply) against the objectKeys Set in
1138
+ // buildNewTranslationsForNs (see isUnderObjectKey). Only a key that itself
1139
+ // contains a wildcard still needs the glob-to-regex path.
1140
+ if (key.includes('*')) {
1141
+ patternsToPreserve.push(`${key}.*`);
1142
+ }
1104
1143
  }
1105
1144
  const preservePatterns = patternsToPreserve.map(globToRegex);
1106
1145
  // Group keys by namespace. If the plugin recorded the namespace as implicit
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.71.1'); // This string is replaced with the actual version at build time by rollup
34
+ .version('1.71.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
@@ -340,13 +340,31 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
340
340
  : nsKeys;
341
341
  // Prepare namespace pattern checking helpers
342
342
  const rawPreserve = config.extract.preservePatterns || [];
343
+ // Fast equivalent of matching a `${objectKey}.*` glob per object key: instead of
344
+ // testing O(objectKeys) regexes per key, walk the key's '.' boundaries and look
345
+ // each ancestor prefix up in the Set — O(key depth). See issue #286.
346
+ const isUnderObjectKey = (key) => {
347
+ if (objectKeys.size === 0)
348
+ return false;
349
+ let i = key.indexOf('.');
350
+ while (i !== -1) {
351
+ if (objectKeys.has(key.slice(0, i)))
352
+ return true;
353
+ i = key.indexOf('.', i + 1);
354
+ }
355
+ return false;
356
+ };
343
357
  // Helper to check if a key should be filtered out during extraction
344
358
  const shouldFilterKey = (key) => {
345
- // 1) regex based patterns (existing behavior)
359
+ // 1) keys nested under a returnObjects / selector-API base key
360
+ if (isUnderObjectKey(key)) {
361
+ return true;
362
+ }
363
+ // 2) regex based patterns (existing behavior)
346
364
  if (preservePatterns.some(re => re.test(key))) {
347
365
  return true;
348
366
  }
349
- // 2) namespace:* style patterns (respect nsSeparator)
367
+ // 3) namespace:* style patterns (respect nsSeparator)
350
368
  for (const rp of rawPreserve) {
351
369
  if (typeof rp !== 'string')
352
370
  continue;
@@ -363,11 +381,15 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
363
381
  };
364
382
  // Helper to check if an existing key should be preserved
365
383
  const shouldPreserveExistingKey = (key) => {
366
- // 1) regex-style patterns
384
+ // 1) keys nested under a returnObjects / selector-API base key
385
+ if (isUnderObjectKey(key)) {
386
+ return true;
387
+ }
388
+ // 2) regex-style patterns
367
389
  if (preservePatterns.some(re => re.test(key))) {
368
390
  return true;
369
391
  }
370
- // 2) namespace:key patterns - check if pattern matches this namespace:key combination
392
+ // 3) namespace:key patterns - check if pattern matches this namespace:key combination
371
393
  for (const rp of rawPreserve) {
372
394
  if (typeof rp !== 'string')
373
395
  continue;
@@ -573,6 +595,19 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
573
595
  }
574
596
  }
575
597
  }
598
+ // Precompute every proper ancestor prefix (up to each keySeparator boundary) of the
599
+ // extracted keys, so the per-key "is this a leaf?" check below is a single Set lookup
600
+ // instead of an O(keys) scan per key. See issue #286.
601
+ const parentPrefixesInNewKeys = new Set();
602
+ if (typeof keySeparator === 'string') {
603
+ for (const { key } of filteredKeys) {
604
+ let i = key.indexOf(keySeparator);
605
+ while (i !== -1 && i < key.length) {
606
+ parentPrefixesInNewKeys.add(key.slice(0, i));
607
+ i = key.indexOf(keySeparator, i + 1);
608
+ }
609
+ }
610
+ }
576
611
  // 1. Build the object first, without any sorting.
577
612
  for (const { key, defaultValue, explicitDefault, hasCount, isExpandedPlural, isOrdinal } of filteredKeys) {
578
613
  // If this is a base plural key (hasCount true but not an already-expanded variant)
@@ -715,7 +750,7 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
715
750
  // For flat keys there cannot be nested children, so treat them as leaves.
716
751
  const isLeafInNewKeys = keySeparator === false
717
752
  ? true
718
- : !filteredKeys.some(otherKey => otherKey.key !== key && otherKey.key.startsWith(`${key}${keySeparator}`));
753
+ : !parentPrefixesInNewKeys.has(key);
719
754
  const isDerivedDefault = isDerivedFromKey(key, defaultValue, explicitDefault);
720
755
  // Determine if we should preserve an existing object
721
756
  const shouldPreserveObject = typeof existingValue === 'object' && existingValue !== null && (objectKeys.has(key) || // Explicit returnObjects
@@ -1097,8 +1132,12 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
1097
1132
  const patternsToPreserve = [...(config.extract.preservePatterns || [])];
1098
1133
  const indentation = config.extract.indentation ?? 2;
1099
1134
  for (const key of objectKeys) {
1100
- // Convert the object key to a glob pattern to preserve all its children
1101
- patternsToPreserve.push(`${key}.*`);
1135
+ // Object keys are matched directly (and cheaply) against the objectKeys Set in
1136
+ // buildNewTranslationsForNs (see isUnderObjectKey). Only a key that itself
1137
+ // contains a wildcard still needs the glob-to-regex path.
1138
+ if (key.includes('*')) {
1139
+ patternsToPreserve.push(`${key}.*`);
1140
+ }
1102
1141
  }
1103
1142
  const preservePatterns = patternsToPreserve.map(globToRegex);
1104
1143
  // Group keys by namespace. If the plugin recorded the namespace as implicit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.71.1",
3
+ "version": "1.71.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;AA2rC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAyK9B"}
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;AA8tC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CA6K9B"}