react-i18next
Version:
Internationalization for react done right. Using the i18next i18n ecosystem.
104 lines (99 loc) • 3.32 kB
JavaScript
import { useContext } from 'react';
import { IcuTransWithoutContext } from './IcuTransWithoutContext.js';
import { getI18n, I18nContext } from './context.js';
/**
* IcuTrans component for rendering ICU MessageFormat translations with React components
*
* This component provides a context-aware wrapper around IcuTransWithoutContext,
* automatically retrieving the i18n instance from React context when used within
* an I18nextProvider. It uses a declaration tree approach where components are
* defined as type + props blueprints, fetches the translated string, and reconstructs
* the React element tree by replacing numbered tags with actual components.
*
* Key features:
* - Supports React Context (I18nextProvider)
* - Falls back to global i18n instance
* - ICU MessageFormat compatible
* - Type-safe component declarations
* - Automatic HTML entity decoding
*
* @param {Object} props - Component props
* @param {string} props.i18nKey - The i18n key to look up the translation
* @param {string} props.defaultTranslation - The default translation in ICU format with numbered tags (e.g., "<0>Click here</0>")
* @param {Array<{type: string|React.ComponentType, props?: Object}>} props.content - Declaration tree describing React components and their props
* @param {string|string[]} [props.ns] - Optional namespace(s) for the translation
* @param {Object} [props.values] - Optional values for ICU variable interpolation
* @param {Object} [props.i18n] - Optional i18next instance (overrides context)
* @param {Function} [props.t] - Optional translation function (overrides context)
* @returns {React.ReactElement} React fragment containing the rendered translation
*
* @example
* ```jsx
* // Basic usage with context
* <I18nextProvider i18n={i18n}>
* <IcuTrans
* i18nKey="welcome.message"
* defaultTranslation="Welcome <0>friend</0>!"
* content={[
* { type: 'strong', props: { className: 'highlight' } }
* ]}
* />
* </I18nextProvider>
* ```
*
* @example
* ```jsx
* // With custom components and nested structure
* <IcuTrans
* i18nKey="docs.link"
* defaultTranslation="Read the <0>documentation <1></1></0> for more info"
* content={[
* { type: 'a', props: { href: '/docs' } },
* { type: Icon, props: { name: 'external' } }
* ]}
* />
* ```
*
* @example
* ```jsx
* // With nested children in declarations
* <IcuTrans
* i18nKey="list.items"
* defaultTranslation="<0><0>First item</0><1>Second item</1></0>"
* content={[
* {
* type: 'ul',
* props: {
* children: [
* { type: 'li', props: {} },
* { type: 'li', props: {} }
* ]
* }
* }
* ]}
* />
* ```
*/
export function IcuTrans({
i18nKey,
defaultTranslation,
content,
ns,
values = {},
i18n: i18nFromProps,
t: tFromProps,
}) {
const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext(I18nContext) || {};
const i18n = i18nFromProps || i18nFromContext || getI18n();
const t = tFromProps || i18n?.t.bind(i18n);
return IcuTransWithoutContext({
i18nKey,
defaultTranslation,
content,
ns: ns || t?.ns || defaultNSFromContext || i18n?.options?.defaultNS,
values,
i18n,
t: tFromProps,
});
}
IcuTrans.displayName = 'IcuTrans';