@vocab/core 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/declarations/src/ValidationError.d.ts +5 -5
- package/dist/declarations/src/compile.d.ts +6 -6
- package/dist/declarations/src/config.d.ts +4 -4
- package/dist/declarations/src/generate-language.d.ts +5 -5
- package/dist/declarations/src/icu-handler.d.ts +2 -2
- package/dist/declarations/src/index.d.ts +6 -6
- package/dist/declarations/src/load-translations.d.ts +32 -32
- package/dist/declarations/src/logger.d.ts +3 -3
- package/dist/declarations/src/runtime.d.ts +3 -3
- package/dist/declarations/src/translation-file.d.ts +2 -2
- package/dist/declarations/src/utils.d.ts +26 -26
- package/dist/declarations/src/validate/index.d.ts +3 -3
- package/dist/vocab-core.cjs.dev.js +73 -180
- package/dist/vocab-core.cjs.prod.js +73 -180
- package/dist/vocab-core.esm.js +39 -146
- package/icu-handler/dist/vocab-core-icu-handler.cjs.dev.js +3 -6
- package/icu-handler/dist/vocab-core-icu-handler.cjs.prod.js +3 -6
- package/icu-handler/dist/vocab-core-icu-handler.esm.js +2 -5
- package/package.json +2 -2
- package/translation-file/dist/vocab-core-translation-file.cjs.dev.js +0 -8
- package/translation-file/dist/vocab-core-translation-file.cjs.prod.js +0 -8
- package/translation-file/dist/vocab-core-translation-file.esm.js +0 -8
package/dist/vocab-core.esm.js
CHANGED
|
@@ -84,11 +84,9 @@ function getAltLanguageFilePath(devLanguageFilePath, language) {
|
|
|
84
84
|
function mapValues(obj, func) {
|
|
85
85
|
const newObj = {};
|
|
86
86
|
const keys = Object.keys(obj);
|
|
87
|
-
|
|
88
87
|
for (const key of keys) {
|
|
89
88
|
newObj[key] = func(obj[key]);
|
|
90
89
|
}
|
|
91
|
-
|
|
92
90
|
return newObj;
|
|
93
91
|
}
|
|
94
92
|
function getTranslationMessages(translations) {
|
|
@@ -102,60 +100,49 @@ function generateLanguageFromTranslations({
|
|
|
102
100
|
if (!generator.transformElement && !generator.transformMessage) {
|
|
103
101
|
return baseTranslations;
|
|
104
102
|
}
|
|
105
|
-
|
|
106
103
|
const translationKeys = Object.keys(baseTranslations);
|
|
107
104
|
const generatedTranslations = {};
|
|
108
|
-
|
|
109
105
|
for (const translationKey of translationKeys) {
|
|
110
106
|
const translation = baseTranslations[translationKey];
|
|
111
107
|
let transformedMessage = translation.message;
|
|
112
|
-
|
|
113
108
|
if (generator.transformElement) {
|
|
114
109
|
const messageAst = new IntlMessageFormat(translation.message).getAst();
|
|
115
110
|
const transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement));
|
|
116
111
|
transformedMessage = printAST(transformedAst);
|
|
117
112
|
}
|
|
118
|
-
|
|
119
113
|
if (generator.transformMessage) {
|
|
120
114
|
transformedMessage = generator.transformMessage(transformedMessage);
|
|
121
115
|
}
|
|
122
|
-
|
|
123
116
|
generatedTranslations[translationKey] = {
|
|
124
117
|
message: transformedMessage
|
|
125
118
|
};
|
|
126
119
|
}
|
|
127
|
-
|
|
128
120
|
return generatedTranslations;
|
|
129
121
|
}
|
|
130
|
-
|
|
131
122
|
function transformMessageFormatElement(transformElement) {
|
|
132
123
|
return messageFormatElement => {
|
|
133
|
-
const transformedMessageFormatElement = {
|
|
124
|
+
const transformedMessageFormatElement = {
|
|
125
|
+
...messageFormatElement
|
|
134
126
|
};
|
|
135
|
-
|
|
136
127
|
switch (transformedMessageFormatElement.type) {
|
|
137
128
|
case TYPE.literal:
|
|
138
129
|
const transformedValue = transformElement(transformedMessageFormatElement.value);
|
|
139
130
|
transformedMessageFormatElement.value = transformedValue;
|
|
140
131
|
break;
|
|
141
|
-
|
|
142
132
|
case TYPE.select:
|
|
143
133
|
case TYPE.plural:
|
|
144
|
-
const transformedOptions = {
|
|
134
|
+
const transformedOptions = {
|
|
135
|
+
...transformedMessageFormatElement.options
|
|
145
136
|
};
|
|
146
|
-
|
|
147
137
|
for (const key of Object.keys(transformedOptions)) {
|
|
148
138
|
transformedOptions[key].value = transformedOptions[key].value.map(transformMessageFormatElement(transformElement));
|
|
149
139
|
}
|
|
150
|
-
|
|
151
140
|
break;
|
|
152
|
-
|
|
153
141
|
case TYPE.tag:
|
|
154
142
|
const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement));
|
|
155
143
|
transformedMessageFormatElement.children = transformedChildren;
|
|
156
144
|
break;
|
|
157
145
|
}
|
|
158
|
-
|
|
159
146
|
return transformedMessageFormatElement;
|
|
160
147
|
};
|
|
161
148
|
}
|
|
@@ -170,7 +157,6 @@ function mergeWithDevLanguageTranslation({
|
|
|
170
157
|
// Only use keys from the dev translation
|
|
171
158
|
const keys = Object.keys(devTranslation);
|
|
172
159
|
const newLanguage = {};
|
|
173
|
-
|
|
174
160
|
for (const key of keys) {
|
|
175
161
|
if (translation[key]) {
|
|
176
162
|
newLanguage[key] = {
|
|
@@ -179,24 +165,19 @@ function mergeWithDevLanguageTranslation({
|
|
|
179
165
|
};
|
|
180
166
|
}
|
|
181
167
|
}
|
|
182
|
-
|
|
183
168
|
return newLanguage;
|
|
184
169
|
}
|
|
185
|
-
|
|
186
170
|
function getLanguageFallbacks({
|
|
187
171
|
languages
|
|
188
172
|
}) {
|
|
189
173
|
const languageFallbackMap = new Map();
|
|
190
|
-
|
|
191
174
|
for (const lang of languages) {
|
|
192
175
|
if (lang.extends) {
|
|
193
176
|
languageFallbackMap.set(lang.name, lang.extends);
|
|
194
177
|
}
|
|
195
178
|
}
|
|
196
|
-
|
|
197
179
|
return languageFallbackMap;
|
|
198
180
|
}
|
|
199
|
-
|
|
200
181
|
function getLanguageHierarchy({
|
|
201
182
|
languages
|
|
202
183
|
}) {
|
|
@@ -204,19 +185,15 @@ function getLanguageHierarchy({
|
|
|
204
185
|
const fallbacks = getLanguageFallbacks({
|
|
205
186
|
languages
|
|
206
187
|
});
|
|
207
|
-
|
|
208
188
|
for (const lang of languages) {
|
|
209
189
|
const langHierarchy = [];
|
|
210
190
|
let currLang = lang.extends;
|
|
211
|
-
|
|
212
191
|
while (currLang) {
|
|
213
192
|
langHierarchy.push(currLang);
|
|
214
193
|
currLang = fallbacks.get(currLang);
|
|
215
194
|
}
|
|
216
|
-
|
|
217
195
|
hierarchyMap.set(lang.name, langHierarchy);
|
|
218
196
|
}
|
|
219
|
-
|
|
220
197
|
return hierarchyMap;
|
|
221
198
|
}
|
|
222
199
|
function getFallbackLanguageOrder({
|
|
@@ -228,41 +205,31 @@ function getFallbackLanguageOrder({
|
|
|
228
205
|
const languageHierarchy = getLanguageHierarchy({
|
|
229
206
|
languages
|
|
230
207
|
}).get(languageName);
|
|
231
|
-
|
|
232
208
|
if (!languageHierarchy) {
|
|
233
209
|
throw new Error(`Missing language hierarchy for ${languageName}`);
|
|
234
210
|
}
|
|
235
|
-
|
|
236
211
|
const fallbackLanguageOrder = [languageName];
|
|
237
|
-
|
|
238
212
|
if (fallbacks !== 'none') {
|
|
239
213
|
fallbackLanguageOrder.unshift(...languageHierarchy.reverse());
|
|
240
|
-
|
|
241
214
|
if (fallbacks === 'all' && fallbackLanguageOrder[0] !== devLanguage) {
|
|
242
215
|
fallbackLanguageOrder.unshift(devLanguage);
|
|
243
216
|
}
|
|
244
217
|
}
|
|
245
|
-
|
|
246
218
|
return fallbackLanguageOrder;
|
|
247
219
|
}
|
|
248
|
-
|
|
249
220
|
function getNamespaceByFilePath(relativePath, {
|
|
250
221
|
translationsDirectorySuffix = defaultTranslationDirSuffix
|
|
251
222
|
}) {
|
|
252
223
|
let namespace = path.dirname(relativePath).replace(/^src\//, '').replace(/\//g, '_');
|
|
253
|
-
|
|
254
224
|
if (namespace.endsWith(translationsDirectorySuffix)) {
|
|
255
225
|
namespace = namespace.slice(0, -translationsDirectorySuffix.length);
|
|
256
226
|
}
|
|
257
|
-
|
|
258
227
|
return namespace;
|
|
259
228
|
}
|
|
260
|
-
|
|
261
229
|
function printValidationError(...params) {
|
|
262
230
|
// eslint-disable-next-line no-console
|
|
263
231
|
console.error(chalk.red('Error loading translation:'), ...params);
|
|
264
232
|
}
|
|
265
|
-
|
|
266
233
|
function getTranslationsFromFile(translationFileContents, {
|
|
267
234
|
isAltLanguage,
|
|
268
235
|
filePath,
|
|
@@ -271,29 +238,24 @@ function getTranslationsFromFile(translationFileContents, {
|
|
|
271
238
|
if (!translationFileContents || typeof translationFileContents !== 'object') {
|
|
272
239
|
throw new Error(`Unable to read translation file ${filePath}. Translations must be an object.`);
|
|
273
240
|
}
|
|
274
|
-
|
|
275
241
|
const {
|
|
276
242
|
$namespace,
|
|
277
243
|
_meta,
|
|
278
244
|
...keys
|
|
279
245
|
} = translationFileContents;
|
|
280
|
-
|
|
281
246
|
if (isAltLanguage && $namespace) {
|
|
282
247
|
printValidationError(`Found $namespace in alt language file in ${filePath}. $namespace is only used in the dev language and will be ignored.`);
|
|
283
248
|
}
|
|
284
|
-
|
|
285
249
|
if (!isAltLanguage && $namespace && typeof $namespace !== 'string') {
|
|
286
250
|
printValidationError(`Found non-string $namespace in language file in ${filePath}. $namespace must be a string.`);
|
|
287
251
|
}
|
|
288
|
-
|
|
289
252
|
if (isAltLanguage && _meta !== null && _meta !== void 0 && _meta.tags) {
|
|
290
253
|
printValidationError(`Found _meta.tags in alt language file in ${filePath}. _meta.tags is only used in the dev language and will be ignored.`);
|
|
291
|
-
}
|
|
292
|
-
|
|
254
|
+
}
|
|
293
255
|
|
|
256
|
+
// Never return tags if we're fetching translations for an alt language
|
|
294
257
|
const includeTags = !isAltLanguage && withTags;
|
|
295
258
|
const validKeys = {};
|
|
296
|
-
|
|
297
259
|
for (const [translationKey, {
|
|
298
260
|
tags,
|
|
299
261
|
...translation
|
|
@@ -302,22 +264,19 @@ function getTranslationsFromFile(translationFileContents, {
|
|
|
302
264
|
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
|
|
303
265
|
continue;
|
|
304
266
|
}
|
|
305
|
-
|
|
306
267
|
if (!translation) {
|
|
307
268
|
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
|
|
308
269
|
continue;
|
|
309
270
|
}
|
|
310
|
-
|
|
311
271
|
if (!translation.message || typeof translation.message !== 'string') {
|
|
312
272
|
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
|
|
313
273
|
continue;
|
|
314
274
|
}
|
|
315
|
-
|
|
316
|
-
|
|
275
|
+
validKeys[translationKey] = {
|
|
276
|
+
...translation,
|
|
317
277
|
tags: includeTags ? tags : undefined
|
|
318
278
|
};
|
|
319
279
|
}
|
|
320
|
-
|
|
321
280
|
const metadata = {
|
|
322
281
|
tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined
|
|
323
282
|
};
|
|
@@ -327,7 +286,6 @@ function getTranslationsFromFile(translationFileContents, {
|
|
|
327
286
|
metadata
|
|
328
287
|
};
|
|
329
288
|
}
|
|
330
|
-
|
|
331
289
|
function loadAltLanguageFile({
|
|
332
290
|
filePath,
|
|
333
291
|
languageName,
|
|
@@ -345,15 +303,12 @@ function loadAltLanguageFile({
|
|
|
345
303
|
fallbacks
|
|
346
304
|
});
|
|
347
305
|
trace(`Loading alt language file with precedence: ${fallbackLanguageOrder.slice().reverse().join(' -> ')}`);
|
|
348
|
-
|
|
349
306
|
for (const fallbackLanguage of fallbackLanguageOrder) {
|
|
350
307
|
if (fallbackLanguage !== devLanguage) {
|
|
351
308
|
try {
|
|
352
309
|
const altFilePath = getAltLanguageFilePath(filePath, fallbackLanguage);
|
|
353
310
|
delete require.cache[altFilePath];
|
|
354
|
-
|
|
355
311
|
const translationFile = require(altFilePath);
|
|
356
|
-
|
|
357
312
|
const {
|
|
358
313
|
keys: fallbackLanguageTranslation
|
|
359
314
|
} = getTranslationsFromFile(translationFile, {
|
|
@@ -372,17 +327,14 @@ function loadAltLanguageFile({
|
|
|
372
327
|
Object.assign(altLanguageTranslation, devTranslation);
|
|
373
328
|
}
|
|
374
329
|
}
|
|
375
|
-
|
|
376
330
|
return altLanguageTranslation;
|
|
377
331
|
}
|
|
378
|
-
|
|
379
332
|
function stripTagsFromTranslations(translations) {
|
|
380
333
|
return Object.fromEntries(Object.entries(translations).map(([key, {
|
|
381
334
|
tags,
|
|
382
335
|
...rest
|
|
383
336
|
}]) => [key, rest]));
|
|
384
337
|
}
|
|
385
|
-
|
|
386
338
|
function loadTranslation({
|
|
387
339
|
filePath,
|
|
388
340
|
fallbacks,
|
|
@@ -391,9 +343,7 @@ function loadTranslation({
|
|
|
391
343
|
trace(`Loading translation file in "${fallbacks}" fallback mode: "${filePath}"`);
|
|
392
344
|
const languageSet = {};
|
|
393
345
|
delete require.cache[filePath];
|
|
394
|
-
|
|
395
346
|
const translationContent = require(filePath);
|
|
396
|
-
|
|
397
347
|
const relativePath = path.relative(userConfig.projectRoot || process.cwd(), filePath);
|
|
398
348
|
const {
|
|
399
349
|
$namespace,
|
|
@@ -409,7 +359,6 @@ function loadTranslation({
|
|
|
409
359
|
languageSet[userConfig.devLanguage] = devTranslation;
|
|
410
360
|
const devTranslationNoTags = withTags ? stripTagsFromTranslations(devTranslation) : devTranslation;
|
|
411
361
|
const altLanguages = getAltLanguages(userConfig);
|
|
412
|
-
|
|
413
362
|
for (const languageName of altLanguages) {
|
|
414
363
|
languageSet[languageName] = loadAltLanguageFile({
|
|
415
364
|
filePath,
|
|
@@ -418,7 +367,6 @@ function loadTranslation({
|
|
|
418
367
|
fallbacks
|
|
419
368
|
}, userConfig);
|
|
420
369
|
}
|
|
421
|
-
|
|
422
370
|
for (const generatedLanguage of userConfig.generatedLanguages || []) {
|
|
423
371
|
const {
|
|
424
372
|
name: generatedLanguageName,
|
|
@@ -431,7 +379,6 @@ function loadTranslation({
|
|
|
431
379
|
generator
|
|
432
380
|
});
|
|
433
381
|
}
|
|
434
|
-
|
|
435
382
|
return {
|
|
436
383
|
filePath,
|
|
437
384
|
keys: Object.keys(devTranslation),
|
|
@@ -462,42 +409,33 @@ async function loadAllTranslations({
|
|
|
462
409
|
withTags
|
|
463
410
|
}, config)));
|
|
464
411
|
const keys = new Set();
|
|
465
|
-
|
|
466
412
|
for (const loadedTranslation of result) {
|
|
467
413
|
for (const key of loadedTranslation.keys) {
|
|
468
414
|
const uniqueKey = getUniqueKey(key, loadedTranslation.namespace);
|
|
469
|
-
|
|
470
415
|
if (keys.has(uniqueKey)) {
|
|
471
416
|
trace(`Duplicate keys found`);
|
|
472
417
|
throw new Error(`Duplicate keys found. Key with namespace ${loadedTranslation.namespace} and key ${key} was found multiple times.`);
|
|
473
418
|
}
|
|
474
|
-
|
|
475
419
|
keys.add(uniqueKey);
|
|
476
420
|
}
|
|
477
421
|
}
|
|
478
|
-
|
|
479
422
|
return result;
|
|
480
423
|
}
|
|
481
424
|
|
|
482
425
|
const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'");
|
|
483
|
-
|
|
484
426
|
const encodeBackslash = v => v.replace(/\\/g, '\\\\');
|
|
485
|
-
|
|
486
427
|
function extractHasTags(ast) {
|
|
487
428
|
return ast.some(element => {
|
|
488
429
|
if (isSelectElement(element)) {
|
|
489
430
|
const children = Object.values(element.options).map(o => o.value);
|
|
490
431
|
return children.some(child => extractHasTags(child));
|
|
491
432
|
}
|
|
492
|
-
|
|
493
433
|
return isTagElement(element);
|
|
494
434
|
});
|
|
495
435
|
}
|
|
496
|
-
|
|
497
436
|
function extractParamTypes(ast) {
|
|
498
437
|
let params = {};
|
|
499
438
|
let imports = new Set();
|
|
500
|
-
|
|
501
439
|
for (const element of ast) {
|
|
502
440
|
if (isArgumentElement(element)) {
|
|
503
441
|
params[element.value] = 'string';
|
|
@@ -505,6 +443,15 @@ function extractParamTypes(ast) {
|
|
|
505
443
|
params[element.value] = 'number';
|
|
506
444
|
} else if (isPluralElement(element)) {
|
|
507
445
|
params[element.value] = 'number';
|
|
446
|
+
const children = Object.values(element.options).map(o => o.value);
|
|
447
|
+
for (const child of children) {
|
|
448
|
+
const [subParams, subImports] = extractParamTypes(child);
|
|
449
|
+
imports = new Set([...imports, ...subImports]);
|
|
450
|
+
params = {
|
|
451
|
+
...params,
|
|
452
|
+
...subParams
|
|
453
|
+
};
|
|
454
|
+
}
|
|
508
455
|
} else if (isDateElement(element) || isTimeElement(element)) {
|
|
509
456
|
params[element.value] = 'Date | number';
|
|
510
457
|
} else if (isTagElement(element)) {
|
|
@@ -512,29 +459,27 @@ function extractParamTypes(ast) {
|
|
|
512
459
|
imports.add(`import { FormatXMLElementFn } from '@vocab/types';`);
|
|
513
460
|
const [subParams, subImports] = extractParamTypes(element.children);
|
|
514
461
|
imports = new Set([...imports, ...subImports]);
|
|
515
|
-
params = {
|
|
462
|
+
params = {
|
|
463
|
+
...params,
|
|
516
464
|
...subParams
|
|
517
465
|
};
|
|
518
466
|
} else if (isSelectElement(element)) {
|
|
519
467
|
params[element.value] = Object.keys(element.options).map(o => `'${o}'`).join(' | ');
|
|
520
468
|
const children = Object.values(element.options).map(o => o.value);
|
|
521
|
-
|
|
522
469
|
for (const child of children) {
|
|
523
470
|
const [subParams, subImports] = extractParamTypes(child);
|
|
524
471
|
imports = new Set([...imports, ...subImports]);
|
|
525
|
-
params = {
|
|
472
|
+
params = {
|
|
473
|
+
...params,
|
|
526
474
|
...subParams
|
|
527
475
|
};
|
|
528
476
|
}
|
|
529
477
|
}
|
|
530
478
|
}
|
|
531
|
-
|
|
532
479
|
return [params, imports];
|
|
533
480
|
}
|
|
534
|
-
|
|
535
481
|
function serialiseObjectToType(v) {
|
|
536
482
|
let result = '';
|
|
537
|
-
|
|
538
483
|
for (const [key, value] of Object.entries(v)) {
|
|
539
484
|
if (value && typeof value === 'object') {
|
|
540
485
|
result += `'${encodeWithinSingleQuotes(key)}': ${serialiseObjectToType(value)},`;
|
|
@@ -542,32 +487,25 @@ function serialiseObjectToType(v) {
|
|
|
542
487
|
result += `'${encodeWithinSingleQuotes(key)}': ${value},`;
|
|
543
488
|
}
|
|
544
489
|
}
|
|
545
|
-
|
|
546
490
|
return `{ ${result} }`;
|
|
547
491
|
}
|
|
548
|
-
|
|
549
492
|
const banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`;
|
|
550
|
-
|
|
551
493
|
function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
552
494
|
trace('Serialising translations:', loadedTranslation);
|
|
553
495
|
const translationsType = {};
|
|
554
|
-
|
|
555
496
|
for (const [key, {
|
|
556
497
|
params,
|
|
557
498
|
message,
|
|
558
499
|
hasTags
|
|
559
500
|
}] of value.entries()) {
|
|
560
501
|
let translationFunctionString = `() => ${message}`;
|
|
561
|
-
|
|
562
502
|
if (Object.keys(params).length > 0) {
|
|
563
503
|
const formatGeneric = hasTags ? '<T = string>' : '';
|
|
564
504
|
const formatReturn = hasTags ? 'string | T | Array<string | T>' : 'string';
|
|
565
505
|
translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
|
|
566
506
|
}
|
|
567
|
-
|
|
568
507
|
translationsType[encodeBackslash(key)] = translationFunctionString;
|
|
569
508
|
}
|
|
570
|
-
|
|
571
509
|
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
572
510
|
const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | ');
|
|
573
511
|
return `${banner}
|
|
@@ -579,7 +517,6 @@ function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
|
579
517
|
|
|
580
518
|
export default translations;`;
|
|
581
519
|
}
|
|
582
|
-
|
|
583
520
|
async function generateRuntime(loadedTranslation) {
|
|
584
521
|
const {
|
|
585
522
|
languages: loadedLanguages,
|
|
@@ -588,25 +525,23 @@ async function generateRuntime(loadedTranslation) {
|
|
|
588
525
|
trace('Generating types for', filePath);
|
|
589
526
|
const translationTypes = new Map();
|
|
590
527
|
let imports = new Set();
|
|
591
|
-
|
|
592
528
|
for (const key of loadedTranslation.keys) {
|
|
593
529
|
let params = {};
|
|
594
530
|
const messages = new Set();
|
|
595
531
|
let hasTags = false;
|
|
596
|
-
|
|
597
532
|
for (const translatedLanguage of Object.values(loadedLanguages)) {
|
|
598
533
|
if (translatedLanguage[key]) {
|
|
599
534
|
const ast = parse(translatedLanguage[key].message);
|
|
600
535
|
hasTags = hasTags || extractHasTags(ast);
|
|
601
536
|
const [parsedParams, parsedImports] = extractParamTypes(ast);
|
|
602
537
|
imports = new Set([...imports, ...parsedImports]);
|
|
603
|
-
params = {
|
|
538
|
+
params = {
|
|
539
|
+
...params,
|
|
604
540
|
...parsedParams
|
|
605
541
|
};
|
|
606
542
|
messages.add(`'${encodeWithinSingleQuotes(translatedLanguage[key].message)}'`);
|
|
607
543
|
}
|
|
608
544
|
}
|
|
609
|
-
|
|
610
545
|
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string';
|
|
611
546
|
translationTypes.set(key, {
|
|
612
547
|
params,
|
|
@@ -615,10 +550,10 @@ async function generateRuntime(loadedTranslation) {
|
|
|
615
550
|
returnType
|
|
616
551
|
});
|
|
617
552
|
}
|
|
618
|
-
|
|
619
553
|
const prettierConfig = await prettier.resolveConfig(filePath);
|
|
620
554
|
const serializedTranslationType = serialiseTranslationRuntime(translationTypes, imports, loadedTranslation);
|
|
621
|
-
const declaration = prettier.format(serializedTranslationType, {
|
|
555
|
+
const declaration = prettier.format(serializedTranslationType, {
|
|
556
|
+
...prettierConfig,
|
|
622
557
|
parser: 'typescript'
|
|
623
558
|
});
|
|
624
559
|
const outputFilePath = getTSFileFromDevLanguageFile(filePath);
|
|
@@ -632,17 +567,14 @@ function watch(config) {
|
|
|
632
567
|
ignored: config.ignore ? [...config.ignore, '**/node_modules/**'] : ['**/node_modules/**'],
|
|
633
568
|
ignoreInitial: true
|
|
634
569
|
});
|
|
635
|
-
|
|
636
570
|
const onTranslationChange = async relativePath => {
|
|
637
571
|
trace(`Detected change for file ${relativePath}`);
|
|
638
572
|
let targetFile;
|
|
639
|
-
|
|
640
573
|
if (isDevLanguageFile(relativePath)) {
|
|
641
574
|
targetFile = path.resolve(cwd, relativePath);
|
|
642
575
|
} else if (isAltLanguageFile(relativePath)) {
|
|
643
576
|
targetFile = getDevLanguageFileFromAltLanguageFile(path.resolve(cwd, relativePath));
|
|
644
577
|
}
|
|
645
|
-
|
|
646
578
|
if (targetFile) {
|
|
647
579
|
try {
|
|
648
580
|
const loadedTranslation = loadTranslation({
|
|
@@ -652,23 +584,19 @@ function watch(config) {
|
|
|
652
584
|
await generateRuntime(loadedTranslation);
|
|
653
585
|
} catch (e) {
|
|
654
586
|
// eslint-disable-next-line no-console
|
|
655
|
-
console.log('Failed to generate types for', relativePath);
|
|
656
|
-
|
|
587
|
+
console.log('Failed to generate types for', relativePath);
|
|
588
|
+
// eslint-disable-next-line no-console
|
|
657
589
|
console.error(e);
|
|
658
590
|
}
|
|
659
591
|
}
|
|
660
592
|
};
|
|
661
|
-
|
|
662
593
|
const onNewDirectory = async relativePath => {
|
|
663
594
|
trace('Detected new directory', relativePath);
|
|
664
|
-
|
|
665
595
|
if (!isTranslationDirectory(relativePath, config)) {
|
|
666
596
|
trace('Ignoring non-translation directory:', relativePath);
|
|
667
597
|
return;
|
|
668
598
|
}
|
|
669
|
-
|
|
670
599
|
const newFilePath = path.join(relativePath, devTranslationFileName);
|
|
671
|
-
|
|
672
600
|
if (!existsSync(newFilePath)) {
|
|
673
601
|
await promises.writeFile(newFilePath, JSON.stringify({}, null, 2));
|
|
674
602
|
trace('Created new empty translation file:', newFilePath);
|
|
@@ -676,7 +604,6 @@ function watch(config) {
|
|
|
676
604
|
trace(`New directory already contains translation file. Skipping creation. Existing file ${newFilePath}`);
|
|
677
605
|
}
|
|
678
606
|
};
|
|
679
|
-
|
|
680
607
|
watcher.on('addDir', onNewDirectory);
|
|
681
608
|
watcher.on('add', onTranslationChange).on('change', onTranslationChange);
|
|
682
609
|
return () => watcher.close();
|
|
@@ -688,28 +615,24 @@ async function compile({
|
|
|
688
615
|
fallbacks: 'all',
|
|
689
616
|
includeNodeModules: false
|
|
690
617
|
}, config);
|
|
691
|
-
|
|
692
618
|
for (const loadedTranslation of translations) {
|
|
693
619
|
await generateRuntime(loadedTranslation);
|
|
694
620
|
}
|
|
695
|
-
|
|
696
621
|
if (shouldWatch) {
|
|
697
622
|
trace('Listening for changes to files...');
|
|
698
623
|
return watch(config);
|
|
699
624
|
}
|
|
700
625
|
}
|
|
701
|
-
|
|
702
626
|
async function writeIfChanged(filepath, contents) {
|
|
703
627
|
let hasChanged = true;
|
|
704
|
-
|
|
705
628
|
try {
|
|
706
629
|
const existingContents = await promises.readFile(filepath, {
|
|
707
630
|
encoding: 'utf-8'
|
|
708
631
|
});
|
|
709
632
|
hasChanged = existingContents !== contents;
|
|
710
|
-
} catch (e) {
|
|
633
|
+
} catch (e) {
|
|
634
|
+
// ignore error, likely a file doesn't exist error so we want to write anyway
|
|
711
635
|
}
|
|
712
|
-
|
|
713
636
|
if (hasChanged) {
|
|
714
637
|
await promises.writeFile(filepath, contents, {
|
|
715
638
|
encoding: 'utf-8'
|
|
@@ -720,36 +643,28 @@ async function writeIfChanged(filepath, contents) {
|
|
|
720
643
|
/* eslint-disable no-console */
|
|
721
644
|
function findMissingKeys(loadedTranslation, devLanguageName, altLanguages) {
|
|
722
645
|
const devLanguage = loadedTranslation.languages[devLanguageName];
|
|
723
|
-
|
|
724
646
|
if (!devLanguage) {
|
|
725
647
|
throw new Error(`Failed to load dev language: ${loadedTranslation.filePath}`);
|
|
726
648
|
}
|
|
727
|
-
|
|
728
649
|
const result = {};
|
|
729
650
|
let valid = true;
|
|
730
651
|
const requiredKeys = Object.keys(devLanguage);
|
|
731
|
-
|
|
732
652
|
if (requiredKeys.length > 0) {
|
|
733
653
|
for (const altLanguageName of altLanguages) {
|
|
734
654
|
var _loadedTranslation$la;
|
|
735
|
-
|
|
736
655
|
const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {};
|
|
737
|
-
|
|
738
656
|
for (const key of requiredKeys) {
|
|
739
657
|
var _altLanguage$key;
|
|
740
|
-
|
|
741
658
|
if (typeof ((_altLanguage$key = altLanguage[key]) === null || _altLanguage$key === void 0 ? void 0 : _altLanguage$key.message) !== 'string') {
|
|
742
659
|
if (!result[altLanguageName]) {
|
|
743
660
|
result[altLanguageName] = [];
|
|
744
661
|
}
|
|
745
|
-
|
|
746
662
|
result[altLanguageName].push(key);
|
|
747
663
|
valid = false;
|
|
748
664
|
}
|
|
749
665
|
}
|
|
750
666
|
}
|
|
751
667
|
}
|
|
752
|
-
|
|
753
668
|
return [valid, result];
|
|
754
669
|
}
|
|
755
670
|
async function validate(config) {
|
|
@@ -758,21 +673,17 @@ async function validate(config) {
|
|
|
758
673
|
includeNodeModules: true
|
|
759
674
|
}, config);
|
|
760
675
|
let valid = true;
|
|
761
|
-
|
|
762
676
|
for (const loadedTranslation of allTranslations) {
|
|
763
677
|
const [translationValid, result] = findMissingKeys(loadedTranslation, config.devLanguage, getAltLanguages(config));
|
|
764
|
-
|
|
765
678
|
if (!translationValid) {
|
|
766
679
|
valid = false;
|
|
767
680
|
console.log(chalk.red`Incomplete translations: "${chalk.bold(loadedTranslation.relativePath)}"`);
|
|
768
|
-
|
|
769
681
|
for (const lang of Object.keys(result)) {
|
|
770
682
|
const missingKeys = result[lang];
|
|
771
683
|
console.log(chalk.yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', '));
|
|
772
684
|
}
|
|
773
685
|
}
|
|
774
686
|
}
|
|
775
|
-
|
|
776
687
|
return valid;
|
|
777
688
|
}
|
|
778
689
|
|
|
@@ -782,7 +693,6 @@ class ValidationError extends Error {
|
|
|
782
693
|
this.code = code;
|
|
783
694
|
this.rawMessage = message;
|
|
784
695
|
}
|
|
785
|
-
|
|
786
696
|
}
|
|
787
697
|
|
|
788
698
|
const validator = new Validator();
|
|
@@ -850,75 +760,63 @@ const schema = {
|
|
|
850
760
|
}
|
|
851
761
|
};
|
|
852
762
|
const checkConfigFile = validator.compile(schema);
|
|
853
|
-
|
|
854
763
|
const splitMap = (message, callback) => message.split(' ,').map(v => callback(v)).join(' ,');
|
|
855
|
-
|
|
856
764
|
function validateConfig(c) {
|
|
857
|
-
trace('Validating configuration file');
|
|
858
|
-
|
|
765
|
+
trace('Validating configuration file');
|
|
766
|
+
// Note: checkConfigFile mutates the config file by applying defaults
|
|
859
767
|
const isValid = checkConfigFile(c);
|
|
860
|
-
|
|
861
768
|
if (isValid !== true) {
|
|
862
769
|
throw new ValidationError('InvalidStructure', (Array.isArray(isValid) ? isValid : []).map(v => {
|
|
863
770
|
if (v.type === 'objectStrict') {
|
|
864
771
|
return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk.cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk.green)}`;
|
|
865
772
|
}
|
|
866
|
-
|
|
867
773
|
if (v.field) {
|
|
868
774
|
var _v$message;
|
|
869
|
-
|
|
870
775
|
return (_v$message = v.message) === null || _v$message === void 0 ? void 0 : _v$message.replace(v.field, chalk.cyan(v.field));
|
|
871
776
|
}
|
|
872
|
-
|
|
873
777
|
return v.message;
|
|
874
778
|
}).join(' \n'));
|
|
875
779
|
}
|
|
780
|
+
const languageStrings = c.languages.map(v => v.name);
|
|
876
781
|
|
|
877
|
-
|
|
878
|
-
|
|
782
|
+
// Dev Language should exist in languages
|
|
879
783
|
if (!languageStrings.includes(c.devLanguage)) {
|
|
880
784
|
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk.bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
|
|
881
785
|
}
|
|
882
|
-
|
|
883
786
|
const foundLanguages = [];
|
|
884
|
-
|
|
885
787
|
for (const lang of c.languages) {
|
|
886
788
|
// Languages must only exist once
|
|
887
789
|
if (foundLanguages.includes(lang.name)) {
|
|
888
790
|
throw new ValidationError('DuplicateLanguage', `The language "${chalk.bold.cyan(lang.name)}" was defined multiple times.`);
|
|
889
791
|
}
|
|
792
|
+
foundLanguages.push(lang.name);
|
|
890
793
|
|
|
891
|
-
|
|
892
|
-
|
|
794
|
+
// Any extends must be in languages
|
|
893
795
|
if (lang.extends && !languageStrings.includes(lang.extends)) {
|
|
894
796
|
throw new ValidationError('InvalidExtends', `The language "${chalk.bold.cyan(lang.name)}"'s extends of ${chalk.bold.cyan(lang.extends)} was not found in languages ${languageStrings.join(', ')}.`);
|
|
895
797
|
}
|
|
896
798
|
}
|
|
897
|
-
|
|
898
799
|
const foundGeneratedLanguages = [];
|
|
899
|
-
|
|
900
800
|
for (const generatedLang of c.generatedLanguages || []) {
|
|
901
801
|
// Generated languages must only exist once
|
|
902
802
|
if (foundGeneratedLanguages.includes(generatedLang.name)) {
|
|
903
803
|
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk.bold.cyan(generatedLang.name)}" was defined multiple times.`);
|
|
904
804
|
}
|
|
805
|
+
foundGeneratedLanguages.push(generatedLang.name);
|
|
905
806
|
|
|
906
|
-
|
|
907
|
-
|
|
807
|
+
// Generated language names must not conflict with language names
|
|
908
808
|
if (languageStrings.includes(generatedLang.name)) {
|
|
909
809
|
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk.bold.cyan(generatedLang.name)}" is already defined as a language.`);
|
|
910
|
-
}
|
|
911
|
-
|
|
810
|
+
}
|
|
912
811
|
|
|
812
|
+
// Any extends must be in languages
|
|
913
813
|
if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) {
|
|
914
814
|
throw new ValidationError('InvalidExtends', `The generated language "${chalk.bold.cyan(generatedLang.name)}"'s extends of ${chalk.bold.cyan(generatedLang.extends)} was not found in languages ${languageStrings.join(', ')}.`);
|
|
915
815
|
}
|
|
916
816
|
}
|
|
917
|
-
|
|
918
817
|
trace('Configuration file is valid');
|
|
919
818
|
return true;
|
|
920
819
|
}
|
|
921
|
-
|
|
922
820
|
function createConfig(configFilePath) {
|
|
923
821
|
const cwd = path.dirname(configFilePath);
|
|
924
822
|
return {
|
|
@@ -926,26 +824,21 @@ function createConfig(configFilePath) {
|
|
|
926
824
|
...require(configFilePath)
|
|
927
825
|
};
|
|
928
826
|
}
|
|
929
|
-
|
|
930
827
|
async function resolveConfig(customConfigFilePath) {
|
|
931
828
|
const configFilePath = customConfigFilePath ? path.resolve(customConfigFilePath) : await findUp('vocab.config.js');
|
|
932
|
-
|
|
933
829
|
if (configFilePath) {
|
|
934
830
|
trace(`Resolved configuration file to ${configFilePath}`);
|
|
935
831
|
return createConfig(configFilePath);
|
|
936
832
|
}
|
|
937
|
-
|
|
938
833
|
trace('No configuration file found');
|
|
939
834
|
return null;
|
|
940
835
|
}
|
|
941
836
|
function resolveConfigSync(customConfigFilePath) {
|
|
942
837
|
const configFilePath = customConfigFilePath ? path.resolve(customConfigFilePath) : findUp.sync('vocab.config.js');
|
|
943
|
-
|
|
944
838
|
if (configFilePath) {
|
|
945
839
|
trace(`Resolved configuration file to ${configFilePath}`);
|
|
946
840
|
return createConfig(configFilePath);
|
|
947
841
|
}
|
|
948
|
-
|
|
949
842
|
trace('No configuration file found');
|
|
950
843
|
return null;
|
|
951
844
|
}
|