@team-monolith/cds 1.129.2-alpha.2 → 1.130.0-alpha
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/emotion.d.ts +185 -0
- package/dist/index.d.ts +8 -1
- package/dist/patterns/LexicalEditor/LexicalCustomConfigContext.d.ts +1 -0
- package/dist/patterns/LexicalEditor/LexicalCustomConfigContext.js +2 -1
- package/dist/patterns/LexicalEditor/LexicalEditor.d.ts +4 -0
- package/dist/patterns/LexicalEditor/LexicalEditor.js +9 -7
- package/dist/patterns/LexicalEditor/Plugins.d.ts +1 -0
- package/dist/patterns/LexicalEditor/Plugins.js +65 -61
- package/dist/patterns/LexicalEditor/components/FileSelectInput.js +24 -23
- package/dist/patterns/LexicalEditor/components/UploadFileDialog/UploadFileDialog.js +2 -1
- package/dist/patterns/LexicalEditor/nodes/FileNode/CompactFileView.d.ts +7 -0
- package/dist/patterns/LexicalEditor/nodes/FileNode/CompactFileView.js +53 -0
- package/dist/patterns/LexicalEditor/nodes/FileNode/FileComponent.d.ts +0 -27
- package/dist/patterns/LexicalEditor/nodes/FileNode/FileComponent.js +44 -115
- package/dist/patterns/LexicalEditor/nodes/FileNode/FileDownloadButton.js +1 -1
- package/dist/patterns/LexicalEditor/nodes/FileNode/fileStyles.d.ts +27 -0
- package/dist/patterns/LexicalEditor/nodes/FileNode/fileStyles.js +85 -0
- package/dist/patterns/LexicalEditor/nodes/ImageNode/CompactImageView.d.ts +6 -0
- package/dist/patterns/LexicalEditor/nodes/ImageNode/CompactImageView.js +91 -0
- package/dist/patterns/LexicalEditor/nodes/ImageNode/ImageComponent.js +108 -102
- package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +7 -7
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +4 -4
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.js +4 -4
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.js +4 -4
- package/dist/patterns/LexicalEditor/plugins/CompactToolbarPlugin/CompactToolbarPlugin.d.ts +1 -0
- package/dist/patterns/LexicalEditor/plugins/CompactToolbarPlugin/CompactToolbarPlugin.js +106 -0
- package/dist/patterns/LexicalEditor/plugins/CompactToolbarPlugin/compactAttachmentNodes.d.ts +8 -0
- package/dist/patterns/LexicalEditor/plugins/CompactToolbarPlugin/compactAttachmentNodes.js +6 -0
- package/dist/patterns/LexicalEditor/plugins/CompactToolbarPlugin/index.d.ts +1 -0
- package/dist/patterns/LexicalEditor/plugins/DragDropPastePlugin/index.js +15 -14
- package/dist/patterns/LexicalEditor/uploadConstants.d.ts +6 -0
- package/dist/patterns/LexicalEditor/uploadConstants.js +4 -0
- package/package.json +3 -7
- package/@types/emotion.d.ts +0 -197
- package/node_modules/react-i18next/.eslintrc.json +0 -74
- package/node_modules/react-i18next/.husky/pre-commit +0 -1
- package/node_modules/react-i18next/.prettierignore +0 -4
- package/node_modules/react-i18next/CHANGELOG.md +0 -1433
- package/node_modules/react-i18next/LICENSE +0 -22
- package/node_modules/react-i18next/README.md +0 -181
- package/node_modules/react-i18next/TransWithoutContext.d.mts +0 -1
- package/node_modules/react-i18next/TransWithoutContext.d.ts +0 -129
- package/node_modules/react-i18next/dist/amd/react-i18next.js +0 -867
- package/node_modules/react-i18next/dist/amd/react-i18next.min.js +0 -1
- package/node_modules/react-i18next/dist/commonjs/I18nextProvider.js +0 -21
- package/node_modules/react-i18next/dist/commonjs/Trans.js +0 -54
- package/node_modules/react-i18next/dist/commonjs/TransWithoutContext.js +0 -330
- package/node_modules/react-i18next/dist/commonjs/Translation.js +0 -19
- package/node_modules/react-i18next/dist/commonjs/context.js +0 -81
- package/node_modules/react-i18next/dist/commonjs/defaults.js +0 -26
- package/node_modules/react-i18next/dist/commonjs/i18nInstance.js +0 -13
- package/node_modules/react-i18next/dist/commonjs/index.js +0 -128
- package/node_modules/react-i18next/dist/commonjs/initReactI18next.js +0 -15
- package/node_modules/react-i18next/dist/commonjs/unescape.js +0 -32
- package/node_modules/react-i18next/dist/commonjs/useSSR.js +0 -34
- package/node_modules/react-i18next/dist/commonjs/useTranslation.js +0 -114
- package/node_modules/react-i18next/dist/commonjs/utils.js +0 -76
- package/node_modules/react-i18next/dist/commonjs/withSSR.js +0 -27
- package/node_modules/react-i18next/dist/commonjs/withTranslation.js +0 -39
- package/node_modules/react-i18next/dist/es/I18nextProvider.js +0 -15
- package/node_modules/react-i18next/dist/es/Trans.js +0 -43
- package/node_modules/react-i18next/dist/es/TransWithoutContext.js +0 -321
- package/node_modules/react-i18next/dist/es/Translation.js +0 -12
- package/node_modules/react-i18next/dist/es/context.js +0 -42
- package/node_modules/react-i18next/dist/es/defaults.js +0 -18
- package/node_modules/react-i18next/dist/es/i18nInstance.js +0 -5
- package/node_modules/react-i18next/dist/es/index.js +0 -18
- package/node_modules/react-i18next/dist/es/initReactI18next.js +0 -9
- package/node_modules/react-i18next/dist/es/package.json +0 -1
- package/node_modules/react-i18next/dist/es/unescape.js +0 -25
- package/node_modules/react-i18next/dist/es/useSSR.js +0 -27
- package/node_modules/react-i18next/dist/es/useTranslation.js +0 -107
- package/node_modules/react-i18next/dist/es/utils.js +0 -62
- package/node_modules/react-i18next/dist/es/withSSR.js +0 -20
- package/node_modules/react-i18next/dist/es/withTranslation.js +0 -32
- package/node_modules/react-i18next/dist/umd/react-i18next.js +0 -871
- package/node_modules/react-i18next/dist/umd/react-i18next.min.js +0 -1
- package/node_modules/react-i18next/helpers.d.ts +0 -3
- package/node_modules/react-i18next/icu.macro.d.mts +0 -1
- package/node_modules/react-i18next/icu.macro.d.ts +0 -103
- package/node_modules/react-i18next/icu.macro.js +0 -729
- package/node_modules/react-i18next/index.d.mts +0 -1
- package/node_modules/react-i18next/index.d.ts +0 -209
- package/node_modules/react-i18next/initReactI18next.d.mts +0 -1
- package/node_modules/react-i18next/initReactI18next.d.ts +0 -3
- package/node_modules/react-i18next/lint-staged.config.mjs +0 -4
- package/node_modules/react-i18next/package.json +0 -167
- package/node_modules/react-i18next/react-i18next.js +0 -871
- package/node_modules/react-i18next/react-i18next.min.js +0 -1
- package/node_modules/react-i18next/src/I18nextProvider.js +0 -7
- package/node_modules/react-i18next/src/Trans.js +0 -45
- package/node_modules/react-i18next/src/TransWithoutContext.js +0 -479
- package/node_modules/react-i18next/src/Translation.js +0 -14
- package/node_modules/react-i18next/src/context.js +0 -54
- package/node_modules/react-i18next/src/defaults.js +0 -20
- package/node_modules/react-i18next/src/i18nInstance.js +0 -7
- package/node_modules/react-i18next/src/index.js +0 -22
- package/node_modules/react-i18next/src/initReactI18next.js +0 -11
- package/node_modules/react-i18next/src/unescape.js +0 -31
- package/node_modules/react-i18next/src/useSSR.js +0 -33
- package/node_modules/react-i18next/src/useTranslation.js +0 -171
- package/node_modules/react-i18next/src/utils.js +0 -93
- package/node_modules/react-i18next/src/withSSR.js +0 -21
- package/node_modules/react-i18next/src/withTranslation.js +0 -35
- package/node_modules/react-i18next/vitest.workspace.typescript.mts +0 -52
|
@@ -1,729 +0,0 @@
|
|
|
1
|
-
const { createMacro } = require('babel-plugin-macros');
|
|
2
|
-
|
|
3
|
-
// copy to:
|
|
4
|
-
// https://astexplorer.net/#/gist/642aebbb9e449e959f4ad8907b4adf3a/4a65742e2a3e926eb55eaa3d657d1472b9ac7970
|
|
5
|
-
module.exports = createMacro(ICUMacro);
|
|
6
|
-
|
|
7
|
-
function ICUMacro({ references, state, babel }) {
|
|
8
|
-
const {
|
|
9
|
-
Trans = [],
|
|
10
|
-
Plural = [],
|
|
11
|
-
Select = [],
|
|
12
|
-
SelectOrdinal = [],
|
|
13
|
-
number = [],
|
|
14
|
-
date = [],
|
|
15
|
-
select = [],
|
|
16
|
-
selectOrdinal = [],
|
|
17
|
-
plural = [],
|
|
18
|
-
time = [],
|
|
19
|
-
} = references;
|
|
20
|
-
|
|
21
|
-
// assert we have the react-i18next Trans component imported
|
|
22
|
-
addNeededImports(state, babel, references);
|
|
23
|
-
|
|
24
|
-
// transform Plural and SelectOrdinal
|
|
25
|
-
[...Plural, ...SelectOrdinal].forEach((referencePath) => {
|
|
26
|
-
if (referencePath.parentPath.type === 'JSXOpeningElement') {
|
|
27
|
-
pluralAsJSX(
|
|
28
|
-
referencePath.parentPath,
|
|
29
|
-
{
|
|
30
|
-
attributes: referencePath.parentPath.get('attributes'),
|
|
31
|
-
children: referencePath.parentPath.parentPath.get('children'),
|
|
32
|
-
},
|
|
33
|
-
babel,
|
|
34
|
-
);
|
|
35
|
-
} else {
|
|
36
|
-
// throw a helpful error message or something :)
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// transform Select
|
|
41
|
-
Select.forEach((referencePath) => {
|
|
42
|
-
if (referencePath.parentPath.type === 'JSXOpeningElement') {
|
|
43
|
-
selectAsJSX(
|
|
44
|
-
referencePath.parentPath,
|
|
45
|
-
{
|
|
46
|
-
attributes: referencePath.parentPath.get('attributes'),
|
|
47
|
-
children: referencePath.parentPath.parentPath.get('children'),
|
|
48
|
-
},
|
|
49
|
-
babel,
|
|
50
|
-
);
|
|
51
|
-
} else {
|
|
52
|
-
// throw a helpful error message or something :)
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// transform Trans
|
|
57
|
-
Trans.forEach((referencePath) => {
|
|
58
|
-
if (referencePath.parentPath.type === 'JSXOpeningElement') {
|
|
59
|
-
transAsJSX(
|
|
60
|
-
referencePath.parentPath,
|
|
61
|
-
{
|
|
62
|
-
attributes: referencePath.parentPath.get('attributes'),
|
|
63
|
-
children: referencePath.parentPath.parentPath.get('children'),
|
|
64
|
-
},
|
|
65
|
-
babel,
|
|
66
|
-
state,
|
|
67
|
-
);
|
|
68
|
-
} else {
|
|
69
|
-
// throw a helpful error message or something :)
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// check for number`` and others outside of <Trans>
|
|
74
|
-
Object.entries({
|
|
75
|
-
number,
|
|
76
|
-
date,
|
|
77
|
-
time,
|
|
78
|
-
select,
|
|
79
|
-
plural,
|
|
80
|
-
selectOrdinal,
|
|
81
|
-
}).forEach(([name, node]) => {
|
|
82
|
-
node.forEach((item) => {
|
|
83
|
-
let f = item.parentPath;
|
|
84
|
-
while (f) {
|
|
85
|
-
if (babel.types.isJSXElement(f)) {
|
|
86
|
-
if (f.node.openingElement.name.name === 'Trans') {
|
|
87
|
-
// this is a valid use of number/date/time/etc.
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
f = f.parentPath;
|
|
92
|
-
}
|
|
93
|
-
throw new Error(
|
|
94
|
-
`"${name}\`\`" can only be used inside <Trans> in "${item.node.loc.filename}" on line ${item.node.loc.start.line}`,
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function pluralAsJSX(parentPath, { attributes }, babel) {
|
|
101
|
-
const t = babel.types;
|
|
102
|
-
const toObjectProperty = (name, value) =>
|
|
103
|
-
t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
|
|
104
|
-
|
|
105
|
-
// plural or selectordinal
|
|
106
|
-
const nodeName = parentPath.node.name.name.toLocaleLowerCase();
|
|
107
|
-
|
|
108
|
-
// will need to merge count attribute with existing values attribute in some cases
|
|
109
|
-
const existingValuesAttribute = findAttribute('values', attributes);
|
|
110
|
-
const existingValues = existingValuesAttribute
|
|
111
|
-
? existingValuesAttribute.node.value.expression.properties
|
|
112
|
-
: [];
|
|
113
|
-
|
|
114
|
-
let componentStartIndex = 0;
|
|
115
|
-
const extracted = attributes.reduce(
|
|
116
|
-
(mem, attr) => {
|
|
117
|
-
if (attr.node.name.name === 'i18nKey') {
|
|
118
|
-
// copy the i18nKey
|
|
119
|
-
mem.attributesToCopy.push(attr.node);
|
|
120
|
-
} else if (attr.node.name.name === 'count') {
|
|
121
|
-
// take the count for element
|
|
122
|
-
let exprName = attr.node.value.expression.name;
|
|
123
|
-
if (!exprName) {
|
|
124
|
-
exprName = 'count';
|
|
125
|
-
}
|
|
126
|
-
if (exprName === 'count') {
|
|
127
|
-
// if the prop expression name is also "count", copy it instead: <Plural count={count} --> <Trans count={count}
|
|
128
|
-
mem.attributesToCopy.push(attr.node);
|
|
129
|
-
} else {
|
|
130
|
-
mem.values.unshift(toObjectProperty(exprName));
|
|
131
|
-
}
|
|
132
|
-
mem.defaults = `{${exprName}, ${nodeName}, ${mem.defaults}`;
|
|
133
|
-
} else if (attr.node.name.name === 'values') {
|
|
134
|
-
// skip the values attribute, as it has already been processed into mem from existingValues
|
|
135
|
-
} else if (attr.node.value.type === 'StringLiteral') {
|
|
136
|
-
// take any string node as plural option
|
|
137
|
-
let pluralForm = attr.node.name.name;
|
|
138
|
-
if (pluralForm.indexOf('$') === 0) pluralForm = pluralForm.replace('$', '=');
|
|
139
|
-
mem.defaults = `${mem.defaults} ${pluralForm} {${attr.node.value.value}}`;
|
|
140
|
-
} else if (attr.node.value.type === 'JSXExpressionContainer') {
|
|
141
|
-
// convert any Trans component to plural option extracting any values and components
|
|
142
|
-
const children = attr.node.value.expression.children || [];
|
|
143
|
-
const thisTrans = processTrans(children, babel, componentStartIndex);
|
|
144
|
-
|
|
145
|
-
let pluralForm = attr.node.name.name;
|
|
146
|
-
if (pluralForm.indexOf('$') === 0) pluralForm = pluralForm.replace('$', '=');
|
|
147
|
-
|
|
148
|
-
mem.defaults = `${mem.defaults} ${pluralForm} {${thisTrans.defaults}}`;
|
|
149
|
-
mem.components = mem.components.concat(thisTrans.components);
|
|
150
|
-
|
|
151
|
-
componentStartIndex += thisTrans.components.length;
|
|
152
|
-
}
|
|
153
|
-
return mem;
|
|
154
|
-
},
|
|
155
|
-
{ attributesToCopy: [], values: existingValues, components: [], defaults: '' },
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
// replace the node with the new Trans
|
|
159
|
-
parentPath.replaceWith(buildTransElement(extracted, extracted.attributesToCopy, t, true));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function selectAsJSX(parentPath, { attributes }, babel) {
|
|
163
|
-
const t = babel.types;
|
|
164
|
-
const toObjectProperty = (name, value) =>
|
|
165
|
-
t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
|
|
166
|
-
|
|
167
|
-
// will need to merge switch attribute with existing values attribute
|
|
168
|
-
const existingValuesAttribute = findAttribute('values', attributes);
|
|
169
|
-
const existingValues = existingValuesAttribute
|
|
170
|
-
? existingValuesAttribute.node.value.expression.properties
|
|
171
|
-
: [];
|
|
172
|
-
|
|
173
|
-
let componentStartIndex = 0;
|
|
174
|
-
|
|
175
|
-
const extracted = attributes.reduce(
|
|
176
|
-
(mem, attr) => {
|
|
177
|
-
if (attr.node.name.name === 'i18nKey') {
|
|
178
|
-
// copy the i18nKey
|
|
179
|
-
mem.attributesToCopy.push(attr.node);
|
|
180
|
-
} else if (attr.node.name.name === 'switch') {
|
|
181
|
-
// take the switch for select element
|
|
182
|
-
let exprName = attr.node.value.expression.name;
|
|
183
|
-
if (!exprName) {
|
|
184
|
-
exprName = 'selectKey';
|
|
185
|
-
mem.values.unshift(t.objectProperty(t.identifier(exprName), attr.node.value.expression));
|
|
186
|
-
} else {
|
|
187
|
-
mem.values.unshift(toObjectProperty(exprName));
|
|
188
|
-
}
|
|
189
|
-
mem.defaults = `{${exprName}, select, ${mem.defaults}`;
|
|
190
|
-
} else if (attr.node.name.name === 'values') {
|
|
191
|
-
// skip the values attribute, as it has already been processed into mem as existingValues
|
|
192
|
-
} else if (attr.node.value.type === 'StringLiteral') {
|
|
193
|
-
// take any string node as select option
|
|
194
|
-
mem.defaults = `${mem.defaults} ${attr.node.name.name} {${attr.node.value.value}}`;
|
|
195
|
-
} else if (attr.node.value.type === 'JSXExpressionContainer') {
|
|
196
|
-
// convert any Trans component to select option extracting any values and components
|
|
197
|
-
const children = attr.node.value.expression.children || [];
|
|
198
|
-
const thisTrans = processTrans(children, babel, componentStartIndex);
|
|
199
|
-
|
|
200
|
-
mem.defaults = `${mem.defaults} ${attr.node.name.name} {${thisTrans.defaults}}`;
|
|
201
|
-
mem.components = mem.components.concat(thisTrans.components);
|
|
202
|
-
|
|
203
|
-
componentStartIndex += thisTrans.components.length;
|
|
204
|
-
}
|
|
205
|
-
return mem;
|
|
206
|
-
},
|
|
207
|
-
{ attributesToCopy: [], values: existingValues, components: [], defaults: '' },
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
// replace the node with the new Trans
|
|
211
|
-
parentPath.replaceWith(buildTransElement(extracted, extracted.attributesToCopy, t, true));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function transAsJSX(parentPath, { attributes, children }, babel, { filename }) {
|
|
215
|
-
const defaultsAttr = findAttribute('defaults', attributes);
|
|
216
|
-
const componentsAttr = findAttribute('components', attributes);
|
|
217
|
-
// if there is "defaults" attribute and no "components" attribute, parse defaults and extract from the parsed defaults instead of children
|
|
218
|
-
// if a "components" attribute has been provided, we assume they have already constructed a valid "defaults" and it does not need to be parsed
|
|
219
|
-
const parseDefaults = defaultsAttr && !componentsAttr;
|
|
220
|
-
|
|
221
|
-
let extracted;
|
|
222
|
-
if (parseDefaults) {
|
|
223
|
-
const defaultsExpression = defaultsAttr.node.value.value;
|
|
224
|
-
const parsed = babel.parse(`<>${defaultsExpression}</>`, {
|
|
225
|
-
presets: ['@babel/react'],
|
|
226
|
-
filename,
|
|
227
|
-
}).program.body[0].expression.children;
|
|
228
|
-
|
|
229
|
-
extracted = processTrans(parsed, babel);
|
|
230
|
-
} else {
|
|
231
|
-
extracted = processTrans(children, babel);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
let clonedAttributes = cloneExistingAttributes(attributes);
|
|
235
|
-
if (parseDefaults) {
|
|
236
|
-
// remove existing defaults so it can be replaced later with the new parsed defaults
|
|
237
|
-
clonedAttributes = clonedAttributes.filter((node) => node.name.name !== 'defaults');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// replace the node with the new Trans
|
|
241
|
-
const replacePath = children.length ? children[0].parentPath : parentPath;
|
|
242
|
-
replacePath.replaceWith(
|
|
243
|
-
buildTransElement(extracted, clonedAttributes, babel.types, false, !!children.length),
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function buildTransElement(
|
|
248
|
-
extracted,
|
|
249
|
-
finalAttributes,
|
|
250
|
-
t,
|
|
251
|
-
closeDefaults = false,
|
|
252
|
-
wasElementWithChildren = false,
|
|
253
|
-
) {
|
|
254
|
-
const nodeName = t.jSXIdentifier('Trans');
|
|
255
|
-
|
|
256
|
-
// plural, select open { but do not close it while reduce
|
|
257
|
-
if (closeDefaults) extracted.defaults += '}';
|
|
258
|
-
|
|
259
|
-
// convert arrays into needed expressions
|
|
260
|
-
extracted.components = t.arrayExpression(extracted.components);
|
|
261
|
-
extracted.values = t.objectExpression(extracted.values);
|
|
262
|
-
|
|
263
|
-
// add generated Trans attributes
|
|
264
|
-
if (!attributeExistsAlready('defaults', finalAttributes))
|
|
265
|
-
if (extracted.defaults.includes(`"`)) {
|
|
266
|
-
// wrap defaults that contain double quotes in brackets
|
|
267
|
-
finalAttributes.push(
|
|
268
|
-
t.jSXAttribute(
|
|
269
|
-
t.jSXIdentifier('defaults'),
|
|
270
|
-
t.jSXExpressionContainer(t.StringLiteral(extracted.defaults)),
|
|
271
|
-
),
|
|
272
|
-
);
|
|
273
|
-
} else {
|
|
274
|
-
finalAttributes.push(
|
|
275
|
-
t.jSXAttribute(t.jSXIdentifier('defaults'), t.StringLiteral(extracted.defaults)),
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (!attributeExistsAlready('components', finalAttributes))
|
|
280
|
-
finalAttributes.push(
|
|
281
|
-
t.jSXAttribute(t.jSXIdentifier('components'), t.jSXExpressionContainer(extracted.components)),
|
|
282
|
-
);
|
|
283
|
-
if (!attributeExistsAlready('values', finalAttributes))
|
|
284
|
-
finalAttributes.push(
|
|
285
|
-
t.jSXAttribute(t.jSXIdentifier('values'), t.jSXExpressionContainer(extracted.values)),
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
// create selfclosing Trans component
|
|
289
|
-
const openElement = t.jSXOpeningElement(nodeName, finalAttributes, true);
|
|
290
|
-
if (!wasElementWithChildren) return openElement;
|
|
291
|
-
|
|
292
|
-
return t.jSXElement(openElement, null, [], true);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function cloneExistingAttributes(attributes) {
|
|
296
|
-
return attributes.reduce((mem, attr) => {
|
|
297
|
-
mem.push(attr.node);
|
|
298
|
-
return mem;
|
|
299
|
-
}, []);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function findAttribute(name, attributes) {
|
|
303
|
-
return attributes.find((child) => {
|
|
304
|
-
const ele = child.node ? child.node : child;
|
|
305
|
-
return ele.name.name === name;
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function attributeExistsAlready(name, attributes) {
|
|
310
|
-
return !!findAttribute(name, attributes);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function processTrans(children, babel, componentStartIndex = 0) {
|
|
314
|
-
const res = {};
|
|
315
|
-
|
|
316
|
-
res.defaults = mergeChildren(children, babel, componentStartIndex);
|
|
317
|
-
res.components = getComponents(children, babel);
|
|
318
|
-
res.values = getValues(children, babel);
|
|
319
|
-
|
|
320
|
-
return res;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const leadingNewLineAndWhitespace = /^\n\s+/g;
|
|
324
|
-
const trailingNewLineAndWhitespace = /\n\s+$/g;
|
|
325
|
-
function trimIndent(text) {
|
|
326
|
-
const newText = text
|
|
327
|
-
.replace(leadingNewLineAndWhitespace, '')
|
|
328
|
-
.replace(trailingNewLineAndWhitespace, '');
|
|
329
|
-
return newText;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* add comma-delimited expressions like `{ val, number }`
|
|
334
|
-
*/
|
|
335
|
-
function mergeCommaExpressions(ele) {
|
|
336
|
-
if (ele.expression && ele.expression.expressions) {
|
|
337
|
-
return `{${ele.expression.expressions
|
|
338
|
-
.reduce((m, i) => {
|
|
339
|
-
m.push(i.name || i.value);
|
|
340
|
-
return m;
|
|
341
|
-
}, [])
|
|
342
|
-
.join(', ')}}`;
|
|
343
|
-
}
|
|
344
|
-
return '';
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* this is for supporting complex icu type interpolations
|
|
349
|
-
* date`${variable}` and number`{${varName}, ::percent}`
|
|
350
|
-
* also, plural`{${count}, one { ... } other { ... }}
|
|
351
|
-
*/
|
|
352
|
-
function mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel) {
|
|
353
|
-
if (t.isTaggedTemplateExpression(ele.expression)) {
|
|
354
|
-
const [, text, index] = getTextAndInterpolatedVariables(
|
|
355
|
-
ele.expression.tag.name,
|
|
356
|
-
ele.expression,
|
|
357
|
-
componentFoundIndex,
|
|
358
|
-
babel,
|
|
359
|
-
);
|
|
360
|
-
return [text, index];
|
|
361
|
-
}
|
|
362
|
-
return ['', componentFoundIndex];
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function mergeChildren(children, babel, componentStartIndex = 0) {
|
|
366
|
-
const t = babel.types;
|
|
367
|
-
let componentFoundIndex = componentStartIndex;
|
|
368
|
-
|
|
369
|
-
return children.reduce((mem, child) => {
|
|
370
|
-
const ele = child.node ? child.node : child;
|
|
371
|
-
let result = mem;
|
|
372
|
-
|
|
373
|
-
// add text, but trim indentation whitespace
|
|
374
|
-
if (t.isJSXText(ele) && ele.value) result += trimIndent(ele.value);
|
|
375
|
-
// add ?!? forgot
|
|
376
|
-
if (ele.expression && ele.expression.value) result += ele.expression.value;
|
|
377
|
-
// add `{ val }`
|
|
378
|
-
if (ele.expression && ele.expression.name) result += `{${ele.expression.name}}`;
|
|
379
|
-
// add `{ val, number }`
|
|
380
|
-
result += mergeCommaExpressions(ele);
|
|
381
|
-
const [nextText, newIndex] = mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel);
|
|
382
|
-
result += nextText;
|
|
383
|
-
componentFoundIndex = newIndex;
|
|
384
|
-
// add <strong>...</strong> with replace to <0>inner string</0>
|
|
385
|
-
if (t.isJSXElement(ele)) {
|
|
386
|
-
result += `<${componentFoundIndex}>${mergeChildren(
|
|
387
|
-
ele.children,
|
|
388
|
-
babel,
|
|
389
|
-
)}</${componentFoundIndex}>`;
|
|
390
|
-
componentFoundIndex += 1;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return result;
|
|
394
|
-
}, '');
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const extractTaggedTemplateValues = (ele, babel, toObjectProperty) => {
|
|
398
|
-
// date`${variable}` and so on
|
|
399
|
-
if (ele.expression && ele.expression.type === 'TaggedTemplateExpression') {
|
|
400
|
-
const [variables] = getTextAndInterpolatedVariables(
|
|
401
|
-
ele.expression.tag.name,
|
|
402
|
-
ele.expression,
|
|
403
|
-
0,
|
|
404
|
-
babel,
|
|
405
|
-
);
|
|
406
|
-
return variables.map((vari) => toObjectProperty(vari));
|
|
407
|
-
}
|
|
408
|
-
return [];
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Extract the names of interpolated value as object properties to pass to Trans
|
|
413
|
-
*/
|
|
414
|
-
function getValues(children, babel) {
|
|
415
|
-
const t = babel.types;
|
|
416
|
-
const toObjectProperty = (name, value) =>
|
|
417
|
-
t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
|
|
418
|
-
|
|
419
|
-
return children.reduce((mem, child) => {
|
|
420
|
-
const ele = child.node ? child.node : child;
|
|
421
|
-
let result = mem;
|
|
422
|
-
|
|
423
|
-
// add `{ var }` to values
|
|
424
|
-
if (ele.expression && ele.expression.name) mem.push(toObjectProperty(ele.expression.name));
|
|
425
|
-
// add `{ var, number }` to values
|
|
426
|
-
if (ele.expression && ele.expression.expressions)
|
|
427
|
-
result.push(
|
|
428
|
-
toObjectProperty(ele.expression.expressions[0].name || ele.expression.expressions[0].value),
|
|
429
|
-
);
|
|
430
|
-
// add `{ var: 'bar' }` to values
|
|
431
|
-
if (ele.expression && ele.expression.properties)
|
|
432
|
-
result = result.concat(ele.expression.properties);
|
|
433
|
-
// date`${variable}` and so on
|
|
434
|
-
result = result.concat(extractTaggedTemplateValues(ele, babel, toObjectProperty));
|
|
435
|
-
// recursive add inner elements stuff to values
|
|
436
|
-
if (t.isJSXElement(ele)) {
|
|
437
|
-
result = result.concat(getValues(ele.children, babel));
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return result;
|
|
441
|
-
}, []);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Common logic for adding a child element of Trans to the list of components to hydrate the translation
|
|
446
|
-
* @param {JSXElement} jsxElement
|
|
447
|
-
* @param {JSXElement[]} mem
|
|
448
|
-
*/
|
|
449
|
-
const processJSXElement = (jsxElement, mem, t) => {
|
|
450
|
-
const clone = t.clone(jsxElement);
|
|
451
|
-
clone.children = clone.children.reduce((clonedMem, clonedChild) => {
|
|
452
|
-
const clonedEle = clonedChild.node ? clonedChild.node : clonedChild;
|
|
453
|
-
|
|
454
|
-
// clean out invalid definitions by replacing `{ catchDate, date, short }` with `{ catchDate }`
|
|
455
|
-
if (clonedEle.expression && clonedEle.expression.expressions)
|
|
456
|
-
clonedEle.expression.expressions = [clonedEle.expression.expressions[0]];
|
|
457
|
-
|
|
458
|
-
clonedMem.push(clonedChild);
|
|
459
|
-
return clonedMem;
|
|
460
|
-
}, []);
|
|
461
|
-
|
|
462
|
-
mem.push(jsxElement);
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Extract the React components to pass to Trans as components
|
|
467
|
-
*/
|
|
468
|
-
function getComponents(children, babel) {
|
|
469
|
-
const t = babel.types;
|
|
470
|
-
|
|
471
|
-
return children.reduce((mem, child) => {
|
|
472
|
-
const ele = child.node ? child.node : child;
|
|
473
|
-
|
|
474
|
-
if (t.isJSXExpressionContainer(ele)) {
|
|
475
|
-
// check for date`` and so on
|
|
476
|
-
if (t.isTaggedTemplateExpression(ele.expression)) {
|
|
477
|
-
ele.expression.quasi.expressions.forEach((expr) => {
|
|
478
|
-
// check for sub-expressions. This can happen with plural`` or select`` or selectOrdinal``
|
|
479
|
-
// these can have nested components
|
|
480
|
-
if (t.isTaggedTemplateExpression(expr) && expr.quasi.expressions.length) {
|
|
481
|
-
mem.push(...getComponents(expr.quasi.expressions, babel));
|
|
482
|
-
}
|
|
483
|
-
if (!t.isJSXElement(expr)) {
|
|
484
|
-
// ignore anything that is not a component
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
processJSXElement(expr, mem, t);
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (t.isJSXElement(ele)) {
|
|
492
|
-
processJSXElement(ele, mem, t);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return mem;
|
|
496
|
-
}, []);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const icuInterpolators = ['date', 'time', 'number', 'plural', 'select', 'selectOrdinal'];
|
|
500
|
-
const importsToAdd = ['Trans'];
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* helper split out of addNeededImports to make codeclimate happy
|
|
504
|
-
*
|
|
505
|
-
* This does the work of amending an existing import from "react-i18next", or
|
|
506
|
-
* creating a new one if it doesn't exist
|
|
507
|
-
*/
|
|
508
|
-
function addImports(state, existingImport, allImportsToAdd, t) {
|
|
509
|
-
// append imports to existing or add a new react-i18next import for the Trans and icu tagged template literals
|
|
510
|
-
if (existingImport) {
|
|
511
|
-
allImportsToAdd.forEach((name) => {
|
|
512
|
-
if (
|
|
513
|
-
existingImport.specifiers.findIndex(
|
|
514
|
-
(specifier) => specifier.imported && specifier.imported.name === name,
|
|
515
|
-
) === -1
|
|
516
|
-
) {
|
|
517
|
-
existingImport.specifiers.push(t.importSpecifier(t.identifier(name), t.identifier(name)));
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
} else {
|
|
521
|
-
state.file.path.node.body.unshift(
|
|
522
|
-
t.importDeclaration(
|
|
523
|
-
allImportsToAdd.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name))),
|
|
524
|
-
t.stringLiteral('react-i18next'),
|
|
525
|
-
),
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Add `import { Trans, number, date, <etc.> } from "react-i18next"` as needed
|
|
532
|
-
*/
|
|
533
|
-
function addNeededImports(state, babel, references) {
|
|
534
|
-
const t = babel.types;
|
|
535
|
-
|
|
536
|
-
// check if there is an existing react-i18next import
|
|
537
|
-
const existingImport = state.file.path.node.body.find(
|
|
538
|
-
(importNode) =>
|
|
539
|
-
t.isImportDeclaration(importNode) && importNode.source.value === 'react-i18next',
|
|
540
|
-
);
|
|
541
|
-
// check for any of the tagged template literals that are used in the source, and add them
|
|
542
|
-
const usedRefs = Object.keys(references).filter((importName) => {
|
|
543
|
-
if (!icuInterpolators.includes(importName)) {
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
return references[importName].length;
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
// combine Trans + any tagged template literals
|
|
550
|
-
const allImportsToAdd = importsToAdd.concat(usedRefs);
|
|
551
|
-
|
|
552
|
-
addImports(state, existingImport, allImportsToAdd, t);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* iterate over a node detected inside a tagged template literal
|
|
557
|
-
*
|
|
558
|
-
* This is a helper function for `extractVariableNamesFromQuasiNodes` defined below
|
|
559
|
-
*
|
|
560
|
-
* this is called using reduce as a way of tricking what would be `.map()`
|
|
561
|
-
* into passing in the parameters needed to both modify `componentFoundIndex`,
|
|
562
|
-
* `stringOutput`, and `interpolatedVariableNames`
|
|
563
|
-
* and to pass in the dependencies babel, and type. Type is the template type.
|
|
564
|
-
* For "date``" the type will be `date`. for "number``" the type is `number`, etc.
|
|
565
|
-
*/
|
|
566
|
-
const extractNestedTemplatesAndComponents = (
|
|
567
|
-
{ componentFoundIndex: lastIndex, babel, stringOutput, type, interpolatedVariableNames },
|
|
568
|
-
node,
|
|
569
|
-
) => {
|
|
570
|
-
let componentFoundIndex = lastIndex;
|
|
571
|
-
if (node.type === 'JSXElement') {
|
|
572
|
-
// perform the interpolation of components just as we do in a normal Trans setting
|
|
573
|
-
const subText = `<${componentFoundIndex}>${mergeChildren(
|
|
574
|
-
node.children,
|
|
575
|
-
babel,
|
|
576
|
-
)}</${componentFoundIndex}>`;
|
|
577
|
-
componentFoundIndex += 1;
|
|
578
|
-
stringOutput.push(subText);
|
|
579
|
-
} else if (node.type === 'TaggedTemplateExpression') {
|
|
580
|
-
// a nested date``/number``/plural`` etc., extract whatever is inside of it
|
|
581
|
-
const [variableNames, childText, newIndex] = getTextAndInterpolatedVariables(
|
|
582
|
-
node.tag.name,
|
|
583
|
-
node,
|
|
584
|
-
componentFoundIndex,
|
|
585
|
-
babel,
|
|
586
|
-
);
|
|
587
|
-
interpolatedVariableNames.push(...variableNames);
|
|
588
|
-
componentFoundIndex = newIndex;
|
|
589
|
-
stringOutput.push(childText);
|
|
590
|
-
} else if (node.type === 'Identifier') {
|
|
591
|
-
// turn date`${thing}` into `thing, date`
|
|
592
|
-
stringOutput.push(`${node.name}, ${type}`);
|
|
593
|
-
} else if (node.type === 'TemplateElement') {
|
|
594
|
-
// convert all whitespace into a single space for the text in the tagged template literal
|
|
595
|
-
stringOutput.push(node.value.cooked.replace(/\s+/g, ' '));
|
|
596
|
-
} else {
|
|
597
|
-
// unknown node type, ignore
|
|
598
|
-
}
|
|
599
|
-
return { componentFoundIndex, babel, stringOutput, type, interpolatedVariableNames };
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* filter the list of nodes within a tagged template literal to the 4 types we can process,
|
|
604
|
-
* and ignore anything else.
|
|
605
|
-
*
|
|
606
|
-
* this is a helper function for `extractVariableNamesFromQuasiNodes`
|
|
607
|
-
*/
|
|
608
|
-
const filterNodes = (node) => {
|
|
609
|
-
if (node.type === 'Identifier') {
|
|
610
|
-
// if the node has a name, keep it
|
|
611
|
-
return node.name;
|
|
612
|
-
}
|
|
613
|
-
if (node.type === 'JSXElement' || node.type === 'TaggedTemplateExpression') {
|
|
614
|
-
// always keep interpolated elements or other tagged template literals like a nested date`` inside a plural``
|
|
615
|
-
return true;
|
|
616
|
-
}
|
|
617
|
-
if (node.type === 'TemplateElement') {
|
|
618
|
-
// return the "cooked" (escaped) text for the text in the template literal (`, ::percent` in number`${varname}, ::percent`)
|
|
619
|
-
return node.value.cooked;
|
|
620
|
-
}
|
|
621
|
-
// unknown node type, ignore
|
|
622
|
-
return false;
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
const errorOnInvalidQuasiNodes = (primaryNode) => {
|
|
626
|
-
const noInterpolationError = !primaryNode.quasi.expressions.length;
|
|
627
|
-
const wrongOrderError = primaryNode.quasi.quasis[0].value.raw.length;
|
|
628
|
-
const message = `${primaryNode.tag.name} argument must be interpolated ${
|
|
629
|
-
noInterpolationError ? 'in' : 'at the beginning of'
|
|
630
|
-
} "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${
|
|
631
|
-
primaryNode.loc.start.line
|
|
632
|
-
}`;
|
|
633
|
-
if (noInterpolationError || wrongOrderError) {
|
|
634
|
-
throw new Error(message);
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
const extractNodeVariableNames = (varNode, babel) => {
|
|
639
|
-
const interpolatedVariableNames = [];
|
|
640
|
-
if (varNode.type === 'JSXElement') {
|
|
641
|
-
// extract inner interpolated variables and add to the list
|
|
642
|
-
interpolatedVariableNames.push(
|
|
643
|
-
...getValues(varNode.children, babel).map((value) => value.value.name),
|
|
644
|
-
);
|
|
645
|
-
} else if (varNode.type === 'Identifier') {
|
|
646
|
-
// the name of the interpolated variable
|
|
647
|
-
interpolatedVariableNames.push(varNode.name);
|
|
648
|
-
}
|
|
649
|
-
return interpolatedVariableNames;
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
const extractVariableNamesFromQuasiNodes = (primaryNode, babel) => {
|
|
653
|
-
errorOnInvalidQuasiNodes(primaryNode);
|
|
654
|
-
// this will contain all the nodes to convert to the ICU messageformat text
|
|
655
|
-
// at first they are unsorted, but will be ordered correctly at the end of the function
|
|
656
|
-
const text = [];
|
|
657
|
-
// the variable names. These are converted to object references as required for the Trans values
|
|
658
|
-
// in getValues() (toObjectProperty helper function)
|
|
659
|
-
const interpolatedVariableNames = [];
|
|
660
|
-
primaryNode.quasi.expressions.forEach((varNode) => {
|
|
661
|
-
if (
|
|
662
|
-
!babel.types.isIdentifier(varNode) &&
|
|
663
|
-
!babel.types.isTaggedTemplateExpression(varNode) &&
|
|
664
|
-
!babel.types.isJSXElement(varNode)
|
|
665
|
-
) {
|
|
666
|
-
throw new Error(
|
|
667
|
-
`Must pass a variable, not an expression to "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${primaryNode.loc.start.line}`,
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
text.push(varNode);
|
|
671
|
-
interpolatedVariableNames.push(...extractNodeVariableNames(varNode, babel));
|
|
672
|
-
});
|
|
673
|
-
primaryNode.quasi.quasis.forEach((quasiNode) => {
|
|
674
|
-
// these are the text surrounding the variable interpolation
|
|
675
|
-
// so in date`${varname}, short` it would be `''` and `, short`.
|
|
676
|
-
// (the empty string before `${varname}` and the stuff after it)
|
|
677
|
-
text.push(quasiNode);
|
|
678
|
-
});
|
|
679
|
-
return { text, interpolatedVariableNames };
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
const throwOnInvalidType = (type, primaryNode) => {
|
|
683
|
-
if (!icuInterpolators.includes(type)) {
|
|
684
|
-
throw new Error(
|
|
685
|
-
`Unsupported tagged template literal "${type}", must be one of date, time, number, plural, select, selectOrdinal in "${primaryNode.loc.filename}" on line ${primaryNode.loc.start.line}`,
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* Retrieve the new text to use, and any interpolated variables
|
|
692
|
-
*
|
|
693
|
-
* This is used to process tagged template literals like date`${variable}` and number`${num}, ::percent`
|
|
694
|
-
*
|
|
695
|
-
* for the data example, it will return text of `{variable, date}` with a variable of `variable`
|
|
696
|
-
* for the number example, it will return text of `{num, number, ::percent}` with a variable of `num`
|
|
697
|
-
* @param {string} type the name of the tagged template (`date`, `number`, `plural`, etc. - any valid complex ICU type)
|
|
698
|
-
* @param {TaggedTemplateExpression} primaryNode the template expression node
|
|
699
|
-
* @param {int} index starting index number of components to be used for interpolations like <0>
|
|
700
|
-
* @param {*} babel
|
|
701
|
-
*/
|
|
702
|
-
function getTextAndInterpolatedVariables(type, primaryNode, index, babel) {
|
|
703
|
-
throwOnInvalidType(type, primaryNode);
|
|
704
|
-
const componentFoundIndex = index;
|
|
705
|
-
const { text, interpolatedVariableNames } = extractVariableNamesFromQuasiNodes(
|
|
706
|
-
primaryNode,
|
|
707
|
-
babel,
|
|
708
|
-
);
|
|
709
|
-
const { stringOutput, componentFoundIndex: newIndex } = text
|
|
710
|
-
.filter(filterNodes)
|
|
711
|
-
// sort by the order they appear in the source code
|
|
712
|
-
.sort((a, b) => {
|
|
713
|
-
if (a.start > b.start) return 1;
|
|
714
|
-
return -1;
|
|
715
|
-
})
|
|
716
|
-
.reduce(extractNestedTemplatesAndComponents, {
|
|
717
|
-
babel,
|
|
718
|
-
componentFoundIndex,
|
|
719
|
-
stringOutput: [],
|
|
720
|
-
type,
|
|
721
|
-
interpolatedVariableNames,
|
|
722
|
-
});
|
|
723
|
-
return [
|
|
724
|
-
interpolatedVariableNames,
|
|
725
|
-
`{${stringOutput.join('')}}`,
|
|
726
|
-
// return the new component interpolation index
|
|
727
|
-
newIndex,
|
|
728
|
-
];
|
|
729
|
-
}
|