Skip to content

Commit 6b11535

Browse files
feat: format warning with code and data to allow conditional logging
1 parent ff509ba commit 6b11535

File tree

3 files changed

+78
-42
lines changed

3 files changed

+78
-42
lines changed

src/TransWithoutContext.js

+45-29
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export const nodesToString = (children, i18nOptions, i18n, i18nKey) => {
4545
// actual e.g. lorem
4646
// expected e.g. lorem
4747
stringNode += `${child}`;
48-
} else if (isValidElement(child)) {
48+
return;
49+
}
50+
if (isValidElement(child)) {
4951
const { props, type } = child;
5052
const childPropsCount = Object.keys(props).length;
5153
const shouldKeepChild = keepArray.indexOf(type) > -1;
@@ -55,53 +57,57 @@ export const nodesToString = (children, i18nOptions, i18n, i18nKey) => {
5557
// actual e.g. lorem <br/> ipsum
5658
// expected e.g. lorem <br/> ipsum
5759
stringNode += `<${type}/>`;
58-
} else if (
59-
(!childChildren && (!shouldKeepChild || childPropsCount)) ||
60-
props.i18nIsDynamicList
61-
) {
60+
return;
61+
}
62+
if ((!childChildren && (!shouldKeepChild || childPropsCount)) || props.i18nIsDynamicList) {
6263
// actual e.g. lorem <hr className="test" /> ipsum
6364
// expected e.g. lorem <0></0> ipsum
6465
// or
6566
// we got a dynamic list like
6667
// e.g. <ul i18nIsDynamicList>{['a', 'b'].map(item => ( <li key={item}>{item}</li> ))}</ul>
6768
// expected e.g. "<0></0>", not e.g. "<0><0>a</0><1>b</1></0>"
6869
stringNode += `<${childIndex}></${childIndex}>`;
69-
} else if (shouldKeepChild && childPropsCount === 1 && isString(childChildren)) {
70+
return;
71+
}
72+
if (shouldKeepChild && childPropsCount === 1 && isString(childChildren)) {
7073
// actual e.g. dolor <strong>bold</strong> amet
7174
// expected e.g. dolor <strong>bold</strong> amet
7275
stringNode += `<${type}>${childChildren}</${type}>`;
73-
} else {
74-
// regular case mapping the inner children
75-
const content = nodesToString(childChildren, i18nOptions, i18n, i18nKey);
76-
stringNode += `<${childIndex}>${content}</${childIndex}>`;
76+
return;
7777
}
78-
} else if (child === null) {
79-
warn(i18n, `Trans: the passed in value is invalid - seems you passed in a null child.`);
80-
} else if (isObject(child)) {
78+
// regular case mapping the inner children
79+
const content = nodesToString(childChildren, i18nOptions, i18n, i18nKey);
80+
stringNode += `<${childIndex}>${content}</${childIndex}>`;
81+
return;
82+
}
83+
if (child === null) {
84+
warn(i18n, 'TRANS_NULL_VALUE', `Passed in a null value as child`, { i18nKey });
85+
return;
86+
}
87+
if (isObject(child)) {
8188
// e.g. lorem {{ value, format }} ipsum
8289
const { format, ...clone } = child;
8390
const keys = Object.keys(clone);
8491

8592
if (keys.length === 1) {
8693
const value = format ? `${keys[0]}, ${format}` : keys[0];
8794
stringNode += `{{${value}}}`;
88-
} else {
89-
// not a valid interpolation object (can only contain one value plus format)
90-
warn(
91-
i18n,
92-
`react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.`,
93-
child,
94-
i18nKey,
95-
);
95+
return;
9696
}
97-
} else {
9897
warn(
9998
i18n,
100-
`Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.`,
101-
child,
102-
i18nKey,
99+
'TRANS_INVALID_OBJ',
100+
`Invalid child - Object should only have keys {{ value, format }} (format is optional).`,
101+
{ i18nKey, child },
103102
);
103+
return;
104104
}
105+
warn(
106+
i18n,
107+
'TRANS_INVALID_VAR',
108+
`Passed in a variable like {number} - pass variables for interpolation as full objects like {{number}}.`,
109+
{ i18nKey, child },
110+
);
105111
});
106112

107113
return stringNode;
@@ -336,7 +342,7 @@ const generateObjectComponents = (components, translation) => {
336342
return componentMap;
337343
};
338344

