react-i18next 16.5.7 → 16.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-i18next",
3
- "version": "16.5.7",
3
+ "version": "16.6.0",
4
4
  "description": "Internationalization for react done right. Using the i18next i18n ecosystem.",
5
5
  "main": "dist/commonjs/index.js",
6
6
  "types": "./index.d.mts",
@@ -67,7 +67,7 @@
67
67
  "url": "https://github.com/i18next/react-i18next.git"
68
68
  },
69
69
  "dependencies": {
70
- "@babel/runtime": "^7.28.4",
70
+ "@babel/runtime": "^7.29.2",
71
71
  "html-parse-stringify": "^3.0.1",
72
72
  "use-sync-external-store": "^1.6.0"
73
73
  },
@@ -88,17 +88,17 @@
88
88
  }
89
89
  },
90
90
  "devDependencies": {
91
- "@babel/cli": "^7.28.3",
92
- "@babel/core": "^7.28.5",
93
- "@babel/eslint-parser": "^7.28.5",
91
+ "@babel/cli": "^7.28.6",
92
+ "@babel/core": "^7.29.0",
93
+ "@babel/eslint-parser": "^7.28.6",
94
94
  "@babel/plugin-proposal-async-generator-functions": "^7.20.7",
95
95
  "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
96
- "@babel/plugin-transform-modules-commonjs": "^7.27.1",
97
- "@babel/plugin-transform-runtime": "^7.28.5",
96
+ "@babel/plugin-transform-modules-commonjs": "^7.28.6",
97
+ "@babel/plugin-transform-runtime": "^7.29.0",
98
98
  "@babel/polyfill": "^7.12.1",
99
- "@babel/preset-env": "^7.28.5",
99
+ "@babel/preset-env": "^7.29.2",
100
100
  "@babel/preset-react": "^7.28.5",
101
- "@babel/register": "^7.28.3",
101
+ "@babel/register": "^7.28.6",
102
102
  "@rollup/plugin-babel": "^6.1.0",
103
103
  "@rollup/plugin-commonjs": "^26.0.3",
104
104
  "@rollup/plugin-node-resolve": "^15.3.1",
@@ -106,9 +106,9 @@
106
106
  "@rollup/plugin-terser": "0.4.4",
107
107
  "@testing-library/dom": "^10.4.1",
108
108
  "@testing-library/jest-dom": "^6.9.1",
109
- "@testing-library/react": "^16.3.1",
110
- "@types/jest": "^29.5.12",
111
- "@types/react": "^19.2.7",
109
+ "@testing-library/react": "^16.3.2",
110
+ "@types/jest": "^30.0.0",
111
+ "@types/react": "^19.2.14",
112
112
  "@vitest/coverage-v8": "^2.0.5",
113
113
  "all-contributors-cli": "^6.26.1",
114
114
  "babel-core": "^7.0.0-bridge.0",
@@ -127,15 +127,15 @@
127
127
  "eslint-plugin-testing-library": "^6.5.0",
128
128
  "happy-dom": "^14.12.3",
129
129
  "husky": "^9.1.7",
130
- "i18next": "^25.7.3",
131
- "lint-staged": "^15.5.2",
130
+ "i18next": "^25.10.1",
131
+ "lint-staged": "^16.4.0",
132
132
  "mkdirp": "^3.0.1",
133
- "prettier": "^3.7.4",
134
- "react": "^19.2.3",
135
- "react-dom": "^19.2.3",
136
- "react-test-renderer": "^19.2.3",
137
- "rimraf": "^6.1.2",
138
- "rollup": "^4.54.0",
133
+ "prettier": "^3.8.1",
134
+ "react": "^19.2.4",
135
+ "react-dom": "^19.2.4",
136
+ "react-test-renderer": "^19.2.4",
137
+ "rimraf": "^6.1.3",
138
+ "rollup": "^4.59.1",
139
139
  "typescript": "~5.9.3",
140
140
  "vitest": "^2.0.5",
141
141
  "yargs": "^17.7.2"
package/react-i18next.js CHANGED
@@ -194,7 +194,7 @@
194
194
  }
195
195
  return current;
196
196
  };
197
- const getCleanedCode = code => code?.replace('_', '-');
197
+ const getCleanedCode = code => code?.replace(/_/g, '-');
198
198
  const consoleLogger = {
199
199
  type: 'logger',
200
200
  log(args) {
@@ -450,7 +450,16 @@
450
450
  const {
451
451
  [PATH_KEY]: path
452
452
  } = selector(createProxy());
453
- return path.join(opts?.keySeparator ?? '.');
453
+ const keySeparator = opts?.keySeparator ?? '.';
454
+ const nsSeparator = opts?.nsSeparator ?? ':';
455
+ if (path.length > 1 && nsSeparator) {
456
+ const ns = opts?.ns;
457
+ const nsArray = Array.isArray(ns) ? ns : null;
458
+ if (nsArray && nsArray.length > 1 && nsArray.slice(1).includes(path[0])) {
459
+ return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
460
+ }
461
+ }
462
+ return path.join(keySeparator);
454
463
  }
455
464
  const checkedLoadedFor = {};
456
465
  const shouldHandleAsObject = res => !isString$1(res) && typeof res !== 'boolean' && typeof res !== 'number';
@@ -523,6 +532,10 @@
523
532
  ...opt
524
533
  });
