@oracle/oraclejet-icu-l10n 16.0.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/Bundler.d.ts +46 -0
- package/Bundler.js +312 -0
- package/PatternCompiler.d.ts +83 -0
- package/PatternCompiler.js +253 -0
- package/README.md +321 -0
- package/l10nBundleBuilder.js +88 -0
- package/package.json +30 -0
- package/test/bundleTypeTest.ts +40 -0
- package/test/cliTest.js +48 -0
- package/test/jest.config.js +9 -0
- package/test/l10nTest.js +492 -0
- package/test/resources/CustomMessageType.d.ts +6 -0
- package/test/resources/custom-hooks-no-def.js +3 -0
- package/test/resources/custom-hooks.js +8 -0
- package/test/resources/escape-util.js +3 -0
- package/test/resources/nls/app-strings-legacy-keys.json +5 -0
- package/test/resources/nls/app-strings-x.json +4 -0
- package/test/resources/nls/app-strings.json +48 -0
- package/test/resources/nls/azb/app-strings.json +3 -0
- package/test/resources/nls/azb-AZ/app-strings.json +3 -0
- package/test/resources/nls/ro-MD/app-strings.json +3 -0
- package/test/resources/nls/ru/app-strings.json +4 -0
- package/test/resources/nls/ru-RU/app-strings-x.json +3 -0
- package/test/resources/nls/ru-RU/app-strings.json +3 -0
- package/test/resources/nls/zh/app-strings.json +3 -0
- package/test/resources/nls/zh-Hans/app-strings.json +2 -0
- package/test/resources/nls/zh-Hans/extra-app-strings-x.json +3 -0
- package/test/resources/nls/zh-Hans-CN/app-strings.json +2 -0
- package/test/resources/nls-bad-metadata/app-strings-bad-root.json +11 -0
- package/test/resources/nls-extra-keys/README.md +3 -0
- package/test/resources/nls-extra-keys/app-bundle-bad.json +4 -0
- package/test/resources/nls-extra-keys/en/app-bundle-bad.json +3 -0
- package/tsconfig.json +7 -0
package/test/l10nTest.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { build, isNlsDir } = require('../Bundler');
|
|
5
|
+
const fsx = require('fs-extra');
|
|
6
|
+
const vm = require('vm');
|
|
7
|
+
const glob = require('glob');
|
|
8
|
+
|
|
9
|
+
const rootDir = path.join(__dirname, 'resources/nls');
|
|
10
|
+
const outputRoot = path.resolve(__dirname, 'built');
|
|
11
|
+
|
|
12
|
+
function runTestCases(extractBundle) {
|
|
13
|
+
describe('Bundle files', () => {
|
|
14
|
+
it('creates the root bundle in English', () => {
|
|
15
|
+
const bundle = extractBundle(`app-strings.js`);
|
|
16
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard Content Area');
|
|
17
|
+
assert.equal(
|
|
18
|
+
bundle.pageIntro({ name: 'Dashboard' }),
|
|
19
|
+
'To change the content of this section, you will make edits to the Dashboard file located in the /js/views folder.'
|
|
20
|
+
);
|
|
21
|
+
assert.equal(bundle.alpha(), 'Alpha');
|
|
22
|
+
assert.equal(bundle.beta(), 'Beta');
|
|
23
|
+
assert.equal(bundle.delta(), 'Delta');
|
|
24
|
+
assert.equal(bundle.epsilon(), 'Epsilon');
|
|
25
|
+
assert.equal(bundle.blank(), '');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('creates the azb bundle in Azerbaijani', () => {
|
|
29
|
+
const bundle = extractBundle('azb/app-strings.js');
|
|
30
|
+
assert.equal(bundle.welcome(), 'Xoş gəlmisiniz');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('creates the azb-AZ bundle in Azerbaijani', () => {
|
|
34
|
+
const bundle = extractBundle('azb-AZ/app-strings.js');
|
|
35
|
+
assert.equal(bundle.welcome(), 'Xoş gəlmisiniz');
|
|
36
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard Məzmun sahəsi');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('creates the ru bundle in Russian', () => {
|
|
40
|
+
const bundle = extractBundle(`ru/app-strings.js`);
|
|
41
|
+
// Derived value from root bundle--in English
|
|
42
|
+
assert.equal(bundle.welcome(), 'Welcome');
|
|
43
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard область содержимого');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('creates the ru-RU bundle in Russian', () => {
|
|
47
|
+
const bundle = extractBundle(`ru-RU/app-strings.js`);
|
|
48
|
+
// Derived value from root bundle--in English
|
|
49
|
+
assert.equal(bundle.welcome(), 'Welcome');
|
|
50
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard область содержимого');
|
|
51
|
+
assert.equal(
|
|
52
|
+
bundle.pageIntro({ name: 'Dashboard' }),
|
|
53
|
+
'Чтобы изменить содержимое этого раздела, вы внесете изменения в файл Dashboard, расположенный в папке /js/views.'
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('creates the zh bundle in Russian', () => {
|
|
58
|
+
const bundle = extractBundle(`zh/app-strings.js`);
|
|
59
|
+
// Derived value from root bundle--in English
|
|
60
|
+
assert.equal(bundle.welcome(), '欢迎');
|
|
61
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard Content Area');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('creates the zh-Hans bundle in Russian', () => {
|
|
65
|
+
const bundle = extractBundle(`zh-Hans/app-strings.js`);
|
|
66
|
+
// Derived value from root bundle--in English
|
|
67
|
+
assert.equal(bundle.welcome(), '欢迎');
|
|
68
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard Content Area');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('creates the zh-Hans-CN bundle in Russian', () => {
|
|
72
|
+
const bundle = extractBundle(`zh-Hans-CN/app-strings.js`);
|
|
73
|
+
// Derived value from root bundle--in English
|
|
74
|
+
assert.equal(bundle.welcome(), '欢迎');
|
|
75
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard Content Area');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('creates parameterized plural values', () => {
|
|
79
|
+
const bundle = extractBundle(`ru-RU/app-strings.js`);
|
|
80
|
+
assert.equal(
|
|
81
|
+
bundle.invitation({
|
|
82
|
+
host: 'Barbara',
|
|
83
|
+
guest: 'Phil',
|
|
84
|
+
gender_of_host: 'female',
|
|
85
|
+
num_guests: 0
|
|
86
|
+
}),
|
|
87
|
+
'Barbara does not give a party.'
|
|
88
|
+
);
|
|
89
|
+
assert.equal(
|
|
90
|
+
bundle.invitation({
|
|
91
|
+
host: 'Barbara',
|
|
92
|
+
guest: 'Phil',
|
|
93
|
+
gender_of_host: 'female',
|
|
94
|
+
num_guests: 1
|
|
95
|
+
}),
|
|
96
|
+
'Barbara invites Phil to her party.'
|
|
97
|
+
);
|
|
98
|
+
assert.equal(
|
|
99
|
+
bundle.invitation({
|
|
100
|
+
host: 'Barbara',
|
|
101
|
+
guest: 'Phil',
|
|
102
|
+
gender_of_host: 'female',
|
|
103
|
+
num_guests: 2
|
|
104
|
+
}),
|
|
105
|
+
'Barbara invites Phil and one other person to her party.'
|
|
106
|
+
);
|
|
107
|
+
assert.equal(
|
|
108
|
+
bundle.invitation({
|
|
109
|
+
host: 'Bob',
|
|
110
|
+
guest: 'Sue',
|
|
111
|
+
gender_of_host: 'male',
|
|
112
|
+
num_guests: 3
|
|
113
|
+
}),
|
|
114
|
+
'Bob invites Sue and 2 other people to his party.'
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('uses plural select rules for buckets', () => {
|
|
119
|
+
const enBundle = extractBundle(`app-strings.js`);
|
|
120
|
+
assert.equal(
|
|
121
|
+
enBundle.plural({
|
|
122
|
+
total_rows: 0
|
|
123
|
+
}),
|
|
124
|
+
'No rows'
|
|
125
|
+
);
|
|
126
|
+
assert.equal(
|
|
127
|
+
enBundle.plural({
|
|
128
|
+
total_rows: 1
|
|
129
|
+
}),
|
|
130
|
+
'1 row'
|
|
131
|
+
);
|
|
132
|
+
assert.equal(
|
|
133
|
+
enBundle.plural({
|
|
134
|
+
total_rows: 2
|
|
135
|
+
}),
|
|
136
|
+
'2 rows'
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const ruBundle = extractBundle(`ru-RU/app-strings.js`);
|
|
140
|
+
assert.equal(
|
|
141
|
+
ruBundle.plural({
|
|
142
|
+
total_rows: 0
|
|
143
|
+
}),
|
|
144
|
+
'нет строк'
|
|
145
|
+
);
|
|
146
|
+
assert.equal(
|
|
147
|
+
ruBundle.plural({
|
|
148
|
+
total_rows: 1
|
|
149
|
+
}),
|
|
150
|
+
'1 ряд'
|
|
151
|
+
);
|
|
152
|
+
assert.equal(
|
|
153
|
+
ruBundle.plural({
|
|
154
|
+
total_rows: 2
|
|
155
|
+
}),
|
|
156
|
+
'2 ряда'
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('Number formats', () => {
|
|
162
|
+
const bundle = extractBundle('app-strings.js');
|
|
163
|
+
|
|
164
|
+
it('formats date/time', () => {
|
|
165
|
+
const testDate = '2022-09-29 07:25 GMT';
|
|
166
|
+
assert.equal(
|
|
167
|
+
bundle.dateFormat({
|
|
168
|
+
date_time: testDate,
|
|
169
|
+
item: 'an alien',
|
|
170
|
+
planet: 5
|
|
171
|
+
}),
|
|
172
|
+
'At 25 past, on September 29, there was an alien on planet 5.'
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('formats currency', () => {
|
|
177
|
+
assert.equal(bundle.currencyFormat({ gbp: 99 }), 'The budget is +£99 and no more.');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('Metadata', () => {
|
|
182
|
+
const bundle = extractBundle('ro-MD/app-strings.js');
|
|
183
|
+
|
|
184
|
+
it('redefining parameter type should cause failure', () => {
|
|
185
|
+
let didFail = false;
|
|
186
|
+
try {
|
|
187
|
+
build({
|
|
188
|
+
rootDir: path.join(`${rootDir}-bad-metadata`),
|
|
189
|
+
locale: 'ro-MD',
|
|
190
|
+
bundleName: 'app-strings-bad-root.json',
|
|
191
|
+
outDir: path.join(outputRoot, 'nls-bad-metadata')
|
|
192
|
+
});
|
|
193
|
+
} catch (ex) {
|
|
194
|
+
didFail = !!ex.message.match(/While processing ro-MD\/app-strings-bad-root.json/);
|
|
195
|
+
}
|
|
196
|
+
assert(didFail === true);
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('metadata keys should not be in bundle', () => {
|
|
200
|
+
assert.strictEqual(Object.keys(bundle).every(k => !k.startsWith('@')), true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('metadata parameters present in locale bundle', () => {
|
|
204
|
+
const birthDate = '2022-09-29 07:25 GMT';
|
|
205
|
+
assert.equal(bundle.localeParams({
|
|
206
|
+
total: 1,
|
|
207
|
+
gender: 'male',
|
|
208
|
+
title: 'Mr.',
|
|
209
|
+
birthDate
|
|
210
|
+
}), 'Moldavian total: 1 unu, gender: male, title: Mr., birthDate: 29.09.2022');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
describe('Custom hooks', () => {
|
|
217
|
+
it('can produce custom return types', () => {
|
|
218
|
+
const outDir = path.resolve(outputRoot, 'custom-hooks');
|
|
219
|
+
fsx.removeSync(outDir);
|
|
220
|
+
build({
|
|
221
|
+
rootDir,
|
|
222
|
+
bundleName: 'app-strings.json',
|
|
223
|
+
locale: 'en-US',
|
|
224
|
+
outDir,
|
|
225
|
+
hooks: require.resolve('./resources/custom-hooks.js'),
|
|
226
|
+
module: 'cjs'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const bundle = extractCjsBundle(outDir, 'app-strings.js').default;
|
|
230
|
+
expect(bundle.welcome().bundle).toEqual('app-strings.json');
|
|
231
|
+
expect(bundle.welcome().key).toEqual('welcome');
|
|
232
|
+
expect(bundle.welcome().params).toBeUndefined();
|
|
233
|
+
expect(bundle.welcome().value).toEqual('Welcome');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('can produce custom return types without definition', () => {
|
|
237
|
+
const outDir = path.resolve(outputRoot, 'custom-hooks-no-def');
|
|
238
|
+
fsx.removeSync(outDir);
|
|
239
|
+
build({
|
|
240
|
+
rootDir,
|
|
241
|
+
bundleName: 'app-strings.json',
|
|
242
|
+
locale: 'en-US',
|
|
243
|
+
outDir,
|
|
244
|
+
hooks: require.resolve('./resources/custom-hooks-no-def.js'),
|
|
245
|
+
module: 'cjs'
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const bundle = extractCjsBundle(outDir, 'app-strings.js').default;
|
|
249
|
+
expect(bundle.welcome().bundleId).toEqual('app-strings.json');
|
|
250
|
+
expect(bundle.welcome().id).toEqual('welcome');
|
|
251
|
+
expect(bundle.welcome().params).toBeUndefined();
|
|
252
|
+
expect(bundle.welcome().translation).toEqual('Welcome');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Read contents of CJS bundle
|
|
258
|
+
* @param {string} outDir
|
|
259
|
+
* @param {string} filePath
|
|
260
|
+
*/
|
|
261
|
+
function extractCjsBundle(outDir, filepath) {
|
|
262
|
+
return require(path.join(outDir, filepath));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
describe('CJS default export', () => {
|
|
266
|
+
const outDir = path.resolve(outputRoot, 'cjs');
|
|
267
|
+
|
|
268
|
+
fsx.removeSync(outDir);
|
|
269
|
+
build({
|
|
270
|
+
rootDir,
|
|
271
|
+
bundleName: 'app-strings.json',
|
|
272
|
+
locale: 'en-US',
|
|
273
|
+
outDir,
|
|
274
|
+
module: 'cjs'
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
runTestCases((fp) => extractCjsBundle(outDir, fp).default);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('AMD', () => {
|
|
281
|
+
/**
|
|
282
|
+
* Read contents of ES6 file and convert to CommonJS export for Node testing
|
|
283
|
+
* @param {string} filepath
|
|
284
|
+
*/
|
|
285
|
+
function extractBundle(filepath, exportType) {
|
|
286
|
+
const bundleContents = fsx.readFileSync(filepath).toString();
|
|
287
|
+
// Create a 'define' method in the context which returns the object passed in
|
|
288
|
+
const script = new vm.Script(bundleContents, '');
|
|
289
|
+
const context = vm.createContext({
|
|
290
|
+
define: function (rb) {
|
|
291
|
+
// define({ ... })
|
|
292
|
+
if (arguments.length === 1) {
|
|
293
|
+
return rb;
|
|
294
|
+
}
|
|
295
|
+
// define(['require', 'exports'], function(require, exports))
|
|
296
|
+
const exp = {};
|
|
297
|
+
// pass exports for every argument
|
|
298
|
+
const args = new Array(arguments[0].length);
|
|
299
|
+
const ret = arguments[1](...args.fill(exp));
|
|
300
|
+
// AMD may return the value or assign to 'exports'
|
|
301
|
+
const resource = ret || exp;
|
|
302
|
+
return exportType === 'default' ? resource.default : resource;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const rb = script.runInContext(context);
|
|
306
|
+
return rb;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function testAmd(exportType) {
|
|
310
|
+
const outDir = path.resolve(outputRoot, `amd-${exportType}`);
|
|
311
|
+
|
|
312
|
+
fsx.removeSync(outDir);
|
|
313
|
+
build({
|
|
314
|
+
rootDir,
|
|
315
|
+
bundleName: 'app-strings.json',
|
|
316
|
+
locale: 'en-US',
|
|
317
|
+
outDir,
|
|
318
|
+
module: 'amd',
|
|
319
|
+
exportType
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
runTestCases((fp) => extractBundle(path.join(outDir, fp), exportType));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
describe('default output', () => {
|
|
326
|
+
testAmd('default');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('named output', () => {
|
|
330
|
+
testAmd('named');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('legacy-amd', () => {
|
|
334
|
+
const outDir = path.resolve(outputRoot, 'legacy-amd');
|
|
335
|
+
|
|
336
|
+
beforeAll(() => {
|
|
337
|
+
fsx.removeSync(outDir);
|
|
338
|
+
build({
|
|
339
|
+
rootDir,
|
|
340
|
+
bundleName: 'app-strings-legacy-keys.json',
|
|
341
|
+
locale: 'en-US',
|
|
342
|
+
outDir,
|
|
343
|
+
module: 'legacy-amd'
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('does not contain ts output', () => {
|
|
348
|
+
expect(glob.sync(path.join(outDir, '**', '*.ts'))).toEqual([]);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('creates non-standard keys', () => {
|
|
352
|
+
const bundle = extractBundle(
|
|
353
|
+
path.join(outDir, 'app-strings-legacy-keys.js'),
|
|
354
|
+
'named'
|
|
355
|
+
);
|
|
356
|
+
expect(bundle['dot.key']()).toEqual('Dot key');
|
|
357
|
+
expect(bundle['.leading.dot.key']()).toEqual('Leading dot key');
|
|
358
|
+
expect(bundle['dash-key']()).toEqual('Dash key');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
describe('Extra locales', () => {
|
|
364
|
+
const outDir = path.resolve(outputRoot, 'extra');
|
|
365
|
+
const additionalLocales = ['de', 'de-DE', 'fr-FR', 'ru', 'ru-RU'];
|
|
366
|
+
const allLocales = [
|
|
367
|
+
...new Set(fsx.readdirSync(rootDir).filter(isNlsDir).concat(additionalLocales).sort())
|
|
368
|
+
];
|
|
369
|
+
|
|
370
|
+
fsx.removeSync(outDir);
|
|
371
|
+
build({
|
|
372
|
+
rootDir,
|
|
373
|
+
bundleName: 'app-strings.json',
|
|
374
|
+
locale: 'en-US',
|
|
375
|
+
outDir,
|
|
376
|
+
module: 'cjs',
|
|
377
|
+
additionalLocales
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const extractBundle = (fp) => extractCjsBundle(outDir, fp).default;
|
|
381
|
+
runTestCases(extractBundle);
|
|
382
|
+
|
|
383
|
+
it('creates the de bundle in English', () => {
|
|
384
|
+
const bundle = extractBundle('de/app-strings.js');
|
|
385
|
+
assert.equal(bundle.welcome(), 'Welcome');
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('creates the de-DE bundle in English', () => {
|
|
389
|
+
const bundle = extractBundle('de-DE/app-strings.js');
|
|
390
|
+
assert.equal(bundle.welcome(), 'Welcome');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('creates the fr-FR bundle in English', () => {
|
|
394
|
+
const bundle = extractBundle('fr-FR/app-strings.js');
|
|
395
|
+
assert.equal(bundle.welcome(), 'Welcome');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('exports the list of supported locales', () => {
|
|
399
|
+
const supportedLocales = extractBundle('supportedLocales.js');
|
|
400
|
+
assert.deepEqual(supportedLocales, allLocales);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('Override files', () => {
|
|
405
|
+
const outDir = path.resolve(outputRoot, 'override');
|
|
406
|
+
const extractBundle = (fp) => extractCjsBundle(outDir, fp).default;
|
|
407
|
+
|
|
408
|
+
describe('with root overrides', () => {
|
|
409
|
+
build({
|
|
410
|
+
rootDir,
|
|
411
|
+
bundleName: 'app-strings-x.json',
|
|
412
|
+
locale: 'en-US',
|
|
413
|
+
override: true,
|
|
414
|
+
additionalLocales: ['en', 'ru-RU'],
|
|
415
|
+
outDir,
|
|
416
|
+
module: 'cjs'
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('creates the root override bundle in English', () => {
|
|
420
|
+
const bundle = extractBundle(`app-strings-x.js`);
|
|
421
|
+
assert.equal(bundle.pageIntro(), 'root pageIntro');
|
|
422
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard root contentArea');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('creates the en override bundle from root', () => {
|
|
426
|
+
const bundle = extractBundle(`en/app-strings-x.js`);
|
|
427
|
+
assert.equal(bundle.pageIntro(), 'root pageIntro');
|
|
428
|
+
assert.equal(bundle.contentArea({ name: 'Dashboard' }), 'Dashboard root contentArea');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('creates the ru-RU override bundle in Russian', () => {
|
|
432
|
+
const bundle = extractBundle(`ru-RU/app-strings-x.js`);
|
|
433
|
+
assert.equal(Object.keys(bundle).length, 2, "override should have root keys");
|
|
434
|
+
assert.equal(
|
|
435
|
+
bundle.contentArea({ name: 'Dashboard' }), 'Dashboard ru-RU contentArea override');
|
|
436
|
+
assert.equal(bundle.pageIntro(), 'root pageIntro');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should not create overrides in any other locales', () => {
|
|
440
|
+
let exists = false;
|
|
441
|
+
try {
|
|
442
|
+
extractBundle('ru/app-strings-x.js');
|
|
443
|
+
exists = true;
|
|
444
|
+
} catch (ex) {}
|
|
445
|
+
try {
|
|
446
|
+
extractBundle('fr/app-strings-x.js');
|
|
447
|
+
exists = true;
|
|
448
|
+
} catch (ex) {}
|
|
449
|
+
expect(exists).toBeFalsy();
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe('without root overrides', () => {
|
|
454
|
+
build({
|
|
455
|
+
rootDir,
|
|
456
|
+
bundleName: 'extra-app-strings-x.json',
|
|
457
|
+
override: true,
|
|
458
|
+
additionalLocales: ['zh-Hans'],
|
|
459
|
+
outDir,
|
|
460
|
+
module: 'cjs'
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('creates only zh-Hans overrides', () => {
|
|
464
|
+
const generatedFiles = glob.sync(`${outDir}/**/extra-app-strings-x.js`);
|
|
465
|
+
expect(generatedFiles.length).toEqual(1);
|
|
466
|
+
|
|
467
|
+
const bundle = extractBundle('zh-Hans/extra-app-strings-x.js');
|
|
468
|
+
assert.equal(Object.keys(bundle).length, 1, "override should not have root keys");
|
|
469
|
+
assert.equal(
|
|
470
|
+
bundle.contentArea({ name: 'Dashboard' }), 'Dashboard zh-Hans contentArea override');
|
|
471
|
+
})
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
describe('extra keys', () => {
|
|
477
|
+
const outDir = path.resolve(outputRoot, 'extra-keys');
|
|
478
|
+
|
|
479
|
+
it('should fail if locale bundle has extra keys', () => {
|
|
480
|
+
let didFail = false;
|
|
481
|
+
try {
|
|
482
|
+
build({
|
|
483
|
+
rootDir: `${rootDir}-extra-keys`,
|
|
484
|
+
bundleName: 'app-bundle-bad.json',
|
|
485
|
+
outDir
|
|
486
|
+
});
|
|
487
|
+
} catch (ex) {
|
|
488
|
+
didFail = ex.message.indexOf('description') >= 0;
|
|
489
|
+
}
|
|
490
|
+
expect(didFail).toBeTruthy();
|
|
491
|
+
})
|
|
492
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
typeImport: {
|
|
3
|
+
CustomMessageType: 'import { CustomMessageType } from "../../resources/CustomMessageType";'
|
|
4
|
+
},
|
|
5
|
+
otherImports: 'import { escape } from "../../resources/escape-util";',
|
|
6
|
+
// remap some of the parameter names to different keys
|
|
7
|
+
convertor: '(args: ParamsType): CustomMessageType => ({bundle: args.bundleId, key: args.id, params: args.params, value: escape(args.translation)})'
|
|
8
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@count": {
|
|
3
|
+
"type": "number"
|
|
4
|
+
},
|
|
5
|
+
"@@x-base-bundle": true,
|
|
6
|
+
"welcome": "Welcome",
|
|
7
|
+
"contentArea": "{name} Content Area",
|
|
8
|
+
"pageIntro": "To change the content of this section, you will make edits to the {name} file located in the /js/views folder.",
|
|
9
|
+
"invitation": "{gender_of_host, select, female {{num_guests, plural, offset:1 =0 {{host} does not give a party.} =1 {{host} invites {guest} to her party.} =2 {{host} invites {guest} and one other person to her party.} other {{host} invites {guest} and # other people to her party.}}} male {{num_guests, plural, offset:1 =0 {{host} does not give a party.} =1 {{host} invites {guest} to his party.} =2 {{host} invites {guest} and one other person to his party.} other {{host} invites {guest} and # other people to his party.}}} other {{num_guests, plural, offset:1 =0 {{host} does not give a party.} =1 {{host} invites {guest} to their party.} =2 {{host} invites {guest} and one other person to their party.} other {{host} invites {guest} and # other people to their party.}}}}",
|
|
10
|
+
"plural": "{total_rows, plural, offset:0 =0 {No rows} one {# row} other {# rows}}",
|
|
11
|
+
"dateFormat": "At {date_time,time,::jmm} past, on {date_time,date,::dMMMM}, there was {item} on planet {planet,number,integer}.",
|
|
12
|
+
"currencyFormat": "The budget is {gbp, number, :: +! K currency/GBP} and no more.",
|
|
13
|
+
"@@locale": "en",
|
|
14
|
+
"alpha": "Alpha",
|
|
15
|
+
"beta": "Beta",
|
|
16
|
+
"another_button": "Another button",
|
|
17
|
+
"@another_button": {
|
|
18
|
+
"description": "anything"
|
|
19
|
+
},
|
|
20
|
+
"delta": "Delta",
|
|
21
|
+
"epsilon": "Epsilon",
|
|
22
|
+
"blank": "",
|
|
23
|
+
"testPlural": "You have {itemCount, plural, =0 {no items} one {item} other {{itemCount} items}}",
|
|
24
|
+
"localeParams": "Other locale params besides this {one}",
|
|
25
|
+
"@localeParams": {
|
|
26
|
+
"placeholders": {
|
|
27
|
+
"total": {
|
|
28
|
+
"type": "plural",
|
|
29
|
+
"description": "first param is used in dev English bundle"
|
|
30
|
+
},
|
|
31
|
+
"gender": {
|
|
32
|
+
"type": "select",
|
|
33
|
+
"description": "This is an extra one that might be used in other languages"
|
|
34
|
+
},
|
|
35
|
+
"title": {
|
|
36
|
+
"type": "text",
|
|
37
|
+
"description": "Person's title, Mr., Mrs., etc."
|
|
38
|
+
},
|
|
39
|
+
"birthDate": {
|
|
40
|
+
"type": "date",
|
|
41
|
+
"description": "Your b-day"
|
|
42
|
+
},
|
|
43
|
+
"one": {
|
|
44
|
+
"description": "{one} param is described here without redefining the type. should be ok."
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|