From 8848b1e5efa696747865bfaf509bad55de5c0773 Mon Sep 17 00:00:00 2001 From: rawpixel-vincent Date: Sun, 29 Dec 2024 20:43:33 +0700 Subject: [PATCH] feat: add codes and data to warnings to allow conditional logging --- src/TransWithoutContext.js | 97 ++++++++++++++++++++++++++------------ src/useTranslation.js | 14 +++++- src/utils.js | 15 +++++- 3 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/TransWithoutContext.js b/src/TransWithoutContext.js index f47a99ef..801196c7 100644 --- a/src/TransWithoutContext.js +++ b/src/TransWithoutContext.js @@ -1,6 +1,6 @@ import { Fragment, isValidElement, cloneElement, createElement, Children } from 'react'; import HTML from 'html-parse-stringify'; -import { isObject, isString, warn, warnOnce } from './utils.js'; +import { ERR_CODES, isObject, isString, warn, warnOnce } from './utils.js'; import { getDefaults } from './defaults.js'; import { getI18n } from './i18nInstance.js'; @@ -45,7 +45,10 @@ export const nodesToString = (children, i18nOptions, i18n, i18nKey) => { // actual e.g. lorem // expected e.g. lorem stringNode += `${child}`; - } else if (isValidElement(child)) { + return; + } + + if (isValidElement(child)) { const { props, type } = child; const childPropsCount = Object.keys(props).length; const shouldKeepChild = keepArray.indexOf(type) > -1; @@ -55,10 +58,10 @@ export const nodesToString = (children, i18nOptions, i18n, i18nKey) => { // actual e.g. lorem
ipsum // expected e.g. lorem
ipsum stringNode += `<${type}/>`; - } else if ( - (!childChildren && (!shouldKeepChild || childPropsCount)) || - props.i18nIsDynamicList - ) { + return; + } + + if ((!childChildren && (!shouldKeepChild || childPropsCount)) || props.i18nIsDynamicList) { // actual e.g. lorem
ipsum // expected e.g. lorem <0> ipsum // or @@ -66,18 +69,32 @@ export const nodesToString = (children, i18nOptions, i18n, i18nKey) => { // e.g. // expected e.g. "<0>", not e.g. "<0><0>a<1>b" stringNode += `<${childIndex}>`; - } else if (shouldKeepChild && childPropsCount === 1 && isString(childChildren)) { + return; + } + + if (shouldKeepChild && childPropsCount === 1 && isString(childChildren)) { // actual e.g. dolor bold amet // expected e.g. dolor bold amet stringNode += `<${type}>${childChildren}`; - } else { - // regular case mapping the inner children - const content = nodesToString(childChildren, i18nOptions, i18n, i18nKey); - stringNode += `<${childIndex}>${content}`; + return; } - } else if (child === null) { - warn(i18n, `Trans: the passed in value is invalid - seems you passed in a null child.`); - } else if (isObject(child)) { + + // regular case mapping the inner children + const content = nodesToString(childChildren, i18nOptions, i18n, i18nKey); + stringNode += `<${childIndex}>${content}`; + + return; + } + + if (child === null) { + warn(i18n, `Trans: The passed in value is invalid - seems you passed in a null child.`, { + code: ERR_CODES.TRANS_NULL_VALUE, + i18nKey, + }); + return; + } + + if (isObject(child)) { // e.g. lorem {{ value, format }} ipsum const { format, ...clone } = child; const keys = Object.keys(clone); @@ -85,23 +102,33 @@ export const nodesToString = (children, i18nOptions, i18n, i18nKey) => { if (keys.length === 1) { const value = format ? `${keys[0]}, ${format}` : keys[0]; stringNode += `{{${value}}}`; - } else { - // not a valid interpolation object (can only contain one value plus format) - warn( - i18n, - `react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.`, - child, - i18nKey, - ); + return; } - } else { + + // not a valid interpolation object (can only contain one value plus format) warn( i18n, - `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}}.`, - child, - i18nKey, + `Trans: The passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.`, + { + code: ERR_CODES.TRANS_INVALID_OBJ, + i18nKey, + child, + }, ); + + return; } + + // e.g. lorem {number} ipsum + warn( + i18n, + `Trans: Passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.`, + { + code: ERR_CODES.TRANS_INVALID_VAR, + i18nKey, + child, + }, + ); }); return stringNode; @@ -336,7 +363,7 @@ const generateObjectComponents = (components, translation) => { return componentMap; }; -const generateComponents = (components, translation, i18n) => { +const generateComponents = (components, translation, i18n, i18nKey) => { if (!components) return null; // components could be either an array or an object @@ -351,7 +378,10 @@ const generateComponents = (components, translation, i18n) => { // if components is not an array or an object, warn the user // and return null - warnOnce(i18n, ' component prop expects an object or an array'); + warnOnce(i18n, ` "components" prop expects an object or an array`, { + i18nKey, + code: ERR_CODES.TRANS_INVALID_COMPONENTS, + }); return null; }; @@ -374,7 +404,14 @@ export function Trans({ const i18n = i18nFromProps || getI18n(); if (!i18n) { - warnOnce(i18n, 'You will need to pass in an i18next instance by using i18nextReactModule'); + warnOnce( + i18n, + 'Trans: You will need to pass in an i18next instance by using i18nextReactModule', + { + i18nKey, + code: ERR_CODES.NO_I18NEXT_INSTANCE, + }, + ); return children; } @@ -417,7 +454,7 @@ export function Trans({ }; const translation = key ? t(key, combinedTOpts) : defaultValue; - const generatedComponents = generateComponents(components, translation, i18n); + const generatedComponents = generateComponents(components, translation, i18n, i18nKey); const content = renderNodes( generatedComponents || children, diff --git a/src/useTranslation.js b/src/useTranslation.js index 8241e1c2..fcdf8320 100644 --- a/src/useTranslation.js +++ b/src/useTranslation.js @@ -7,6 +7,7 @@ import { hasLoadedNamespace, isString, isObject, + ERR_CODES, } from './utils.js'; const usePrevious = (value, ignore) => { @@ -35,7 +36,13 @@ export const useTranslation = (ns, props = {}) => { const i18n = i18nFromProps || i18nFromContext || getI18n(); if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces(); if (!i18n) { - warnOnce(i18n, 'You will need to pass in an i18next instance by using initReactI18next'); + warnOnce( + i18n, + 'useTranslation: You will need to pass in an i18next instance by using initReactI18next', + { + code: ERR_CODES.NO_I18NEXT_INSTANCE, + }, + ); const notReadyT = (k, optsOrDefaultValue) => { if (isString(optsOrDefaultValue)) return optsOrDefaultValue; if (isObject(optsOrDefaultValue) && isString(optsOrDefaultValue.defaultValue)) @@ -52,7 +59,10 @@ export const useTranslation = (ns, props = {}) => { if (i18n.options.react?.wait) warnOnce( i18n, - 'It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.', + 'useTranslation: It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.', + { + code: ERR_CODES.DEPRECATED_WAIT_OPTION, + }, ); const i18nOptions = { ...getDefaults(), ...i18n.options.react, ...props }; diff --git a/src/utils.js b/src/utils.js index 0c96bd8f..7e6e3d8e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -17,6 +17,16 @@ export const warnOnce = (i18n, ...args) => { warn(i18n, ...args); }; +export const ERR_CODES = { + NO_I18NEXT_INSTANCE: 'NO_I18NEXT_INSTANCE', + DEPRECATED_WAIT_OPTION: 'DEPRECATED_WAIT_OPTION', + TRANS_NULL_VALUE: 'TRANS_NULL_VALUE', + TRANS_INVALID_OBJ: 'TRANS_INVALID_OBJ', + TRANS_INVALID_VAR: 'TRANS_INVALID_VAR', + TRANS_INVALID_COMPONENTS: 'TRANS_INVALID_COMPONENTS', + MISSING_LANGUAGES: 'MISSING_LANGUAGES', +}; + // not needed right now // // export const deprecated = (i18n, ...args) => { @@ -60,7 +70,10 @@ export const loadLanguages = (i18n, lng, ns, cb) => { export const hasLoadedNamespace = (ns, i18n, options = {}) => { if (!i18n.languages || !i18n.languages.length) { - warnOnce(i18n, 'i18n.languages were undefined or empty', i18n.languages); + warnOnce(i18n, 'i18n.languages were undefined or empty', { + code: ERR_CODES.MISSING_LANGUAGES, + languages: i18n.languages, + }); return true; }