525
534
  if (!Array.isArray(keys)) keys = [String(keys)];
535
+ keys = keys.map(k => typeof k === 'function' ? keysFromSelector(k, {
536
+ ...this.options,
537
+ ...opt
538
+ }) : String(k));
526
539
  const returnDetails = opt.returnDetails !== undefined ? opt.returnDetails : this.options.returnDetails;
527
540
  const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator;
528
541
  const {
@@ -769,6 +782,10 @@
769
782
  let usedLng;
770
783
  let usedNS;
771
784
  if (isString$1(keys)) keys = [keys];
785
+ if (Array.isArray(keys)) keys = keys.map(k => typeof k === 'function' ? keysFromSelector(k, {
786
+ ...this.options,
787
+ ...opt
788
+ }) : k);
772
789
  keys.forEach(k => {
773
790
  if (this.isValidLookup(found)) return;
774
791
  const extracted = this.extractFromKey(k, opt);
@@ -1007,9 +1024,6 @@
1007
1024
  this.logger = baseLogger.create('pluralResolver');
1008
1025
  this.pluralRulesCache = {};
1009
1026
  }
1010
- addRule(lng, obj) {
1011
- this.rules[lng] = obj;
1012
- }
1013
1027
  clearCache() {
1014
1028
  this.pluralRulesCache = {};
1015
1029
  }
@@ -1029,7 +1043,7 @@
1029
1043
  type
1030
1044
  });
