@octanejs/i18next 0.1.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/LICENSE +22 -0
- package/README.md +151 -0
- package/package.json +73 -0
- package/src/I18nextProvider.js +9 -0
- package/src/IcuTrans.js +104 -0
- package/src/IcuTransUtils/TranslationParserError.js +24 -0
- package/src/IcuTransUtils/htmlEntityDecoder.js +264 -0
- package/src/IcuTransUtils/index.js +4 -0
- package/src/IcuTransUtils/renderTranslation.js +216 -0
- package/src/IcuTransUtils/tokenizer.js +78 -0
- package/src/IcuTransWithoutContext.js +147 -0
- package/src/Trans.js +46 -0
- package/src/TransWithoutContext.d.ts +160 -0
- package/src/TransWithoutContext.js +687 -0
- package/src/Translation.js +15 -0
- package/src/context.js +61 -0
- package/src/defaults.js +21 -0
- package/src/helpers.d.ts +3 -0
- package/src/i18nInstance.js +7 -0
- package/src/index.d.ts +315 -0
- package/src/index.js +26 -0
- package/src/initReactI18next.d.ts +3 -0
- package/src/initReactI18next.js +11 -0
- package/src/internal.js +42 -0
- package/src/unescape.js +31 -0
- package/src/useSSR.js +55 -0
- package/src/useTranslation.js +285 -0
- package/src/utils.js +93 -0
- package/src/withSSR.js +22 -0
- package/src/withTranslation.js +38 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Ported from react-i18next@17.0.9 (8b4a9ea): declarations produce octane descriptors.
|
|
2
|
+
import { createElement, positionalChildren } from 'octane';
|
|
3
|
+
|
|
4
|
+
import { TranslationParserError } from './TranslationParserError.js';
|
|
5
|
+
import { tokenize } from './tokenizer.js';
|
|
6
|
+
import { decodeHtmlEntities } from './htmlEntityDecoder.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render a React element tree from a declaration node and its children
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} declaration - The component declaration (type + props)
|
|
12
|
+
* @param {Array<React.ReactNode>} children - Array of child nodes (text, numbers, React elements)
|
|
13
|
+
* @param {Array<Object>} [childDeclarations] - Optional array of child declarations to use for nested rendering
|
|
14
|
+
* @returns {React.ReactElement} A React element
|
|
15
|
+
*/
|
|
16
|
+
const renderDeclarationNode = (declaration, children, childDeclarations) => {
|
|
17
|
+
const { type, props = {} } = declaration;
|
|
18
|
+
|
|
19
|
+
// If props contain a children declaration AND we have childDeclarations to work with,
|
|
20
|
+
// we need to recursively render the content with those child declarations
|
|
21
|
+
if (props.children && Array.isArray(props.children) && childDeclarations) {
|
|
22
|
+
// The children array contains the parsed content from inside this tag
|
|
23
|
+
// We need to rebuild the translation string and re-parse it with child declarations
|
|
24
|
+
// For now, we'll use the children directly as they're already parsed
|
|
25
|
+
// This happens when renderTranslation is called recursively
|
|
26
|
+
|
|
27
|
+
// Remove children from props since we'll pass them as the third argument
|
|
28
|
+
// eslint-disable-next-line no-unused-vars
|
|
29
|
+
const { children: _childrenToRemove, ...propsWithoutChildren } = props;
|
|
30
|
+
|
|
31
|
+
return createElement(type, propsWithoutChildren, ...children);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Standard rendering with children from translation
|
|
35
|
+
if (children.length === 0) {
|
|
36
|
+
return createElement(type, props);
|
|
37
|
+
}
|
|
38
|
+
if (children.length === 1) {
|
|
39
|
+
return createElement(type, props, children[0]);
|
|
40
|
+
}
|
|
41
|
+
return createElement(type, props, ...children);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render translation string with declaration tree to create React elements
|
|
46
|
+
*
|
|
47
|
+
* This function parses an ICU format translation string and reconstructs
|
|
48
|
+
* a React element tree using the provided declaration tree. It replaces
|
|
49
|
+
* numbered tags (e.g., <0>, <1>) with the corresponding components from
|
|
50
|
+
* the declaration array and fills them with the translated text.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} translation - ICU format string (e.g., "<0>Click here</0>")
|
|
53
|
+
* @param {Array<Object>} [declarations=[]] - Array of component declarations matching tag numbers
|
|
54
|
+
* @returns {Array<React.ReactNode>} Array of React nodes (elements and text)
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```jsx
|
|
58
|
+
* const result = renderTranslation(
|
|
59
|
+
* "<0>bonjour</0> monde",
|
|
60
|
+
* [{ type: 'strong', props: { className: 'bold' } }]
|
|
61
|
+
* );
|
|
62
|
+
* // Returns: [<strong className="bold">bonjour</strong>, " monde"]
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```jsx
|
|
67
|
+
* // With nested children in declaration
|
|
68
|
+
* const result = renderTranslation(
|
|
69
|
+
* "<0>Click <1>here</1></0>",
|
|
70
|
+
* [
|
|
71
|
+
* {
|
|
72
|
+
* type: 'div',
|
|
73
|
+
* props: {
|
|
74
|
+
* children: [{ type: 'span', props: {} }]
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
* ]
|
|
78
|
+
* );
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export const renderTranslation = (translation, declarations = []) => {
|
|
82
|
+
if (!translation) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const tokens = tokenize(translation);
|
|
87
|
+
const result = [];
|
|
88
|
+
const stack = [];
|
|
89
|
+
|
|
90
|
+
// Track tag numbers that should be treated as literal text (no declaration found)
|
|
91
|
+
const literalTagNumbers = new Set();
|
|
92
|
+
|
|
93
|
+
// Helper to get the current declarations array based on context
|
|
94
|
+
const getCurrentDeclarations = () => {
|
|
95
|
+
if (stack.length === 0) {
|
|
96
|
+
return declarations;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const parentFrame = stack[stack.length - 1];
|
|
100
|
+
|
|
101
|
+
// If the parent declaration has children declarations, use those
|
|
102
|
+
if (
|
|
103
|
+
parentFrame.declaration.props?.children &&
|
|
104
|
+
Array.isArray(parentFrame.declaration.props.children)
|
|
105
|
+
) {
|
|
106
|
+
return parentFrame.declaration.props.children;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Otherwise, use the parent's declarations array
|
|
110
|
+
return parentFrame.declarations;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
tokens.forEach((token) => {
|
|
114
|
+
// eslint-disable-next-line default-case
|
|
115
|
+
switch (token.type) {
|
|
116
|
+
case 'Text':
|
|
117
|
+
{
|
|
118
|
+
const decoded = decodeHtmlEntities(token.value);
|
|
119
|
+
const targetArray = stack.length > 0 ? stack[stack.length - 1].children : result;
|
|
120
|
+
|
|
121
|
+
targetArray.push(decoded);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 'TagOpen':
|
|
127
|
+
{
|
|
128
|
+
const { tagNumber } = token;
|
|
129
|
+
const currentDeclarations = getCurrentDeclarations();
|
|
130
|
+
const declaration = currentDeclarations[tagNumber];
|
|
131
|
+
|
|
132
|
+
if (!declaration) {
|
|
133
|
+
// No declaration found - treat this tag as literal text
|
|
134
|
+
literalTagNumbers.add(tagNumber);
|
|
135
|
+
|
|
136
|
+
const literalText = `<${tagNumber}>`;
|
|
137
|
+
const targetArray = stack.length > 0 ? stack[stack.length - 1].children : result;
|
|
138
|
+
|
|
139
|
+
targetArray.push(literalText);
|
|
140
|
+
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
stack.push({
|
|
145
|
+
tagNumber,
|
|
146
|
+
children: [],
|
|
147
|
+
position: token.position,
|
|
148
|
+
declaration,
|
|
149
|
+
declarations: currentDeclarations,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
case 'TagClose':
|
|
156
|
+
{
|
|
157
|
+
const { tagNumber } = token;
|
|
158
|
+
|
|
159
|
+
// If this tag was treated as literal, output the closing tag as literal text
|
|
160
|
+
if (literalTagNumbers.has(tagNumber)) {
|
|
161
|
+
const literalText = `</${tagNumber}>`;
|
|
162
|
+
const literalTargetArray = stack.length > 0 ? stack[stack.length - 1].children : result;
|
|
163
|
+
|
|
164
|
+
literalTargetArray.push(literalText);
|
|
165
|
+
|
|
166
|
+
literalTagNumbers.delete(tagNumber);
|
|
167
|
+
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (stack.length === 0) {
|
|
172
|
+
throw new TranslationParserError(
|
|
173
|
+
`Unexpected closing tag </${tagNumber}> at position ${token.position}`,
|
|
174
|
+
token.position,
|
|
175
|
+
translation,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const frame = stack.pop();
|
|
180
|
+
|
|
181
|
+
if (frame.tagNumber !== tagNumber) {
|
|
182
|
+
throw new TranslationParserError(
|
|
183
|
+
`Mismatched tags: expected </${frame.tagNumber}> but got </${tagNumber}> at position ${token.position}`,
|
|
184
|
+
token.position,
|
|
185
|
+
translation,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Render the element using the declaration and collected children
|
|
190
|
+
const element = renderDeclarationNode(
|
|
191
|
+
frame.declaration,
|
|
192
|
+
frame.children,
|
|
193
|
+
frame.declarations,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const elementTargetArray = stack.length > 0 ? stack[stack.length - 1].children : result;
|
|
197
|
+
|
|
198
|
+
elementTargetArray.push(element);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (stack.length > 0) {
|
|
206
|
+
const unclosed = stack[stack.length - 1];
|
|
207
|
+
|
|
208
|
+
throw new TranslationParserError(
|
|
209
|
+
`Unclosed tag <${unclosed.tagNumber}> at position ${unclosed.position}`,
|
|
210
|
+
unclosed.position,
|
|
211
|
+
translation,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return positionalChildren(result);
|
|
216
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokenize a translation string with numbered tags
|
|
3
|
+
* Note: Variables are already interpolated by the i18n system before we receive the string
|
|
4
|
+
*
|
|
5
|
+
* @param {string} translation - Translation string with numbered tags
|
|
6
|
+
* @returns {Array<Token>} Array of tokens
|
|
7
|
+
*/
|
|
8
|
+
export const tokenize = (translation) => {
|
|
9
|
+
const tokens = [];
|
|
10
|
+
|
|
11
|
+
let position = 0;
|
|
12
|
+
|
|
13
|
+
let currentText = '';
|
|
14
|
+
|
|
15
|
+
const flushText = () => {
|
|
16
|
+
if (currentText) {
|
|
17
|
+
tokens.push({
|
|
18
|
+
type: 'Text',
|
|
19
|
+
value: currentText,
|
|
20
|
+
position: position - currentText.length,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
currentText = '';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
while (position < translation.length) {
|
|
28
|
+
const char = translation[position];
|
|
29
|
+
|
|
30
|
+
// Check for opening tag: <0>, <1>, etc.
|
|
31
|
+
if (char === '<') {
|
|
32
|
+
const tagMatch = translation.slice(position).match(/^<(\d+)>/);
|
|
33
|
+
|
|
34
|
+
if (tagMatch) {
|
|
35
|
+
flushText();
|
|
36
|
+
|
|
37
|
+
tokens.push({
|
|
38
|
+
type: 'TagOpen',
|
|
39
|
+
value: tagMatch[0],
|
|
40
|
+
position,
|
|
41
|
+
tagNumber: parseInt(tagMatch[1], 10),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
position += tagMatch[0].length;
|
|
45
|
+
} else {
|
|
46
|
+
// Check for closing tag: </0>, </1>, etc.
|
|
47
|
+
const closeTagMatch = translation.slice(position).match(/^<\/(\d+)>/);
|
|
48
|
+
|
|
49
|
+
if (closeTagMatch) {
|
|
50
|
+
flushText();
|
|
51
|
+
|
|
52
|
+
tokens.push({
|
|
53
|
+
type: 'TagClose',
|
|
54
|
+
value: closeTagMatch[0],
|
|
55
|
+
position,
|
|
56
|
+
tagNumber: parseInt(closeTagMatch[1], 10),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
position += closeTagMatch[0].length;
|
|
60
|
+
} else {
|
|
61
|
+
// Regular text (including any { } characters that aren't our tags)
|
|
62
|
+
currentText += char;
|
|
63
|
+
|
|
64
|
+
position += 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
// Regular text (including any { } characters that aren't our tags)
|
|
69
|
+
currentText += char;
|
|
70
|
+
|
|
71
|
+
position += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
flushText();
|
|
76
|
+
|
|
77
|
+
return tokens;
|
|
78
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Ported from react-i18next@17.0.9 (8b4a9ea): dynamic nodes are octane descriptors.
|
|
2
|
+
import { warn, warnOnce, isString } from './utils.js';
|
|
3
|
+
import { getI18n } from './i18nInstance.js';
|
|
4
|
+
import { renderTranslation } from './IcuTransUtils/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* IcuTrans component for rendering ICU MessageFormat translations (without React Context)
|
|
8
|
+
*
|
|
9
|
+
* This is the core implementation without React hooks or context dependencies,
|
|
10
|
+
* making it suitable for use in any environment. It uses a declaration tree
|
|
11
|
+
* approach where components are defined as type + props blueprints, fetches
|
|
12
|
+
* the translated string via i18next, and reconstructs the React element tree
|
|
13
|
+
* by replacing numbered tags (<0>, <1>) with actual components.
|
|
14
|
+
*
|
|
15
|
+
* Key features:
|
|
16
|
+
* - No React hooks or context (can be used anywhere)
|
|
17
|
+
* - ICU MessageFormat compatible
|
|
18
|
+
* - Supports nested component declarations
|
|
19
|
+
* - Automatic HTML entity decoding
|
|
20
|
+
* - Graceful error handling with fallbacks
|
|
21
|
+
* - Merges default interpolation variables
|
|
22
|
+
*
|
|
23
|
+
* Note: Users should typically use the IcuTrans export which provides automatic
|
|
24
|
+
* context support. This component is exposed for advanced use cases where direct
|
|
25
|
+
* i18n instance control is needed, or for use outside of React Context.
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} props - Component props
|
|
28
|
+
* @param {string} props.i18nKey - The i18n key to look up the translation
|
|
29
|
+
* @param {string} props.defaultTranslation - The default translation in ICU format with numbered tags (e.g., "<0>Click here</0>")
|
|
30
|
+
* @param {Array<{type: string|React.ComponentType, props?: Object}>} props.content - Declaration tree describing React components and their props
|
|
31
|
+
* @param {string|string[]} [props.ns] - Optional namespace(s) for the translation. Falls back to t.ns, then i18n.options.defaultNS, then 'translation'
|
|
32
|
+
* @param {Object} [props.values={}] - Optional values for ICU variable interpolation (merged with i18n.options.interpolation.defaultVariables if present)
|
|
33
|
+
* @param {Object} [props.i18n] - i18next instance. If not provided, uses global instance from getI18n()
|
|
34
|
+
* @param {Function} [props.t] - Custom translation function. If not provided, uses i18n.t.bind(i18n)
|
|
35
|
+
* @returns {React.ReactElement} React fragment containing the rendered translation
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```jsx
|
|
39
|
+
* // Direct usage with i18n instance
|
|
40
|
+
* <IcuTransWithoutContext
|
|
41
|
+
* i18nKey="welcome.message"
|
|
42
|
+
* defaultTranslation="Welcome <0>back</0>!"
|
|
43
|
+
* content={[
|
|
44
|
+
* { type: 'strong', props: { className: 'highlight' } }
|
|
45
|
+
* ]}
|
|
46
|
+
* i18n={i18nInstance}
|
|
47
|
+
* />
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```jsx
|
|
52
|
+
* // With nested declarations for list rendering
|
|
53
|
+
* <IcuTransWithoutContext
|
|
54
|
+
* i18nKey="features.list"
|
|
55
|
+
* defaultTranslation="Features: <0><0>Fast</0><1>Reliable</1><2>Secure</2></0>"
|
|
56
|
+
* content={[
|
|
57
|
+
* {
|
|
58
|
+
* type: 'ul',
|
|
59
|
+
* props: {
|
|
60
|
+
* children: [
|
|
61
|
+
* { type: 'li', props: {} },
|
|
62
|
+
* { type: 'li', props: {} },
|
|
63
|
+
* { type: 'li', props: {} }
|
|
64
|
+
* ]
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* ]}
|
|
68
|
+
* i18n={i18nInstance}
|
|
69
|
+
* />
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```jsx
|
|
74
|
+
* // With values for ICU variable interpolation
|
|
75
|
+
* <IcuTransWithoutContext
|
|
76
|
+
* i18nKey="greeting"
|
|
77
|
+
* defaultTranslation="Hello <0>{name}</0>!"
|
|
78
|
+
* content={[{ type: 'strong', props: {} }]}
|
|
79
|
+
* values={{ name: 'Alice' }}
|
|
80
|
+
* i18n={i18nInstance}
|
|
81
|
+
* />
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function IcuTransWithoutContext({
|
|
85
|
+
i18nKey,
|
|
86
|
+
defaultTranslation,
|
|
87
|
+
content,
|
|
88
|
+
ns,
|
|
89
|
+
values = {},
|
|
90
|
+
i18n: i18nFromProps,
|
|
91
|
+
t: tFromProps,
|
|
92
|
+
}) {
|
|
93
|
+
const i18n = i18nFromProps || getI18n();
|
|
94
|
+
|
|
95
|
+
if (!i18n) {
|
|
96
|
+
warnOnce(
|
|
97
|
+
i18n,
|
|
98
|
+
'NO_I18NEXT_INSTANCE',
|
|
99
|
+
`IcuTrans: You need to pass in an i18next instance using i18nextReactModule`,
|
|
100
|
+
{ i18nKey },
|
|
101
|
+
);
|
|
102
|
+
return defaultTranslation;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const t = tFromProps || i18n.t?.bind(i18n) || ((k) => k);
|
|
106
|
+
|
|
107
|
+
// prepare having a namespace
|
|
108
|
+
let namespaces = ns || t.ns || i18n.options?.defaultNS;
|
|
109
|
+
namespaces = isString(namespaces) ? [namespaces] : namespaces || ['translation'];
|
|
110
|
+
|
|
111
|
+
// Merge default interpolation variables if they exist
|
|
112
|
+
let mergedValues = values;
|
|
113
|
+
if (i18n.options?.interpolation?.defaultVariables) {
|
|
114
|
+
mergedValues =
|
|
115
|
+
values && Object.keys(values).length > 0
|
|
116
|
+
? { ...values, ...i18n.options.interpolation.defaultVariables }
|
|
117
|
+
: { ...i18n.options.interpolation.defaultVariables };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Get the translation, falling back to defaultTranslation
|
|
121
|
+
const translation = t(i18nKey, {
|
|
122
|
+
defaultValue: defaultTranslation,
|
|
123
|
+
...mergedValues,
|
|
124
|
+
ns: namespaces,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Render the translation with the declaration tree
|
|
128
|
+
try {
|
|
129
|
+
const rendered = renderTranslation(translation, content);
|
|
130
|
+
|
|
131
|
+
// Octane components may return an array directly; Fragment is a
|
|
132
|
+
// compiler sentinel rather than a runtime component constructor.
|
|
133
|
+
return rendered;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// If rendering fails, warn and fall back to the translation string
|
|
136
|
+
warn(
|
|
137
|
+
i18n,
|
|
138
|
+
'ICU_TRANS_RENDER_ERROR',
|
|
139
|
+
`IcuTrans component error for key "${i18nKey}": ${error.message}`,
|
|
140
|
+
{ i18nKey, error },
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return translation;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
IcuTransWithoutContext.displayName = 'IcuTransWithoutContext';
|
package/src/Trans.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Ported from react-i18next@17.0.9 (8b4a9ea): React context -> octane context.
|
|
2
|
+
import { useContext } from 'octane';
|
|
3
|
+
import { nodesToString, Trans as TransWithoutContext } from './TransWithoutContext.js';
|
|
4
|
+
import { getI18n, I18nContext } from './context.js';
|
|
5
|
+
|
|
6
|
+
export { nodesToString };
|
|
7
|
+
|
|
8
|
+
export function Trans({
|
|
9
|
+
children,
|
|
10
|
+
count,
|
|
11
|
+
parent,
|
|
12
|
+
i18nKey,
|
|
13
|
+
context,
|
|
14
|
+
tOptions = {},
|
|
15
|
+
values,
|
|
16
|
+
defaults,
|
|
17
|
+
components,
|
|
18
|
+
ns,
|
|
19
|
+
i18n: i18nFromProps,
|
|
20
|
+
t: tFromProps,
|
|
21
|
+
shouldUnescape,
|
|
22
|
+
...additionalProps
|
|
23
|
+
}) {
|
|
24
|
+
const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext(I18nContext) || {};
|
|
25
|
+
const i18n = i18nFromProps || i18nFromContext || getI18n();
|
|
26
|
+
|
|
27
|
+
const t = tFromProps || i18n?.t.bind(i18n);
|
|
28
|
+
|
|
29
|
+
return TransWithoutContext({
|
|
30
|
+
children,
|
|
31
|
+
count,
|
|
32
|
+
parent,
|
|
33
|
+
i18nKey,
|
|
34
|
+
context,
|
|
35
|
+
tOptions,
|
|
36
|
+
values,
|
|
37
|
+
defaults,
|
|
38
|
+
components,
|
|
39
|
+
// prepare having a namespace
|
|
40
|
+
ns: ns || t?.ns || defaultNSFromContext || i18n?.options?.defaultNS,
|
|
41
|
+
i18n,
|
|
42
|
+
t: tFromProps,
|
|
43
|
+
shouldUnescape,
|
|
44
|
+
...additionalProps,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
i18n,
|
|
3
|
+
ReactOptions,
|
|
4
|
+
ApplyTarget,
|
|
5
|
+
ConstrainTarget,
|
|
6
|
+
GetSource,
|
|
7
|
+
InterpolationMap,
|
|
8
|
+
ParseKeys,
|
|
9
|
+
Namespace,
|
|
10
|
+
SelectorFn,
|
|
11
|
+
SelectorKey,
|
|
12
|
+
TFunctionReturn,
|
|
13
|
+
TypeOptions,
|
|
14
|
+
TOptions,
|
|
15
|
+
TFunction,
|
|
16
|
+
} from 'i18next';
|
|
17
|
+
import type { ComponentBody, ElementDescriptor } from 'octane';
|
|
18
|
+
|
|
19
|
+
type _DefaultNamespace = TypeOptions['defaultNS'];
|
|
20
|
+
type _EnableSelector = TypeOptions['enableSelector'];
|
|
21
|
+
type _KeySeparator = TypeOptions['keySeparator'];
|
|
22
|
+
type _AppendKeyPrefix<Key, KPrefix> = KPrefix extends string
|
|
23
|
+
? `${KPrefix}${_KeySeparator}${Key & string}`
|
|
24
|
+
: Key;
|
|
25
|
+
|
|
26
|
+
type TransChild = unknown;
|
|
27
|
+
type $NoInfer<T> = [T][T extends T ? 0 : never];
|
|
28
|
+
|
|
29
|
+
export type TransProps<
|
|
30
|
+
Key extends ParseKeys<Ns, TOpt, KPrefix>,
|
|
31
|
+
Ns extends Namespace = _DefaultNamespace,
|
|
32
|
+
KPrefix = undefined,
|
|
33
|
+
TContext extends string | undefined = undefined,
|
|
34
|
+
TOpt extends TOptions & { context?: TContext } = { context: TContext },
|
|
35
|
+
Ret = TFunctionReturn<Ns, _AppendKeyPrefix<Key, KPrefix>, TOpt>,
|
|
36
|
+
E = Record<string, unknown>,
|
|
37
|
+
> = E & {
|
|
38
|
+
children?: TransChild | readonly TransChild[];
|
|
39
|
+
components?: readonly ElementDescriptor[] | { readonly [tagName: string]: ElementDescriptor };
|
|
40
|
+
count?: number;
|
|
41
|
+
context?: TContext;
|
|
42
|
+
defaults?: string;
|
|
43
|
+
i18n?: i18n;
|
|
44
|
+
i18nKey?: Key | Key[];
|
|
45
|
+
// allow a single namespace from an array-typed `t` (e.g. useTranslation(['ns'])); TS7 intersects
|
|
46
|
+
// inference candidates from `t` and `ns`, so a bare `Ns` here rejects ns="ns" when t is passed
|
|
47
|
+
ns?: Ns | (Ns extends readonly (infer S extends string)[] ? S : never);
|
|
48
|
+
parent?: string | ComponentBody<any> | null;
|
|
49
|
+
tOptions?: TOpt;
|
|
50
|
+
values?: InterpolationMap<Ret>;
|
|
51
|
+
shouldUnescape?: boolean;
|
|
52
|
+
t?: TFunction<Ns, KPrefix>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export interface TransLegacy {
|
|
56
|
+
<
|
|
57
|
+
const Key extends ParseKeys<Ns, TOpt, KPrefix>,
|
|
58
|
+
Ns extends Namespace = _DefaultNamespace,
|
|
59
|
+
KPrefix = undefined,
|
|
60
|
+
TContext extends string | undefined = undefined,
|
|
61
|
+
TOpt extends TOptions & { context?: TContext } = { context: TContext },
|
|
62
|
+
Ret extends TFunctionReturn<Ns, _AppendKeyPrefix<Key, KPrefix>, TOpt> = TFunctionReturn<
|
|
63
|
+
Ns,
|
|
64
|
+
_AppendKeyPrefix<Key, KPrefix>,
|
|
65
|
+
TOpt
|
|
66
|
+
>,
|
|
67
|
+
E = Record<string, unknown>,
|
|
68
|
+
>(
|
|
69
|
+
props: TransProps<Key, Ns, KPrefix, TContext, TOpt, Ret, E>,
|
|
70
|
+
): unknown;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface TransSelectorProps<
|
|
74
|
+
Key,
|
|
75
|
+
Ns extends Namespace = _DefaultNamespace,
|
|
76
|
+
KPrefix = undefined,
|
|
77
|
+
TContext extends string | undefined = undefined,
|
|
78
|
+
TOpt extends TOptions & { context?: TContext } = { context: TContext },
|
|
79
|
+
> {
|
|
80
|
+
children?: TransChild | readonly TransChild[];
|
|
81
|
+
components?: readonly ElementDescriptor[] | { readonly [tagName: string]: ElementDescriptor };
|
|
82
|
+
count?: number;
|
|
83
|
+
context?: TContext;
|
|
84
|
+
defaults?: string | Key;
|
|
85
|
+
i18n?: i18n;
|
|
86
|
+
i18nKey?: Key | readonly Key[];
|
|
87
|
+
// see TransProps.ns: keep single-namespace values assignable when `t` fixes Ns to an array
|
|
88
|
+
ns?: Ns | (Ns extends readonly (infer S extends string)[] ? S : never);
|
|
89
|
+
parent?: string | ComponentBody<any> | null;
|
|
90
|
+
tOptions?: TOpt;
|
|
91
|
+
values?: Key extends (...args: any[]) => infer R ? InterpolationMap<R> : {};
|
|
92
|
+
shouldUnescape?: boolean;
|
|
93
|
+
t?: TFunction<Ns, KPrefix>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface TransSelector {
|
|
97
|
+
<
|
|
98
|
+
Target extends ConstrainTarget<TOpt>,
|
|
99
|
+
Key extends
|
|
100
|
+
| SelectorFn<GetSource<$NoInfer<Ns>, KPrefix>, ApplyTarget<Target, TOpt>, TOpt>
|
|
101
|
+
| SelectorKey,
|
|
102
|
+
const Ns extends Namespace = _DefaultNamespace,
|
|
103
|
+
KPrefix = undefined,
|
|
104
|
+
TContext extends string | undefined = undefined,
|
|
105
|
+
TOpt extends TOptions & { context?: TContext } = { context: TContext },
|
|
106
|
+
E = Record<string, unknown>,
|
|
107
|
+
>(
|
|
108
|
+
props: TransSelectorProps<Key, Ns, KPrefix, TContext, TOpt> & E,
|
|
109
|
+
): unknown;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const Trans: _EnableSelector extends true | 'optimize' | 'strict'
|
|
113
|
+
? TransSelector
|
|
114
|
+
: TransLegacy;
|
|
115
|
+
|
|
116
|
+
export function nodesToString(
|
|
117
|
+
children: unknown,
|
|
118
|
+
i18nOptions?: ReactOptions,
|
|
119
|
+
i18n?: i18n,
|
|
120
|
+
i18nKey?: string,
|
|
121
|
+
): string;
|
|
122
|
+
|
|
123
|
+
export type ErrorCode =
|
|
124
|
+
| 'NO_I18NEXT_INSTANCE'
|
|
125
|
+
| 'NO_LANGUAGES'
|
|
126
|
+
| 'DEPRECATED_OPTION'
|
|
127
|
+
| 'TRANS_NULL_VALUE'
|
|
128
|
+
| 'TRANS_INVALID_OBJ'
|
|
129
|
+
| 'TRANS_INVALID_VAR'
|
|
130
|
+
| 'TRANS_INVALID_COMPONENTS'
|
|
131
|
+
| 'USE_T_BEFORE_READY'
|
|
132
|
+
| 'OCTANE_TRANS_BLOCK_CHILDREN'
|
|
133
|
+
| 'ICU_TRANS_RENDER_ERROR';
|
|
134
|
+
|
|
135
|
+
export type ErrorMeta = {
|
|
136
|
+
code: ErrorCode;
|
|
137
|
+
i18nKey?: string;
|
|
138
|
+
[x: string]: any;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Use to type the logger arguments
|
|
143
|
+
* @example
|
|
144
|
+
* ```
|
|
145
|
+
* import type { ErrorArgs } from 'react-i18next';
|
|
146
|
+
*
|
|
147
|
+
* const logger = {
|
|
148
|
+
* // ....
|
|
149
|
+
* warn: function (...args: ErrorArgs) {
|
|
150
|
+
* if (args[1]?.code === 'TRANS_INVALID_OBJ') {
|
|
151
|
+
* const [msg, { i18nKey, ...rest }] = args;
|
|
152
|
+
* return log(i18nKey, msg, rest);
|
|
153
|
+
* }
|
|
154
|
+
* log(...args);
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
* i18n.use(logger).use(i18nReactPlugin).init({...});
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export type ErrorArgs = readonly [string, ErrorMeta | undefined, ...any[]];
|