339-
const generateComponents = (components, translation, i18n) => {
345+
const generateComponents = (components, translation, i18n, i18nKey) => {
340346
if (!components) return null;
341347

342348
// components could be either an array or an object
@@ -351,7 +357,12 @@ const generateComponents = (components, translation, i18n) => {
351357

352358
// if components is not an array or an object, warn the user
353359
// and return null
354-
warnOnce(i18n, '<Trans /> component prop expects an object or an array');
360+
warnOnce(
361+
i18n,
362+
'TRANS_INVALID_COMPONENTS',
363+
`<Trans /> "components" prop expects an object or array`,
364+
{ i18nKey },
365+
);
355366
return null;
356367
};
357368

@@ -374,7 +385,12 @@ export function Trans({
374385
const i18n = i18nFromProps || getI18n();
375386

376387
if (!i18n) {
377-
warnOnce(i18n, 'You will need to pass in an i18next instance by using i18nextReactModule');
388+
warnOnce(
389+
i18n,
390+
'NO_I18NEXT_INSTANCE',
391+
`Trans: You need to pass in an i18next instance using i18nextReactModule`,
392+
{ i18nKey },
393+
);
378394
return children;
379395
}
380396

@@ -417,7 +433,7 @@ export function Trans({
417433
};
418434
const translation = key ? t(key, combinedTOpts) : defaultValue;
419435

420-
const generatedComponents = generateComponents(components, translation, i18n);
436+
const generatedComponents = generateComponents(components, translation, i18n, i18nKey);
421437

422438
const content = renderNodes(
423439
generatedComponents || children,

src/useTranslation.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ export const useTranslation = (ns, props = {}) => {
3535
const i18n = i18nFromProps || i18nFromContext || getI18n();
3636
if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces();
3737
if (!i18n) {
38-
warnOnce(i18n, 'You will need to pass in an i18next instance by using initReactI18next');
38+
warnOnce(
39+
i18n,
40+
'NO_I18NEXT_INSTANCE',
41+
'useTranslation: You will need to pass in an i18next instance by using initReactI18next',
42+
);
3943
const notReadyT = (k, optsOrDefaultValue) => {
4044
if (isString(optsOrDefaultValue)) return optsOrDefaultValue;
4145
if (isObject(optsOrDefaultValue) && isString(optsOrDefaultValue.defaultValue))
@@ -52,7 +56,8 @@ export const useTranslation = (ns, props = {}) => {
5256
if (i18n.options.react?.wait)
5357
warnOnce(
5458
i18n,
55-
'It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.',
59+
'DEPRECATED_OPTION',
60+
'useTranslation: It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.',
5661
);
5762

5863
const i18nOptions = { ...getDefaults(), ...i18n.options.react, ...props };

src/utils.js

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
1-
export const warn = (i18n, ...args) => {
1+
/** @template {string} T @param {...T} s @return {T[]} */
2+
const tup = (...s) => s;
3+
const ERR_CODES = tup(
4+
'NO_I18NEXT_INSTANCE',
5+
'NO_LANGUAGES',
6+
'DEPRECATED_OPTION',
7+
'TRANS_NULL_VALUE',
8+
'TRANS_INVALID_OBJ',
9+
'TRANS_INVALID_VAR',
10+
'TRANS_INVALID_COMPONENTS',
11+
);
12+
/** @type {(i18n:any,code:typeof ERR_CODES[number],msg?:string, rest?:{[key:string]: any})=>void} */
13+
export const warn = (i18n, code, msg, rest) => {
14+
const args = [msg, { code, ...(rest || {}) }];
215
if (i18n?.services?.logger?.forward) {
3-
i18n.services.logger.forward(args, 'warn', 'react-i18next::', true);
4-
} else if (i18n?.services?.logger?.warn) {
5-
if (isString(args[0])) args[0] = `react-i18next:: ${args[0]}`;
16+
return i18n.services.logger.forward(args, 'warn', 'react-i18next::', true);
17+
}
18+
if (isString(args[0])) args[0] = `react-i18next:: ${args[0]}`;
19+
if (i18n?.services?.logger?.warn) {
620
i18n.services.logger.warn(...args);
721
} else if (console?.warn) {
8-
if (isString(args[0])) args[0] = `react-i18next:: ${args[0]}`;
922
console.warn(...args);
1023
}
1124
};
12-
1325
const alreadyWarned = {};
14-
export const warnOnce = (i18n, ...args) => {
15-
if (isString(args[0]) && alreadyWarned[args[0]]) return;
16-
if (isString(args[0])) alreadyWarned[args[0]] = new Date();
17-
warn(i18n, ...args);
26+
/** @type {typeof warn} */
27+
export const warnOnce = (i18n, code, msg, rest) => {
28+
if (isString(msg) && alreadyWarned[msg]) return;
29+
if (isString(msg)) alreadyWarned[msg] = new Date();
30+
warn(i18n, code, msg, rest);
1831
};
1932

2033
// not needed right now
@@ -60,7 +73,9 @@ export const loadLanguages = (i18n, lng, ns, cb) => {
6073

6174
export const hasLoadedNamespace = (ns, i18n, options = {}) => {
6275
if (!i18n.languages || !i18n.languages.length) {
63-
warnOnce(i18n, 'i18n.languages were undefined or empty', i18n.languages);
76+
warnOnce(i18n, 'NO_LANGUAGES', 'i18n.languages were undefined or empty', {
77+
languages: i18n.languages,
78+
});
6479
return true;
6580
}
6681

0 commit comments

Comments
 (0)