1031
1045
  } catch (err) {
1032
- if (!Intl) {
1046
+ if (typeof Intl === 'undefined') {
1033
1047
  this.logger.error('No Intl support, please use an Intl polyfill!');
1034
1048
  return dummyRule;
1035
1049
  }
@@ -1209,13 +1223,13 @@
1209
1223
  const handleHasOptions = (key, inheritedOptions) => {
1210
1224
  const sep = this.nestingOptionsSeparator;
1211
1225
  if (key.indexOf(sep) < 0) return key;
1212
- const c = key.split(new RegExp(`${sep}[ ]*{`));
1226
+ const c = key.split(new RegExp(`${regexEscape(sep)}[ ]*{`));
1213
1227
  let optionsString = `{${c[1]}`;
1214
1228
  key = c[0];
1215
1229
  optionsString = this.interpolate(optionsString, clonedOptions);
1216
1230
  const matchedSingleQuotes = optionsString.match(/'/g);
1217
1231
  const matchedDoubleQuotes = optionsString.match(/"/g);
1218
- if ((matchedSingleQuotes?.length ?? 0) % 2 === 0 && !matchedDoubleQuotes || matchedDoubleQuotes.length % 2 !== 0) {
1232
+ if ((matchedSingleQuotes?.length ?? 0) % 2 === 0 && !matchedDoubleQuotes || (matchedDoubleQuotes?.length ?? 0) % 2 !== 0) {
1219
1233
  optionsString = optionsString.replace(/'/g, '"');
1220
1234
  }
1221
1235
  try {
@@ -1693,6 +1707,23 @@
1693
1707
  }
1694
1708
  });
1695
1709
  };
1710
+ const SUPPORT_NOTICE_KEY = '__i18next_supportNoticeShown';
1711
+ const getSupportNoticeShown = () => typeof globalThis !== 'undefined' && !!globalThis[SUPPORT_NOTICE_KEY];
1712
+ const setSupportNoticeShown = () => {
1713
+ if (typeof globalThis !== 'undefined') globalThis[SUPPORT_NOTICE_KEY] = true;
1714
+ };
1715
+ const usesLocize = inst => {
1716
+ if (inst?.modules?.backend?.name?.indexOf('Locize') > 0) return true;
1717
+ if (inst?.modules?.backend?.constructor?.name?.indexOf('Locize') > 0) return true;
1718
+ if (inst?.options?.backend?.backends) {
1719
+ if (inst.options.backend.backends.some(b => b?.name?.indexOf('Locize') > 0 || b?.constructor?.name?.indexOf('Locize') > 0)) return true;
1720
+ }
1721
+ if (inst?.options?.backend?.projectId) return true;
1722
+ if (inst?.options?.backend?.backendOptions) {
1723
+ if (inst.options.backend.backendOptions.some(b => b?.projectId)) return true;
1724
+ }
1725
+ return false;
1726
+ };
1696
1727
  class I18n extends EventEmitter {
1697
1728
  constructor(options = {}, callback) {
1698
1729
  super();
@@ -1745,6 +1776,10 @@
1745
1776
  if (typeof this.options.overloadTranslationOptionHandler !== 'function') {
1746
1777
  this.options.overloadTranslationOptionHandler = defOpts.overloadTranslationOptionHandler;
1747
1778
  }
1779
+ if (this.options.showSupportNotice !== false && !usesLocize(this) && !getSupportNoticeShown()) {
1780
+ if (typeof console !== 'undefined' && typeof console.info !== 'undefined') console.info('🌐 i18next is made possible by our own product, Locize — consider powering your project with managed localization (AI, CDN, integrations): https://locize.com 💙');
1781
+ setSupportNoticeShown();
1782
+ }
1748
1783
  const createClassOnDemand = ClassOrObject => {
1749
1784
  if (!ClassOrObject) return null;
1750
1785
  if (typeof ClassOrObject === 'function') return new ClassOrObject();
@@ -2005,21 +2040,20 @@
2005
2040
  o.lngs = o.lngs || fixedT.lngs;
2006
2041
  o.ns = o.ns || fixedT.ns;
2007
2042
  if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
2043
+ const selectorOpts = {
2044
+ ...this.options,
2045
+ ...o
2046
+ };
2047
+ if (typeof o.keyPrefix === 'function') o.keyPrefix = keysFromSelector(o.keyPrefix, selectorOpts);
2008
2048
  const keySeparator = this.options.keySeparator || '.';
2009
2049
  let resultKey;
2010
2050
  if (o.keyPrefix && Array.isArray(key)) {
2011
2051
  resultKey = key.map(k => {
2012
- if (typeof k === 'function') k = keysFromSelector(k, {
2013
- ...this.options,
2014
- ...opts
2015
- });
2052
+ if (typeof k === 'function') k = keysFromSelector(k, selectorOpts);
2016
2053
  return `${o.keyPrefix}${keySeparator}${k}`;
2017
2054
  });
2018
2055
  } else {
2019
- if (typeof key === 'function') key = keysFromSelector(key, {
2020
- ...this.options,
2021
- ...opts
2022
- });
2056
+ if (typeof key === 'function') key = keysFromSelector(key, selectorOpts);
2023
2057
  resultKey = o.keyPrefix ? `${o.keyPrefix}${keySeparator}${key}` : key;
2024
2058
  }
2025
2059
  return this.t(resultKey, o);
@@ -2160,7 +2194,19 @@
2160
2194
  clone.store = new ResourceStore(clonedData, mergedOptions);
2161
2195
  clone.services.resourceStore = clone.store;
2162
2196
  }
2163
- if (options.interpolation) clone.services.interpolator = new Interpolator(mergedOptions);
2197
+ if (options.interpolation) {
2198
+ const defOpts = get();
2199
+ const mergedInterpolation = {
2200
+ ...defOpts.interpolation,
2201
+ ...this.options.interpolation,
2202
+ ...options.interpolation
2203
+ };
2204
+ const mergedForInterpolator = {
2205
+ ...mergedOptions,
2206
+ interpolation: mergedInterpolation
2207
+ };
2208
+ clone.services.interpolator = new Interpolator(mergedForInterpolator);
2209
+ }
2164
2210
  clone.translator = new Translator(clone.services, mergedOptions);
2165
2211
  clone.translator.on('*', (event, ...args) => {
2166
2212
  clone.emit(event, ...args);
@@ -3469,7 +3515,12 @@
3469
3515
  const notReadyT = (k, optsOrDefaultValue) => {
3470
3516
  if (isString(optsOrDefaultValue)) return optsOrDefaultValue;
3471
3517
  if (isObject(optsOrDefaultValue) && isString(optsOrDefaultValue.defaultValue)) return optsOrDefaultValue.defaultValue;
3472
- return Array.isArray(k) ? k[k.length - 1] : k;
3518
+ if (typeof k === 'function') return '';
3519
+ if (Array.isArray(k)) {
3520
+ const last = k[k.length - 1];
3521
+ return typeof last === 'function' ? '' : last;
3522
+ }
3523
+ return k;
3473
3524
  };
3474
3525
  const notReadySnapshot = {
3475
3526
  t: notReadyT,
@@ -3596,8 +3647,12 @@
3596
3647
  wrapperLangRef.current = lang;
3597
3648
  }
3598
3649
  }
3599
- const arr = [t, i18nWrapper, ready];
3600
- arr.t = t;
3650
+ const effectiveT = !ready && !useSuspense ? (...args) => {
3651
+ warnOnce(i18n, 'USE_T_BEFORE_READY', 'useTranslation: t was called before ready. When using useSuspense: false, make sure to check the ready flag before using t.');
3652
+ return t(...args);
3653
+ } : t;
3654
+ const arr = [effectiveT, i18nWrapper, ready];
3655
+ arr.t = effectiveT;
3601
3656
  arr.i18n = i18nWrapper;
3602
3657
  arr.ready = ready;
3603
3658
  return arr;