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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 16.6.0
2
+
3
+ - warn when `t` is called before `ready` with `useSuspense: false` [1896](https://github.com/i18next/react-i18next/issues/1896)
4
+ - type-safe `values` prop on `<Trans />` component — interpolation variables are now inferred from the translation string when custom types are configured [1772](https://github.com/i18next/react-i18next/issues/1772)
5
+
6
+ ### 16.5.8
7
+
8
+ - A selector function cannot be resolved without an i18n instance... returning empty string is safer than leaking the raw function reference. [1907](https://github.com/i18next/react-i18next/issues/1907)
9
+
1
10
  ### 16.5.7
2
11
 
3
12
  - fix: `<Trans>` component with `enableSelector: true` does not support multiple selectors for fallbacks [1907](https://github.com/i18next/react-i18next/issues/1907)
@@ -4,9 +4,11 @@ import type {
4
4
  ApplyTarget,
5
5
  ConstrainTarget,
6
6
  GetSource,
7
+ InterpolationMap,
7
8
  ParseKeys,
8
9
  Namespace,
9
10
  SelectorFn,
11
+ TFunctionReturn,
10
12
  TypeOptions,
11
13
  TOptions,
12
14
  TFunction,
@@ -15,6 +17,10 @@ import * as React from 'react';
15
17
 
16
18
  type _DefaultNamespace = TypeOptions['defaultNS'];
17
19
  type _EnableSelector = TypeOptions['enableSelector'];
20
+ type _KeySeparator = TypeOptions['keySeparator'];
21
+ type _AppendKeyPrefix<Key, KPrefix> = KPrefix extends string
22
+ ? `${KPrefix}${_KeySeparator}${Key & string}`
23
+ : Key;
18
24
 
19
25
  type TransChild = React.ReactNode | Record<string, unknown>;
20
26
  type $NoInfer<T> = [T][T extends T ? 0 : never];
@@ -37,7 +43,7 @@ export type TransProps<
37
43
  ns?: Ns;
38
44
  parent?: string | React.ComponentType<any> | null; // used in React.createElement if not null
39
45
  tOptions?: TOpt;
40
- values?: {};
46
+ values?: InterpolationMap<TFunctionReturn<Ns, _AppendKeyPrefix<Key, KPrefix>, TOpt>>;
41
47
  shouldUnescape?: boolean;
42
48
  t?: TFunction<Ns, KPrefix>;
43
49
  };
@@ -72,7 +78,7 @@ export interface TransSelectorProps<
72
78
  ns?: Ns;
73
79
  parent?: string | React.ComponentType<any> | null; // used in React.createElement if not null
74
80
  tOptions?: TOpt;
75
- values?: {};
81
+ values?: Key extends (...args: any[]) => infer R ? InterpolationMap<R> : {};
76
82
  shouldUnescape?: boolean;
77
83
  t?: TFunction<Ns, KPrefix>;
78
84
  }
@@ -107,7 +113,8 @@ export type ErrorCode =
107
113
  | 'TRANS_NULL_VALUE'
108
114
  | 'TRANS_INVALID_OBJ'
109
115
  | 'TRANS_INVALID_VAR'
110
- | 'TRANS_INVALID_COMPONENTS';
116
+ | 'TRANS_INVALID_COMPONENTS'
117
+ | 'USE_T_BEFORE_READY';
111
118
 
112
119
  export type ErrorMeta = {
113
120
  code: ErrorCode;
@@ -190,7 +190,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
190
190
  }
191
191
  return current;
192
192
  };
193
- const getCleanedCode = code => code?.replace('_', '-');
193
+ const getCleanedCode = code => code?.replace(/_/g, '-');
194
194
  const consoleLogger = {
195
195
  type: 'logger',
196
196
  log(args) {
@@ -446,7 +446,16 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
446
446
  const {
447
447
  [PATH_KEY]: path
448
448
  } = selector(createProxy());
449
- return path.join(opts?.keySeparator ?? '.');
449
+ const keySeparator = opts?.keySeparator ?? '.';
450
+ const nsSeparator = opts?.nsSeparator ?? ':';
451
+ if (path.length > 1 && nsSeparator) {
452
+ const ns = opts?.ns;
453
+ const nsArray = Array.isArray(ns) ? ns : null;
454
+ if (nsArray && nsArray.length > 1 && nsArray.slice(1).includes(path[0])) {
455
+ return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
456
+ }
457
+ }
458
+ return path.join(keySeparator);
450
459
  }
451
460
  const checkedLoadedFor = {};
452
461
  const shouldHandleAsObject = res => !isString$1(res) && typeof res !== 'boolean' && typeof res !== 'number';
@@ -519,6 +528,10 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
519
528
  ...opt
520
529
  });
521
530
  if (!Array.isArray(keys)) keys = [String(keys)];
531
+ keys = keys.map(k => typeof k === 'function' ? keysFromSelector(k, {
532
+ ...this.options,
533
+ ...opt
534
+ }) : String(k));
522
535
  const returnDetails = opt.returnDetails !== undefined ? opt.returnDetails : this.options.returnDetails;
523
536
  const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator;
524
537
  const {
@@ -765,6 +778,10 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
765
778
  let usedLng;
766
779
  let usedNS;
767
780
  if (isString$1(keys)) keys = [keys];
781
+ if (Array.isArray(keys)) keys = keys.map(k => typeof k === 'function' ? keysFromSelector(k, {
782
+ ...this.options,
783
+ ...opt
784
+ }) : k);
768
785
  keys.forEach(k => {
769
786
  if (this.isValidLookup(found)) return;
770
787
  const extracted = this.extractFromKey(k, opt);
@@ -1003,9 +1020,6 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1003
1020
  this.logger = baseLogger.create('pluralResolver');
1004
1021
  this.pluralRulesCache = {};
1005
1022
  }
1006
- addRule(lng, obj) {
1007
- this.rules[lng] = obj;
1008
- }
1009
1023
  clearCache() {
1010
1024
  this.pluralRulesCache = {};
1011
1025
  }
@@ -1025,7 +1039,7 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1025
1039
  type
1026
1040
  });
1027
1041
  } catch (err) {
1028
- if (!Intl) {
1042
+ if (typeof Intl === 'undefined') {
1029
1043
  this.logger.error('No Intl support, please use an Intl polyfill!');
1030
1044
  return dummyRule;
1031
1045
  }
@@ -1205,13 +1219,13 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1205
1219
  const handleHasOptions = (key, inheritedOptions) => {
1206
1220
  const sep = this.nestingOptionsSeparator;
1207
1221
  if (key.indexOf(sep) < 0) return key;
1208
- const c = key.split(new RegExp(`${sep}[ ]*{`));
1222
+ const c = key.split(new RegExp(`${regexEscape(sep)}[ ]*{`));
1209
1223
  let optionsString = `{${c[1]}`;
1210
1224
  key = c[0];
1211
1225
  optionsString = this.interpolate(optionsString, clonedOptions);
1212
1226
  const matchedSingleQuotes = optionsString.match(/'/g);
1213
1227
  const matchedDoubleQuotes = optionsString.match(/"/g);
1214
- if ((matchedSingleQuotes?.length ?? 0) % 2 === 0 && !matchedDoubleQuotes || matchedDoubleQuotes.length % 2 !== 0) {
1228
+ if ((matchedSingleQuotes?.length ?? 0) % 2 === 0 && !matchedDoubleQuotes || (matchedDoubleQuotes?.length ?? 0) % 2 !== 0) {
1215
1229
  optionsString = optionsString.replace(/'/g, '"');
1216
1230
  }
1217
1231
  try {
@@ -1689,6 +1703,23 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1689
1703
  }
1690
1704
  });
1691
1705
  };
1706
+ const SUPPORT_NOTICE_KEY = '__i18next_supportNoticeShown';
1707
+ const getSupportNoticeShown = () => typeof globalThis !== 'undefined' && !!globalThis[SUPPORT_NOTICE_KEY];
1708
+ const setSupportNoticeShown = () => {
1709
+ if (typeof globalThis !== 'undefined') globalThis[SUPPORT_NOTICE_KEY] = true;
1710
+ };
1711
+ const usesLocize = inst => {
1712
+ if (inst?.modules?.backend?.name?.indexOf('Locize') > 0) return true;
1713
+ if (inst?.modules?.backend?.constructor?.name?.indexOf('Locize') > 0) return true;
1714
+ if (inst?.options?.backend?.backends) {
1715
+ if (inst.options.backend.backends.some(b => b?.name?.indexOf('Locize') > 0 || b?.constructor?.name?.indexOf('Locize') > 0)) return true;
1716
+ }
1717
+ if (inst?.options?.backend?.projectId) return true;
1718
+ if (inst?.options?.backend?.backendOptions) {
1719
+ if (inst.options.backend.backendOptions.some(b => b?.projectId)) return true;
1720
+ }
1721
+ return false;
1722
+ };
1692
1723
  class I18n extends EventEmitter {
1693
1724
  constructor(options = {}, callback) {
1694
1725
  super();
@@ -1741,6 +1772,10 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
1741
1772
  if (typeof this.options.overloadTranslationOptionHandler !== 'function') {
1742
1773
  this.options.overloadTranslationOptionHandler = defOpts.overloadTranslationOptionHandler;
1743
1774
  }
1775
+ if (this.options.showSupportNotice !== false && !usesLocize(this) && !getSupportNoticeShown()) {
1776
+ 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 💙');
1777
+ setSupportNoticeShown();
1778
+ }
1744
1779
  const createClassOnDemand = ClassOrObject => {
1745
1780
  if (!ClassOrObject) return null;
1746
1781
  if (typeof ClassOrObject === 'function') return new ClassOrObject();
@@ -2001,21 +2036,20 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2001
2036
  o.lngs = o.lngs || fixedT.lngs;
2002
2037
  o.ns = o.ns || fixedT.ns;
2003
2038
  if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
2039
+ const selectorOpts = {
2040
+ ...this.options,
2041
+ ...o
2042
+ };
2043
+ if (typeof o.keyPrefix === 'function') o.keyPrefix = keysFromSelector(o.keyPrefix, selectorOpts);
2004
2044
  const keySeparator = this.options.keySeparator || '.';
2005
2045
  let resultKey;
2006
2046
  if (o.keyPrefix && Array.isArray(key)) {
2007
2047
  resultKey = key.map(k => {
2008
- if (typeof k === 'function') k = keysFromSelector(k, {
2009
- ...this.options,
2010
- ...opts
2011
- });
2048
+ if (typeof k === 'function') k = keysFromSelector(k, selectorOpts);
2012
2049
  return `${o.keyPrefix}${keySeparator}${k}`;
2013
2050
  });
2014
2051
  } else {
2015
- if (typeof key === 'function') key = keysFromSelector(key, {
2016
- ...this.options,
2017
- ...opts
2018
- });
2052
+ if (typeof key === 'function') key = keysFromSelector(key, selectorOpts);
2019
2053
  resultKey = o.keyPrefix ? `${o.keyPrefix}${keySeparator}${key}` : key;
2020
2054
  }
2021
2055
  return this.t(resultKey, o);
@@ -2156,7 +2190,19 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
2156
2190
  clone.store = new ResourceStore(clonedData, mergedOptions);
2157
2191
  clone.services.resourceStore = clone.store;
2158
2192
  }
2159
- if (options.interpolation) clone.services.interpolator = new Interpolator(mergedOptions);
2193
+ if (options.interpolation) {
2194
+ const defOpts = get();
2195
+ const mergedInterpolation = {
2196
+ ...defOpts.interpolation,
2197
+ ...this.options.interpolation,
2198
+ ...options.interpolation
2199
+ };
2200
+ const mergedForInterpolator = {
2201
+ ...mergedOptions,
2202
+ interpolation: mergedInterpolation
2203
+ };
2204
+ clone.services.interpolator = new Interpolator(mergedForInterpolator);
2205
+ }
2160
2206
  clone.translator = new Translator(clone.services, mergedOptions);
2161
2207
  clone.translator.on('*', (event, ...args) => {
2162
2208
  clone.emit(event, ...args);
@@ -3465,7 +3511,12 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
3465
3511
  const notReadyT = (k, optsOrDefaultValue) => {
3466
3512
  if (isString(optsOrDefaultValue)) return optsOrDefaultValue;
3467
3513
  if (isObject(optsOrDefaultValue) && isString(optsOrDefaultValue.defaultValue)) return optsOrDefaultValue.defaultValue;
3468
- return Array.isArray(k) ? k[k.length - 1] : k;
3514
+ if (typeof k === 'function') return '';
3515
+ if (Array.isArray(k)) {
3516
+ const last = k[k.length - 1];
3517
+ return typeof last === 'function' ? '' : last;
3518
+ }
3519
+ return k;
3469
3520
  };
3470
3521
  const notReadySnapshot = {
3471
3522
  t: notReadyT,
@@ -3592,8 +3643,12 @@ define(['exports', 'react'], (function (exports, React) { 'use strict';
3592
3643
  wrapperLangRef.current = lang;
3593
3644
  }
3594
3645
  }
3595
- const arr = [t, i18nWrapper, ready];
3596
- arr.t = t;
3646
+ const effectiveT = !ready && !useSuspense ? (...args) => {
3647
+ 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.');
3648
+ return t(...args);
3649
+ } : t;
3650
+ const arr = [effectiveT, i18nWrapper, ready];
3651
+ arr.t = effectiveT;
3597
3652
  arr.i18n = i18nWrapper;
3598
3653
  arr.ready = ready;
3599
3654
  return arr;