@intlayer/core 7.4.0 → 7.5.0-canary.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/dist/cjs/localization/getLocale.cjs +3 -4
- package/dist/cjs/localization/getLocale.cjs.map +1 -1
- package/dist/cjs/localization/getLocalizedUrl.cjs +2 -1
- package/dist/cjs/localization/getLocalizedUrl.cjs.map +1 -1
- package/dist/cjs/messageFormat/ICU.cjs +309 -0
- package/dist/cjs/messageFormat/ICU.cjs.map +1 -0
- package/dist/cjs/messageFormat/i18next.cjs +320 -0
- package/dist/cjs/messageFormat/i18next.cjs.map +1 -0
- package/dist/cjs/messageFormat/index.cjs +10 -0
- package/dist/cjs/messageFormat/vue-i18n.cjs +166 -0
- package/dist/cjs/messageFormat/vue-i18n.cjs.map +1 -0
- package/dist/esm/localization/getLocale.mjs +1 -2
- package/dist/esm/localization/getLocale.mjs.map +1 -1
- package/dist/esm/localization/getLocalizedUrl.mjs +2 -1
- package/dist/esm/localization/getLocalizedUrl.mjs.map +1 -1
- package/dist/esm/messageFormat/ICU.mjs +307 -0
- package/dist/esm/messageFormat/ICU.mjs.map +1 -0
- package/dist/esm/messageFormat/i18next.mjs +318 -0
- package/dist/esm/messageFormat/i18next.mjs.map +1 -0
- package/dist/esm/messageFormat/index.mjs +5 -0
- package/dist/esm/messageFormat/vue-i18n.mjs +164 -0
- package/dist/esm/messageFormat/vue-i18n.mjs.map +1 -0
- package/dist/types/deepTransformPlugins/getFilterMissingTranslationsContent.d.ts +8 -7
- package/dist/types/deepTransformPlugins/getFilterMissingTranslationsContent.d.ts.map +1 -1
- package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts +8 -7
- package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts.map +1 -1
- package/dist/types/deepTransformPlugins/getFilteredLocalesContent.d.ts +8 -7
- package/dist/types/deepTransformPlugins/getFilteredLocalesContent.d.ts.map +1 -1
- package/dist/types/dictionaryManipulator/orderDictionaries.d.ts +2 -2
- package/dist/types/dictionaryManipulator/orderDictionaries.d.ts.map +1 -1
- package/dist/types/interpreter/getContent/plugins.d.ts.map +1 -1
- package/dist/types/messageFormat/ICU.d.ts +11 -0
- package/dist/types/messageFormat/ICU.d.ts.map +1 -0
- package/dist/types/messageFormat/i18next.d.ts +9 -0
- package/dist/types/messageFormat/i18next.d.ts.map +1 -0
- package/dist/types/messageFormat/index.d.ts +4 -0
- package/dist/types/messageFormat/vue-i18n.d.ts +9 -0
- package/dist/types/messageFormat/vue-i18n.d.ts.map +1 -0
- package/dist/types/transpiler/translation/translation.d.ts +1 -1
- package/dist/types/transpiler/translation/translation.d.ts.map +1 -1
- package/package.json +11 -6
- package/dist/cjs/dist/esm/getStorageAttributes.cjs +0 -134
- package/dist/cjs/dist/esm/getStorageAttributes.cjs.map +0 -1
- package/dist/cjs/dist/esm/localization/localeResolver.cjs +0 -28
- package/dist/cjs/dist/esm/localization/localeResolver.cjs.map +0 -1
- package/dist/cjs/dist/esm/utils/getCookie.cjs +0 -32
- package/dist/cjs/dist/esm/utils/getCookie.cjs.map +0 -1
- package/dist/cjs/dist/esm/utils/localeStorage.cjs +0 -61
- package/dist/cjs/dist/esm/utils/localeStorage.cjs.map +0 -1
- package/dist/cjs/localization/isValidLocale.cjs +0 -54
- package/dist/cjs/localization/isValidLocale.cjs.map +0 -1
- package/dist/esm/dist/esm/getStorageAttributes.mjs +0 -133
- package/dist/esm/dist/esm/getStorageAttributes.mjs.map +0 -1
- package/dist/esm/dist/esm/localization/localeResolver.mjs +0 -26
- package/dist/esm/dist/esm/localization/localeResolver.mjs.map +0 -1
- package/dist/esm/dist/esm/utils/getCookie.mjs +0 -31
- package/dist/esm/dist/esm/utils/getCookie.mjs.map +0 -1
- package/dist/esm/dist/esm/utils/localeStorage.mjs +0 -59
- package/dist/esm/dist/esm/utils/localeStorage.mjs.map +0 -1
- package/dist/esm/localization/isValidLocale.mjs +0 -52
- package/dist/esm/localization/isValidLocale.mjs.map +0 -1
- package/dist/types/localization/isValidLocale.d.ts +0 -35
- package/dist/types/localization/isValidLocale.d.ts.map +0 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { deepTransformNode } from "../interpreter/getContent/deepTransform.mjs";
|
|
2
|
+
import { enu as enumeration } from "../transpiler/enumeration/enumeration.mjs";
|
|
3
|
+
import { gender } from "../transpiler/gender/gender.mjs";
|
|
4
|
+
import { insert as insertion } from "../transpiler/insertion/insertion.mjs";
|
|
5
|
+
import { NodeType } from "@intlayer/types";
|
|
6
|
+
|
|
7
|
+
//#region src/messageFormat/ICU.ts
|
|
8
|
+
const parseICU = (text) => {
|
|
9
|
+
let index = 0;
|
|
10
|
+
const parseNodes = () => {
|
|
11
|
+
const nodes = [];
|
|
12
|
+
let currentText = "";
|
|
13
|
+
while (index < text.length) {
|
|
14
|
+
const char = text[index];
|
|
15
|
+
if (char === "{") {
|
|
16
|
+
if (currentText) {
|
|
17
|
+
nodes.push(currentText);
|
|
18
|
+
currentText = "";
|
|
19
|
+
}
|
|
20
|
+
index++;
|
|
21
|
+
nodes.push(parseArgument());
|
|
22
|
+
} else if (char === "}") break;
|
|
23
|
+
else if (char === "'") if (index + 1 < text.length && text[index + 1] === "'") {
|
|
24
|
+
currentText += "'";
|
|
25
|
+
index += 2;
|
|
26
|
+
} else {
|
|
27
|
+
const nextQuote = text.indexOf("'", index + 1);
|
|
28
|
+
if (nextQuote !== -1) {
|
|
29
|
+
currentText += text.substring(index + 1, nextQuote);
|
|
30
|
+
index = nextQuote + 1;
|
|
31
|
+
} else {
|
|
32
|
+
currentText += "'";
|
|
33
|
+
index++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
currentText += char;
|
|
38
|
+
index++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (currentText) nodes.push(currentText);
|
|
42
|
+
return nodes;
|
|
43
|
+
};
|
|
44
|
+
const parseArgument = () => {
|
|
45
|
+
let name = "";
|
|
46
|
+
while (index < text.length && /[^,}]/.test(text[index])) {
|
|
47
|
+
name += text[index];
|
|
48
|
+
index++;
|
|
49
|
+
}
|
|
50
|
+
name = name.trim();
|
|
51
|
+
if (index >= text.length) throw new Error("Unclosed argument");
|
|
52
|
+
if (text[index] === "}") {
|
|
53
|
+
index++;
|
|
54
|
+
return {
|
|
55
|
+
type: "argument",
|
|
56
|
+
name
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (text[index] === ",") {
|
|
60
|
+
index++;
|
|
61
|
+
let type = "";
|
|
62
|
+
while (index < text.length && /[^,}]/.test(text[index])) {
|
|
63
|
+
type += text[index];
|
|
64
|
+
index++;
|
|
65
|
+
}
|
|
66
|
+
type = type.trim();
|
|
67
|
+
if (index >= text.length) throw new Error("Unclosed argument");
|
|
68
|
+
if (text[index] === "}") {
|
|
69
|
+
index++;
|
|
70
|
+
return {
|
|
71
|
+
type: "argument",
|
|
72
|
+
name,
|
|
73
|
+
format: { type }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (text[index] === ",") {
|
|
77
|
+
index++;
|
|
78
|
+
if (type === "plural" || type === "select") {
|
|
79
|
+
const options = {};
|
|
80
|
+
while (index < text.length && text[index] !== "}") {
|
|
81
|
+
while (index < text.length && /\s/.test(text[index])) index++;
|
|
82
|
+
let key = "";
|
|
83
|
+
while (index < text.length && /[^{\s]/.test(text[index])) {
|
|
84
|
+
key += text[index];
|
|
85
|
+
index++;
|
|
86
|
+
}
|
|
87
|
+
while (index < text.length && /\s/.test(text[index])) index++;
|
|
88
|
+
if (text[index] !== "{") throw new Error("Expected { after option key");
|
|
89
|
+
index++;
|
|
90
|
+
const value = parseNodes();
|
|
91
|
+
if (text[index] !== "}") throw new Error("Expected } after option value");
|
|
92
|
+
index++;
|
|
93
|
+
options[key] = value;
|
|
94
|
+
while (index < text.length && /\s/.test(text[index])) index++;
|
|
95
|
+
}
|
|
96
|
+
index++;
|
|
97
|
+
if (type === "plural") return {
|
|
98
|
+
type: "plural",
|
|
99
|
+
name,
|
|
100
|
+
options
|
|
101
|
+
};
|
|
102
|
+
else if (type === "select") return {
|
|
103
|
+
type: "select",
|
|
104
|
+
name,
|
|
105
|
+
options
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
let style = "";
|
|
109
|
+
while (index < text.length && text[index] !== "}") {
|
|
110
|
+
style += text[index];
|
|
111
|
+
index++;
|
|
112
|
+
}
|
|
113
|
+
if (index >= text.length) throw new Error("Unclosed argument");
|
|
114
|
+
style = style.trim();
|
|
115
|
+
index++;
|
|
116
|
+
return {
|
|
117
|
+
type: "argument",
|
|
118
|
+
name,
|
|
119
|
+
format: {
|
|
120
|
+
type,
|
|
121
|
+
style
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw new Error("Malformed argument");
|
|
128
|
+
};
|
|
129
|
+
return parseNodes();
|
|
130
|
+
};
|
|
131
|
+
const icuNodesToIntlayer = (nodes) => {
|
|
132
|
+
if (nodes.length === 0) return "";
|
|
133
|
+
if (nodes.length === 1 && typeof nodes[0] === "string") return nodes[0];
|
|
134
|
+
if (nodes.every((node) => typeof node === "string" || node.type === "argument")) {
|
|
135
|
+
let str = "";
|
|
136
|
+
for (const node of nodes) if (typeof node === "string") str += node;
|
|
137
|
+
else if (typeof node !== "string" && node.type === "argument") if (node.format) str += `{${node.name}, ${node.format.type}${node.format.style ? `, ${node.format.style}` : ""}}`;
|
|
138
|
+
else str += `{{${node.name}}}`;
|
|
139
|
+
return insertion(str);
|
|
140
|
+
}
|
|
141
|
+
if (nodes.length === 1) {
|
|
142
|
+
const node = nodes[0];
|
|
143
|
+
if (typeof node === "string") return node;
|
|
144
|
+
if (node.type === "argument") {
|
|
145
|
+
if (node.format) return insertion(`{${node.name}, ${node.format.type}${node.format.style ? `, ${node.format.style}` : ""}}`);
|
|
146
|
+
return insertion(`{{${node.name}}}`);
|
|
147
|
+
}
|
|
148
|
+
if (node.type === "plural") {
|
|
149
|
+
const options = {};
|
|
150
|
+
for (const [key, val] of Object.entries(node.options)) {
|
|
151
|
+
let newKey = key;
|
|
152
|
+
if (key.startsWith("=")) newKey = key.substring(1);
|
|
153
|
+
else if (key === "one") newKey = "1";
|
|
154
|
+
else if (key === "two") newKey = "2";
|
|
155
|
+
else if (key === "few") newKey = "<=3";
|
|
156
|
+
else if (key === "many") newKey = ">=4";
|
|
157
|
+
else if (key === "other") newKey = "fallback";
|
|
158
|
+
options[newKey] = icuNodesToIntlayer(val.map((v) => {
|
|
159
|
+
if (typeof v === "string") return v.replace(/#/g, `{{${node.name}}}`);
|
|
160
|
+
return v;
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
options.__intlayer_icu_var = node.name;
|
|
164
|
+
return enumeration(options);
|
|
165
|
+
}
|
|
166
|
+
if (node.type === "select") {
|
|
167
|
+
const options = {};
|
|
168
|
+
for (const [key, val] of Object.entries(node.options)) options[key] = icuNodesToIntlayer(val);
|
|
169
|
+
const optionKeys = Object.keys(options);
|
|
170
|
+
if ((options.male || options.female) && optionKeys.every((k) => [
|
|
171
|
+
"male",
|
|
172
|
+
"female",
|
|
173
|
+
"other",
|
|
174
|
+
"fallback"
|
|
175
|
+
].includes(k))) return gender({
|
|
176
|
+
fallback: options.other,
|
|
177
|
+
male: options.male,
|
|
178
|
+
female: options.female
|
|
179
|
+
});
|
|
180
|
+
options.__intlayer_icu_var = node.name;
|
|
181
|
+
return enumeration(options);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return nodes.map((node) => icuNodesToIntlayer([node]));
|
|
185
|
+
};
|
|
186
|
+
const icuToIntlayerPlugin = {
|
|
187
|
+
canHandle: (node) => typeof node === "string" && (node.includes("{") || node.includes("}")),
|
|
188
|
+
transform: (node) => {
|
|
189
|
+
try {
|
|
190
|
+
return icuNodesToIntlayer(parseICU(node));
|
|
191
|
+
} catch {
|
|
192
|
+
return node;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const intlayerToIcuPlugin = {
|
|
197
|
+
canHandle: (node) => typeof node === "string" && (node.includes("{") || node.includes("}")) || node && typeof node === "object" && (node.nodeType === NodeType.Insertion || node.nodeType === NodeType.Enumeration || node.nodeType === NodeType.Gender || node.nodeType === "composite") || Array.isArray(node),
|
|
198
|
+
transform: (node, props, next) => {
|
|
199
|
+
if (typeof node === "string") return node.replace(/\{\{([^}]+)\}\}/g, "{$1}");
|
|
200
|
+
if (node.nodeType === NodeType.Insertion) return node.insertion.replace(/\{\{([^}]+)\}\}/g, "{$1}");
|
|
201
|
+
if (node.nodeType === NodeType.Enumeration) {
|
|
202
|
+
const options = node.enumeration;
|
|
203
|
+
const transformedOptions = {};
|
|
204
|
+
for (const [key, val] of Object.entries(options)) {
|
|
205
|
+
if (key === "__intlayer_icu_var") continue;
|
|
206
|
+
const childVal = next(val, props);
|
|
207
|
+
transformedOptions[key] = typeof childVal === "string" ? childVal : JSON.stringify(childVal);
|
|
208
|
+
}
|
|
209
|
+
let varName = options.__intlayer_icu_var || "n";
|
|
210
|
+
if (!options.__intlayer_icu_var) {
|
|
211
|
+
const match = (transformedOptions.fallback || transformedOptions.other || Object.values(transformedOptions)[0]).match(/\{([a-zA-Z0-9_]+)\}(?!,)/);
|
|
212
|
+
if (match) varName = match[1];
|
|
213
|
+
}
|
|
214
|
+
const keys = Object.keys(transformedOptions);
|
|
215
|
+
const pluralKeys = [
|
|
216
|
+
"1",
|
|
217
|
+
"2",
|
|
218
|
+
"<=3",
|
|
219
|
+
">=4",
|
|
220
|
+
"fallback",
|
|
221
|
+
"other",
|
|
222
|
+
"zero",
|
|
223
|
+
"one",
|
|
224
|
+
"two",
|
|
225
|
+
"few",
|
|
226
|
+
"many"
|
|
227
|
+
];
|
|
228
|
+
const isPlural = keys.every((k) => pluralKeys.includes(k) || /^[<>=]?\d+(\.\d+)?$/.test(k));
|
|
229
|
+
const parts = [];
|
|
230
|
+
if (isPlural) {
|
|
231
|
+
for (const [key, val] of Object.entries(transformedOptions)) {
|
|
232
|
+
let icuKey = key;
|
|
233
|
+
if (key === "fallback") icuKey = "other";
|
|
234
|
+
else if (key === "<=3") icuKey = "few";
|
|
235
|
+
else if (key === ">=4") icuKey = "many";
|
|
236
|
+
else if (/^\d+$/.test(key)) icuKey = `=${key}`;
|
|
237
|
+
else if ([
|
|
238
|
+
"zero",
|
|
239
|
+
"few",
|
|
240
|
+
"many"
|
|
241
|
+
].includes(key)) icuKey = key;
|
|
242
|
+
else icuKey = "other";
|
|
243
|
+
let strVal = val;
|
|
244
|
+
strVal = strVal.replace(new RegExp(`\\{${varName}\\}`, "g"), "#");
|
|
245
|
+
parts.push(`${icuKey} {${strVal}}`);
|
|
246
|
+
}
|
|
247
|
+
return `{${varName}, plural, ${parts.join(" ")}}`;
|
|
248
|
+
} else {
|
|
249
|
+
const entries = Object.entries(transformedOptions).sort(([keyA], [keyB]) => {
|
|
250
|
+
if (keyA === "fallback" || keyA === "other") return 1;
|
|
251
|
+
if (keyB === "fallback" || keyB === "other") return -1;
|
|
252
|
+
return 0;
|
|
253
|
+
});
|
|
254
|
+
for (const [key, val] of entries) {
|
|
255
|
+
let icuKey = key;
|
|
256
|
+
if (key === "fallback") icuKey = "other";
|
|
257
|
+
parts.push(`${icuKey} {${val}}`);
|
|
258
|
+
}
|
|
259
|
+
return `{${varName}, select, ${parts.join(" ")}}`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (node.nodeType === NodeType.Gender) {
|
|
263
|
+
const options = node.gender;
|
|
264
|
+
const varName = "gender";
|
|
265
|
+
const parts = [];
|
|
266
|
+
const entries = Object.entries(options).sort(([keyA], [keyB]) => {
|
|
267
|
+
if (keyA === "fallback") return 1;
|
|
268
|
+
if (keyB === "fallback") return -1;
|
|
269
|
+
return 0;
|
|
270
|
+
});
|
|
271
|
+
for (const [key, val] of entries) {
|
|
272
|
+
let icuKey = key;
|
|
273
|
+
if (key === "fallback") icuKey = "other";
|
|
274
|
+
const childVal = next(val, props);
|
|
275
|
+
const strVal = typeof childVal === "string" ? childVal : JSON.stringify(childVal);
|
|
276
|
+
parts.push(`${icuKey} {${strVal}}`);
|
|
277
|
+
}
|
|
278
|
+
return `{${varName}, select, ${parts.join(" ")}}`;
|
|
279
|
+
}
|
|
280
|
+
if (Array.isArray(node) || node.nodeType === "composite" && Array.isArray(node.composite)) return (Array.isArray(node) ? node : node.composite).map((item) => next(item, props)).join("");
|
|
281
|
+
return next(node, props);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const intlayerToICUFormatter = (message) => {
|
|
285
|
+
return deepTransformNode(message, {
|
|
286
|
+
dictionaryKey: "icu",
|
|
287
|
+
keyPath: [],
|
|
288
|
+
plugins: [{
|
|
289
|
+
id: "icu",
|
|
290
|
+
...intlayerToIcuPlugin
|
|
291
|
+
}]
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
const icuToIntlayerFormatter = (message) => {
|
|
295
|
+
return deepTransformNode(message, {
|
|
296
|
+
dictionaryKey: "icu",
|
|
297
|
+
keyPath: [],
|
|
298
|
+
plugins: [{
|
|
299
|
+
id: "icu",
|
|
300
|
+
...icuToIntlayerPlugin
|
|
301
|
+
}]
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
export { icuToIntlayerFormatter, intlayerToICUFormatter };
|
|
307
|
+
//# sourceMappingURL=ICU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ICU.mjs","names":["nodes: ICUNode[]","options: Record<string, ICUNode[]>","insert","options: Record<string, any>","enu","transformedOptions: Record<string, string>"],"sources":["../../../src/messageFormat/ICU.ts"],"sourcesContent":["import { type Dictionary, NodeType } from '@intlayer/types';\nimport { deepTransformNode } from '../interpreter';\nimport { enu, gender, insert } from '../transpiler';\n\n// Types for our AST\ntype ICUNode =\n | string\n | {\n type: 'argument';\n name: string;\n format?: { type: string; style?: string };\n }\n | { type: 'plural'; name: string; options: Record<string, ICUNode[]> }\n | { type: 'select'; name: string; options: Record<string, ICUNode[]> };\n\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst parseICU = (text: string): ICUNode[] => {\n let index = 0;\n\n const parseNodes = (): ICUNode[] => {\n const nodes: ICUNode[] = [];\n let currentText = '';\n\n while (index < text.length) {\n const char = text[index];\n\n if (char === '{') {\n if (currentText) {\n nodes.push(currentText);\n currentText = '';\n }\n index++; // skip {\n nodes.push(parseArgument());\n } else if (char === '}') {\n // End of current block\n break;\n } else if (char === \"'\") {\n // Escaping\n if (index + 1 < text.length && text[index + 1] === \"'\") {\n currentText += \"'\";\n index += 2;\n } else {\n // Find next quote\n const nextQuote = text.indexOf(\"'\", index + 1);\n if (nextQuote !== -1) {\n // Determine if this is escaping syntax characters\n // For simplicity, we'll treat content between single quotes as literal\n // provided it contains syntax chars.\n // Standard ICU: ' quoted string '\n // If it is just an apostrophe, it should be doubled.\n // But simplified: take content between quotes literally.\n currentText += text.substring(index + 1, nextQuote);\n index = nextQuote + 1;\n } else {\n currentText += \"'\";\n index++;\n }\n }\n } else {\n currentText += char;\n index++;\n }\n }\n\n if (currentText) {\n nodes.push(currentText);\n }\n return nodes;\n };\n\n const parseArgument = (): ICUNode => {\n // We are past '{'\n // Parse name\n let name = '';\n while (index < text.length && /[^,}]/.test(text[index])) {\n name += text[index];\n index++;\n }\n name = name.trim();\n\n if (index >= text.length) throw new Error('Unclosed argument');\n\n if (text[index] === '}') {\n index++;\n return { type: 'argument', name };\n }\n\n // Must be comma\n if (text[index] === ',') {\n index++;\n // Parse type\n let type = '';\n while (index < text.length && /[^,}]/.test(text[index])) {\n type += text[index];\n index++;\n }\n type = type.trim();\n\n if (index >= text.length) throw new Error('Unclosed argument');\n\n if (text[index] === '}') {\n index++;\n return { type: 'argument', name, format: { type } };\n }\n\n if (text[index] === ',') {\n index++;\n\n // If plural or select, parse options\n if (type === 'plural' || type === 'select') {\n // Parse options\n const options: Record<string, ICUNode[]> = {};\n\n while (index < text.length && text[index] !== '}') {\n // skip whitespace\n while (index < text.length && /\\s/.test(text[index])) index++;\n\n // parse key\n let key = '';\n while (index < text.length && /[^{\\s]/.test(text[index])) {\n key += text[index];\n index++;\n }\n\n while (index < text.length && /\\s/.test(text[index])) index++;\n\n if (text[index] !== '{')\n throw new Error('Expected { after option key');\n index++; // skip {\n\n const value = parseNodes();\n\n if (text[index] !== '}')\n throw new Error('Expected } after option value');\n index++; // skip }\n\n options[key] = value;\n\n while (index < text.length && /\\s/.test(text[index])) index++;\n }\n\n index++; // skip closing argument }\n\n if (type === 'plural') {\n return { type: 'plural', name, options };\n } else if (type === 'select') {\n return { type: 'select', name, options };\n }\n } else {\n // Parse style for number/date/time\n let style = '';\n while (index < text.length && text[index] !== '}') {\n style += text[index];\n index++;\n }\n if (index >= text.length) throw new Error('Unclosed argument');\n\n style = style.trim();\n index++; // skip }\n\n return { type: 'argument', name, format: { type, style } };\n }\n }\n }\n\n throw new Error('Malformed argument');\n };\n\n return parseNodes();\n};\n\nconst icuNodesToIntlayer = (nodes: ICUNode[]): any => {\n if (nodes.length === 0) return '';\n if (nodes.length === 1 && typeof nodes[0] === 'string') return nodes[0];\n\n // Check if we can flatten to a single string (insert)\n const canFlatten = nodes.every(\n (node) => typeof node === 'string' || node.type === 'argument'\n );\n if (canFlatten) {\n let str = '';\n for (const node of nodes) {\n if (typeof node === 'string') {\n str += node;\n } else if (typeof node !== 'string' && node.type === 'argument') {\n if (node.format) {\n str += `{${node.name}, ${node.format.type}${\n node.format.style ? `, ${node.format.style}` : ''\n }}`;\n } else {\n str += `{{${node.name}}}`;\n }\n }\n }\n return insert(str);\n }\n\n // Mix of string and complex types.\n // If we have just one complex type and it covers everything?\n if (nodes.length === 1) {\n const node = nodes[0];\n\n if (typeof node === 'string') return node; // already handled\n if (node.type === 'argument') {\n if (node.format) {\n return insert(\n `{${node.name}, ${node.format.type}${\n node.format.style ? `, ${node.format.style}` : ''\n }}`\n );\n }\n return insert(`{{${node.name}}}`);\n }\n if (node.type === 'plural') {\n const options: Record<string, any> = {};\n\n for (const [key, val] of Object.entries(node.options)) {\n // Map ICU keys to Intlayer keys\n let newKey = key;\n if (key.startsWith('=')) {\n newKey = key.substring(1); // =0 -> 0\n } else if (key === 'one') {\n newKey = '1';\n } else if (key === 'two') {\n newKey = '2';\n } else if (key === 'few') {\n newKey = '<=3';\n } else if (key === 'many') {\n newKey = '>=4';\n } else if (key === 'other') {\n newKey = 'fallback';\n }\n // Handle # in plural value\n // For plural, we need to pass the variable name down or replace #\n // Intlayer uses {{n}} (or whatever var name)\n // We should replace # with {{n}} in the string parts of val\n const replacedVal = val.map((v) => {\n if (typeof v === 'string') {\n return v.replace(/#/g, `{{${node.name}}}`);\n }\n return v;\n });\n\n options[newKey] = icuNodesToIntlayer(replacedVal);\n }\n\n // Preserve variable name\n options.__intlayer_icu_var = node.name;\n\n return enu(options);\n }\n if (node.type === 'select') {\n const options: Record<string, any> = {};\n\n for (const [key, val] of Object.entries(node.options)) {\n options[key] = icuNodesToIntlayer(val);\n }\n\n // Check if it looks like gender\n const optionKeys = Object.keys(options);\n // It is gender if it has 'male' OR 'female' AND only contains gender keys (male, female, other)\n const isGender =\n (options.male || options.female) &&\n optionKeys.every((k) =>\n ['male', 'female', 'other', 'fallback'].includes(k)\n );\n\n if (isGender) {\n return gender({\n fallback: options.other,\n male: options.male,\n female: options.female,\n });\n }\n\n // Preserve variable name\n options.__intlayer_icu_var = node.name;\n\n return enu(options);\n }\n }\n\n // If multiple nodes, return array\n return nodes.map((node) => icuNodesToIntlayer([node]));\n};\n\nconst icuToIntlayerPlugin = {\n canHandle: (node: any) =>\n typeof node === 'string' && (node.includes('{') || node.includes('}')),\n transform: (node: any) => {\n try {\n const ast = parseICU(node);\n return icuNodesToIntlayer(ast);\n } catch {\n // If parsing fails, return original string\n return node;\n }\n },\n};\n\nconst intlayerToIcuPlugin = {\n canHandle: (node: any) =>\n (typeof node === 'string' && (node.includes('{') || node.includes('}'))) ||\n (node &&\n typeof node === 'object' &&\n (node.nodeType === NodeType.Insertion ||\n node.nodeType === NodeType.Enumeration ||\n node.nodeType === NodeType.Gender ||\n node.nodeType === 'composite')) ||\n Array.isArray(node),\n transform: (node: any, props: any, next: any) => {\n if (typeof node === 'string') {\n return node.replace(/\\{\\{([^}]+)\\}\\}/g, '{$1}');\n }\n\n if (node.nodeType === NodeType.Insertion) {\n // insert(\"Hello {{name}}\") -> \"Hello {name}\"\n return node.insertion.replace(/\\{\\{([^}]+)\\}\\}/g, '{$1}');\n }\n\n if (node.nodeType === NodeType.Enumeration) {\n const options = node.enumeration;\n\n // Transform all values first\n const transformedOptions: Record<string, string> = {};\n for (const [key, val] of Object.entries(options)) {\n if (key === '__intlayer_icu_var') continue;\n const childVal = next(val, props);\n transformedOptions[key] =\n typeof childVal === 'string' ? childVal : JSON.stringify(childVal);\n }\n\n // Infer variable name\n let varName = options.__intlayer_icu_var || 'n';\n\n if (!options.__intlayer_icu_var) {\n const fallbackVal =\n transformedOptions.fallback ||\n transformedOptions.other ||\n Object.values(transformedOptions)[0];\n // Match {variable} but avoid {variable, ...} (which are nested ICUs)\n // Actually nested ICU starts with {var, ...\n // Simple variable is {var}\n // We look for {var} that is NOT followed by ,\n const match = fallbackVal.match(/\\{([a-zA-Z0-9_]+)\\}(?!,)/);\n if (match) {\n varName = match[1];\n }\n }\n\n const keys = Object.keys(transformedOptions);\n const pluralKeys = [\n '1',\n '2',\n '<=3',\n '>=4',\n 'fallback',\n 'other',\n 'zero',\n 'one',\n 'two',\n 'few',\n 'many',\n ];\n // Also check for numbers\n const isPlural = keys.every(\n (k) => pluralKeys.includes(k) || /^[<>=]?\\d+(\\.\\d+)?$/.test(k)\n );\n\n const parts = [];\n\n if (isPlural) {\n for (const [key, val] of Object.entries(transformedOptions)) {\n let icuKey = key;\n\n if (key === 'fallback') icuKey = 'other';\n else if (key === '<=3') icuKey = 'few';\n else if (key === '>=4') icuKey = 'many';\n else if (/^\\d+$/.test(key)) icuKey = `=${key}`;\n else if (['zero', 'few', 'many'].includes(key)) icuKey = key;\n else icuKey = 'other';\n\n let strVal = val;\n\n // Replace {varName} with #\n // Note: next() has already converted {{var}} -> {var}\n strVal = strVal.replace(new RegExp(`\\\\{${varName}\\\\}`, 'g'), '#');\n\n parts.push(`${icuKey} {${strVal}}`);\n }\n\n return `{${varName}, plural, ${parts.join(' ')}}`;\n } else {\n // Select\n const entries = Object.entries(transformedOptions).sort(\n ([keyA], [keyB]) => {\n if (keyA === 'fallback' || keyA === 'other') return 1;\n if (keyB === 'fallback' || keyB === 'other') return -1;\n return 0;\n }\n );\n\n for (const [key, val] of entries) {\n let icuKey = key;\n if (key === 'fallback') icuKey = 'other';\n // Do NOT map other keys to 'other'. Keep 'active', 'inactive', etc.\n\n parts.push(`${icuKey} {${val}}`);\n }\n return `{${varName}, select, ${parts.join(' ')}}`;\n }\n }\n\n if (node.nodeType === NodeType.Gender) {\n const options = node.gender;\n const varName = 'gender';\n const parts = [];\n\n const entries = Object.entries(options).sort(([keyA], [keyB]) => {\n if (keyA === 'fallback') return 1;\n if (keyB === 'fallback') return -1;\n return 0;\n });\n\n for (const [key, val] of entries) {\n let icuKey = key;\n if (key === 'fallback') icuKey = 'other';\n\n const childVal = next(val, props);\n const strVal =\n typeof childVal === 'string' ? childVal : JSON.stringify(childVal);\n\n parts.push(`${icuKey} {${strVal}}`);\n }\n return `{${varName}, select, ${parts.join(' ')}}`;\n }\n\n if (\n Array.isArray(node) ||\n (node.nodeType === 'composite' && Array.isArray(node.composite))\n ) {\n // handle array/composite\n const arr = Array.isArray(node) ? node : node.composite;\n const items = arr.map((item: any) => next(item, props));\n return items.join('');\n }\n\n return next(node, props);\n },\n};\n\nexport const intlayerToICUFormatter = (\n message: Dictionary['content']\n): JsonValue => {\n return deepTransformNode(message, {\n dictionaryKey: 'icu',\n keyPath: [],\n plugins: [{ id: 'icu', ...intlayerToIcuPlugin }],\n });\n};\n\nexport const icuToIntlayerFormatter = (\n message: Dictionary['content']\n): JsonValue => {\n return deepTransformNode(message, {\n dictionaryKey: 'icu',\n keyPath: [],\n plugins: [{ id: 'icu', ...icuToIntlayerPlugin }],\n });\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { deepTransformNode } from "../interpreter/getContent/deepTransform.mjs";
|
|
2
|
+
import { enu as enumeration } from "../transpiler/enumeration/enumeration.mjs";
|
|
3
|
+
import { gender } from "../transpiler/gender/gender.mjs";
|
|
4
|
+
import { insert as insertion } from "../transpiler/insertion/insertion.mjs";
|
|
5
|
+
import { NodeType } from "@intlayer/types";
|
|
6
|
+
|
|
7
|
+
//#region src/messageFormat/i18next.ts
|
|
8
|
+
const parseI18Next = (text) => {
|
|
9
|
+
let index = 0;
|
|
10
|
+
const parseNodes = () => {
|
|
11
|
+
const nodes = [];
|
|
12
|
+
let currentText = "";
|
|
13
|
+
while (index < text.length) {
|
|
14
|
+
const char = text[index];
|
|
15
|
+
if (char === "{" && text[index + 1] === "{") {
|
|
16
|
+
if (currentText) {
|
|
17
|
+
nodes.push(currentText);
|
|
18
|
+
currentText = "";
|
|
19
|
+
}
|
|
20
|
+
index += 2;
|
|
21
|
+
nodes.push(parseStandardArgument());
|
|
22
|
+
} else if (char === "{") {
|
|
23
|
+
if (currentText) {
|
|
24
|
+
nodes.push(currentText);
|
|
25
|
+
currentText = "";
|
|
26
|
+
}
|
|
27
|
+
index++;
|
|
28
|
+
nodes.push(parseICUArgument());
|
|
29
|
+
} else if (char === "}") break;
|
|
30
|
+
else {
|
|
31
|
+
currentText += char;
|
|
32
|
+
index++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (currentText) nodes.push(currentText);
|
|
36
|
+
return nodes;
|
|
37
|
+
};
|
|
38
|
+
const parseStandardArgument = () => {
|
|
39
|
+
let name = "";
|
|
40
|
+
while (index < text.length) {
|
|
41
|
+
if (text[index] === "}" && text[index + 1] === "}") {
|
|
42
|
+
index += 2;
|
|
43
|
+
return {
|
|
44
|
+
type: "argument",
|
|
45
|
+
name: name.trim()
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
name += text[index];
|
|
49
|
+
index++;
|
|
50
|
+
}
|
|
51
|
+
throw new Error("Unclosed i18next variable");
|
|
52
|
+
};
|
|
53
|
+
const parseICUArgument = () => {
|
|
54
|
+
let name = "";
|
|
55
|
+
while (index < text.length && /[^,}]/.test(text[index])) {
|
|
56
|
+
name += text[index];
|
|
57
|
+
index++;
|
|
58
|
+
}
|
|
59
|
+
name = name.trim();
|
|
60
|
+
if (index >= text.length) throw new Error("Unclosed argument");
|
|
61
|
+
if (text[index] === "}") {
|
|
62
|
+
index++;
|
|
63
|
+
return {
|
|
64
|
+
type: "argument",
|
|
65
|
+
name
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (text[index] === ",") {
|
|
69
|
+
index++;
|
|
70
|
+
let type = "";
|
|
71
|
+
while (index < text.length && /[^,}]/.test(text[index])) {
|
|
72
|
+
type += text[index];
|
|
73
|
+
index++;
|
|
74
|
+
}
|
|
75
|
+
type = type.trim();
|
|
76
|
+
if (index >= text.length) throw new Error("Unclosed argument");
|
|
77
|
+
if (text[index] === "}") {
|
|
78
|
+
index++;
|
|
79
|
+
return {
|
|
80
|
+
type: "argument",
|
|
81
|
+
name,
|
|
82
|
+
format: { type }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (text[index] === ",") {
|
|
86
|
+
index++;
|
|
87
|
+
if (type === "plural" || type === "select") {
|
|
88
|
+
const options = {};
|
|
89
|
+
while (index < text.length && text[index] !== "}") {
|
|
90
|
+
while (index < text.length && /\s/.test(text[index])) index++;
|
|
91
|
+
let key = "";
|
|
92
|
+
while (index < text.length && /[^{\s]/.test(text[index])) {
|
|
93
|
+
key += text[index];
|
|
94
|
+
index++;
|
|
95
|
+
}
|
|
96
|
+
while (index < text.length && /\s/.test(text[index])) index++;
|
|
97
|
+
if (text[index] !== "{") throw new Error("Expected { after option key");
|
|
98
|
+
index++;
|
|
99
|
+
const value = parseNodes();
|
|
100
|
+
if (text[index] !== "}") throw new Error("Expected } after option value");
|
|
101
|
+
index++;
|
|
102
|
+
options[key] = value;
|
|
103
|
+
while (index < text.length && /\s/.test(text[index])) index++;
|
|
104
|
+
}
|
|
105
|
+
index++;
|
|
106
|
+
if (type === "plural") return {
|
|
107
|
+
type: "plural",
|
|
108
|
+
name,
|
|
109
|
+
options
|
|
110
|
+
};
|
|
111
|
+
else if (type === "select") return {
|
|
112
|
+
type: "select",
|
|
113
|
+
name,
|
|
114
|
+
options
|
|
115
|
+
};
|
|
116
|
+
} else {
|
|
117
|
+
let style = "";
|
|
118
|
+
while (index < text.length && text[index] !== "}") {
|
|
119
|
+
style += text[index];
|
|
120
|
+
index++;
|
|
121
|
+
}
|
|
122
|
+
if (index >= text.length) throw new Error("Unclosed argument");
|
|
123
|
+
style = style.trim();
|
|
124
|
+
index++;
|
|
125
|
+
return {
|
|
126
|
+
type: "argument",
|
|
127
|
+
name,
|
|
128
|
+
format: {
|
|
129
|
+
type,
|
|
130
|
+
style
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
throw new Error("Malformed argument");
|
|
137
|
+
};
|
|
138
|
+
return parseNodes();
|
|
139
|
+
};
|
|
140
|
+
const i18nextNodesToIntlayer = (nodes) => {
|
|
141
|
+
if (nodes.length === 0) return "";
|
|
142
|
+
if (nodes.length === 1 && typeof nodes[0] === "string") return nodes[0];
|
|
143
|
+
if (nodes.every((node) => typeof node === "string" || node.type === "argument")) {
|
|
144
|
+
let str = "";
|
|
145
|
+
for (const node of nodes) if (typeof node === "string") str += node;
|
|
146
|
+
else if (typeof node !== "string" && node.type === "argument") if (node.format) str += `{${node.name}, ${node.format.type}${node.format.style ? `, ${node.format.style}` : ""}}`;
|
|
147
|
+
else str += `{{${node.name}}}`;
|
|
148
|
+
return insertion(str);
|
|
149
|
+
}
|
|
150
|
+
if (nodes.length === 1) {
|
|
151
|
+
const node = nodes[0];
|
|
152
|
+
if (typeof node === "string") return node;
|
|
153
|
+
if (node.type === "argument") {
|
|
154
|
+
if (node.format) return insertion(`{${node.name}, ${node.format.type}${node.format.style ? `, ${node.format.style}` : ""}}`);
|
|
155
|
+
return insertion(`{{${node.name}}}`);
|
|
156
|
+
}
|
|
157
|
+
if (node.type === "plural") {
|
|
158
|
+
const options = {};
|
|
159
|
+
for (const [key, val] of Object.entries(node.options)) {
|
|
160
|
+
let newKey = key;
|
|
161
|
+
if (key.startsWith("=")) newKey = key.substring(1);
|
|
162
|
+
else if (key === "one") newKey = "1";
|
|
163
|
+
else if (key === "two") newKey = "2";
|
|
164
|
+
else if (key === "few") newKey = "<=3";
|
|
165
|
+
else if (key === "many") newKey = ">=4";
|
|
166
|
+
else if (key === "other") newKey = "fallback";
|
|
167
|
+
options[newKey] = i18nextNodesToIntlayer(val.map((v) => {
|
|
168
|
+
if (typeof v === "string") return v.replace(/#/g, `{{${node.name}}}`);
|
|
169
|
+
return v;
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
options.__intlayer_icu_var = node.name;
|
|
173
|
+
return enumeration(options);
|
|
174
|
+
}
|
|
175
|
+
if (node.type === "select") {
|
|
176
|
+
const options = {};
|
|
177
|
+
for (const [key, val] of Object.entries(node.options)) options[key] = i18nextNodesToIntlayer(val);
|
|
178
|
+
const optionKeys = Object.keys(options);
|
|
179
|
+
if ((options.male || options.female) && optionKeys.every((k) => [
|
|
180
|
+
"male",
|
|
181
|
+
"female",
|
|
182
|
+
"other",
|
|
183
|
+
"fallback"
|
|
184
|
+
].includes(k))) return gender({
|
|
185
|
+
fallback: options.other,
|
|
186
|
+
male: options.male,
|
|
187
|
+
female: options.female
|
|
188
|
+
});
|
|
189
|
+
options.__intlayer_icu_var = node.name;
|
|
190
|
+
return enumeration(options);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return nodes.map((node) => i18nextNodesToIntlayer([node]));
|
|
194
|
+
};
|
|
195
|
+
const i18nextToIntlayerPlugin = {
|
|
196
|
+
canHandle: (node) => typeof node === "string" && (node.includes("{") || node.includes("}")),
|
|
197
|
+
transform: (node) => {
|
|
198
|
+
try {
|
|
199
|
+
return i18nextNodesToIntlayer(parseI18Next(node));
|
|
200
|
+
} catch {
|
|
201
|
+
return node;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const intlayerToI18nextPlugin = {
|
|
206
|
+
canHandle: (node) => typeof node === "string" || node && typeof node === "object" && (node.nodeType === NodeType.Insertion || node.nodeType === NodeType.Enumeration || node.nodeType === NodeType.Gender || node.nodeType === "composite") || Array.isArray(node),
|
|
207
|
+
transform: (node, props, next) => {
|
|
208
|
+
if (typeof node === "string") return node;
|
|
209
|
+
if (node.nodeType === NodeType.Insertion) {
|
|
210
|
+
if (node.insertion.match(/\{[^}]*,[^}]*\}/)) return node.insertion;
|
|
211
|
+
return node.insertion;
|
|
212
|
+
}
|
|
213
|
+
if (node.nodeType === NodeType.Enumeration) {
|
|
214
|
+
const options = node.enumeration;
|
|
215
|
+
const transformedOptions = {};
|
|
216
|
+
for (const [key, val] of Object.entries(options)) {
|
|
217
|
+
if (key === "__intlayer_icu_var") continue;
|
|
218
|
+
const childVal = next(val, props);
|
|
219
|
+
transformedOptions[key] = typeof childVal === "string" ? childVal : JSON.stringify(childVal);
|
|
220
|
+
}
|
|
221
|
+
let varName = options.__intlayer_icu_var || "count";
|
|
222
|
+
if (!options.__intlayer_icu_var) {
|
|
223
|
+
const fallbackVal = transformedOptions.fallback || transformedOptions.other || Object.values(transformedOptions)[0];
|
|
224
|
+
const match = fallbackVal.match(/\{\{([a-zA-Z0-9_]+)\}\}/) || fallbackVal.match(/\{([a-zA-Z0-9_]+)\}(?!,)/);
|
|
225
|
+
if (match) varName = match[1];
|
|
226
|
+
}
|
|
227
|
+
const keys = Object.keys(transformedOptions);
|
|
228
|
+
const pluralKeys = [
|
|
229
|
+
"1",
|
|
230
|
+
"2",
|
|
231
|
+
"<=3",
|
|
232
|
+
">=4",
|
|
233
|
+
"fallback",
|
|
234
|
+
"other",
|
|
235
|
+
"zero",
|
|
236
|
+
"one",
|
|
237
|
+
"two",
|
|
238
|
+
"few",
|
|
239
|
+
"many"
|
|
240
|
+
];
|
|
241
|
+
const isPlural = keys.every((k) => pluralKeys.includes(k) || /^[<>=]?\d+(\.\d+)?$/.test(k));
|
|
242
|
+
const parts = [];
|
|
243
|
+
if (isPlural) {
|
|
244
|
+
for (const [key, val] of Object.entries(transformedOptions)) {
|
|
245
|
+
let icuKey = key;
|
|
246
|
+
if (key === "fallback") icuKey = "other";
|
|
247
|
+
else if (key === "<=3") icuKey = "few";
|
|
248
|
+
else if (key === ">=4") icuKey = "many";
|
|
249
|
+
else if (/^\d+$/.test(key)) icuKey = `=${key}`;
|
|
250
|
+
let strVal = val;
|
|
251
|
+
strVal = strVal.replace(/\{\{([^}]+)\}\}/g, "{$1}");
|
|
252
|
+
strVal = strVal.replace(new RegExp(`\\{${varName}\\}`, "g"), "#");
|
|
253
|
+
parts.push(`${icuKey} {${strVal}}`);
|
|
254
|
+
}
|
|
255
|
+
return `{${varName}, plural, ${parts.join(" ")}}`;
|
|
256
|
+
} else {
|
|
257
|
+
const entries = Object.entries(transformedOptions).sort(([keyA], [keyB]) => {
|
|
258
|
+
if (keyA === "fallback" || keyA === "other") return 1;
|
|
259
|
+
if (keyB === "fallback" || keyB === "other") return -1;
|
|
260
|
+
return 0;
|
|
261
|
+
});
|
|
262
|
+
for (const [key, val] of entries) {
|
|
263
|
+
let icuKey = key;
|
|
264
|
+
if (key === "fallback") icuKey = "other";
|
|
265
|
+
let strVal = val;
|
|
266
|
+
strVal = strVal.replace(/\{\{([^}]+)\}\}/g, "{$1}");
|
|
267
|
+
parts.push(`${icuKey} {${strVal}}`);
|
|
268
|
+
}
|
|
269
|
+
return `{${varName}, select, ${parts.join(" ")}}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (node.nodeType === NodeType.Gender) {
|
|
273
|
+
const options = node.gender;
|
|
274
|
+
const varName = "gender";
|
|
275
|
+
const parts = [];
|
|
276
|
+
const entries = Object.entries(options).sort(([keyA], [keyB]) => {
|
|
277
|
+
if (keyA === "fallback") return 1;
|
|
278
|
+
if (keyB === "fallback") return -1;
|
|
279
|
+
return 0;
|
|
280
|
+
});
|
|
281
|
+
for (const [key, val] of entries) {
|
|
282
|
+
let icuKey = key;
|
|
283
|
+
if (key === "fallback") icuKey = "other";
|
|
284
|
+
const childVal = next(val, props);
|
|
285
|
+
let strVal = typeof childVal === "string" ? childVal : JSON.stringify(childVal);
|
|
286
|
+
strVal = strVal.replace(/\{\{([^}]+)\}\}/g, "{$1}");
|
|
287
|
+
parts.push(`${icuKey} {${strVal}}`);
|
|
288
|
+
}
|
|
289
|
+
return `{${varName}, select, ${parts.join(" ")}}`;
|
|
290
|
+
}
|
|
291
|
+
if (Array.isArray(node) || node.nodeType === "composite" && Array.isArray(node.composite)) return (Array.isArray(node) ? node : node.composite).map((item) => next(item, props)).join("");
|
|
292
|
+
return next(node, props);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const intlayerToI18nextFormatter = (message) => {
|
|
296
|
+
return deepTransformNode(message, {
|
|
297
|
+
dictionaryKey: "i18next",
|
|
298
|
+
keyPath: [],
|
|
299
|
+
plugins: [{
|
|
300
|
+
id: "i18next",
|
|
301
|
+
...intlayerToI18nextPlugin
|
|
302
|
+
}]
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
const i18nextToIntlayerFormatter = (message) => {
|
|
306
|
+
return deepTransformNode(message, {
|
|
307
|
+
dictionaryKey: "i18next",
|
|
308
|
+
keyPath: [],
|
|
309
|
+
plugins: [{
|
|
310
|
+
id: "i18next",
|
|
311
|
+
...i18nextToIntlayerPlugin
|
|
312
|
+
}]
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
//#endregion
|
|
317
|
+
export { i18nextToIntlayerFormatter, intlayerToI18nextFormatter };
|
|
318
|
+
//# sourceMappingURL=i18next.mjs.map
|