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