@vocab/core 1.2.0 → 1.2.2
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 +92 -189
- package/dist/vocab-core.cjs.prod.js +92 -189
- package/dist/vocab-core.esm.js +58 -155
- 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
|
-
let
|
|
516
|
-
|
|
454
|
+
let vocabTypesImports = new Set();
|
|
517
455
|
for (const element of ast) {
|
|
518
456
|
if (icuMessageformatParser.isArgumentElement(element)) {
|
|
519
457
|
params[element.value] = 'string';
|
|
@@ -521,36 +459,49 @@ 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
|
+
vocabTypesImports = new Set([...vocabTypesImports, ...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)) {
|
|
527
474
|
params[element.value] = 'FormatXMLElementFn<T>';
|
|
528
|
-
|
|
475
|
+
vocabTypesImports.add('FormatXMLElementFn');
|
|
529
476
|
const [subParams, subImports] = extractParamTypes(element.children);
|
|
530
|
-
|
|
531
|
-
params = {
|
|
477
|
+
vocabTypesImports = new Set([...vocabTypesImports, ...subImports]);
|
|
478
|
+
params = {
|
|
479
|
+
...params,
|
|
532
480
|
...subParams
|
|
533
481
|
};
|
|
534
482
|
} else if (icuMessageformatParser.isSelectElement(element)) {
|
|
535
|
-
|
|
536
|
-
const children = Object.values(element.options).map(o => o.value);
|
|
483
|
+
const options = Object.keys(element.options);
|
|
537
484
|
|
|
485
|
+
// `other` will always be an option as the parser enforces this by default
|
|
486
|
+
const nonOtherOptions = options.filter(o => o !== 'other');
|
|
487
|
+
const nonOtherOptionsUnion = nonOtherOptions.map(o => `'${o}'`).join(' | ');
|
|
488
|
+
params[element.value] = `StringWithSuggestions<${nonOtherOptionsUnion}>`;
|
|
489
|
+
vocabTypesImports.add('StringWithSuggestions');
|
|
490
|
+
const children = Object.values(element.options).map(o => o.value);
|
|
538
491
|
for (const child of children) {
|
|
539
492
|
const [subParams, subImports] = extractParamTypes(child);
|
|
540
|
-
|
|
541
|
-
params = {
|
|
493
|
+
vocabTypesImports = new Set([...vocabTypesImports, ...subImports]);
|
|
494
|
+
params = {
|
|
495
|
+
...params,
|
|
542
496
|
...subParams
|
|
543
497
|
};
|
|
544
498
|
}
|
|
545
499
|
}
|
|
546
500
|
}
|
|
547
|
-
|
|
548
|
-
return [params, imports];
|
|
501
|
+
return [params, vocabTypesImports];
|
|
549
502
|
}
|
|
550
|
-
|
|
551
503
|
function serialiseObjectToType(v) {
|
|
552
504
|
let result = '';
|
|
553
|
-
|
|
554
505
|
for (const [key, value] of Object.entries(v)) {
|
|
555
506
|
if (value && typeof value === 'object') {
|
|
556
507
|
result += `'${encodeWithinSingleQuotes(key)}': ${serialiseObjectToType(value)},`;
|
|
@@ -558,44 +509,40 @@ function serialiseObjectToType(v) {
|
|
|
558
509
|
result += `'${encodeWithinSingleQuotes(key)}': ${value},`;
|
|
559
510
|
}
|
|
560
511
|
}
|
|
561
|
-
|
|
562
512
|
return `{ ${result} }`;
|
|
563
513
|
}
|
|
564
|
-
|
|
565
514
|
const banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`;
|
|
566
|
-
|
|
515
|
+
const serializeModuleImports = (imports, moduleName) => {
|
|
516
|
+
const importNames = Array.from(imports);
|
|
517
|
+
return `import { ${Array.from(importNames).join(', ')} } from '${moduleName}'`;
|
|
518
|
+
};
|
|
567
519
|
function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
568
520
|
trace('Serialising translations:', loadedTranslation);
|
|
569
521
|
const translationsType = {};
|
|
570
|
-
|
|
571
522
|
for (const [key, {
|
|
572
523
|
params,
|
|
573
524
|
message,
|
|
574
525
|
hasTags
|
|
575
526
|
}] of value.entries()) {
|
|
576
527
|
let translationFunctionString = `() => ${message}`;
|
|
577
|
-
|
|
578
528
|
if (Object.keys(params).length > 0) {
|
|
579
529
|
const formatGeneric = hasTags ? '<T = string>' : '';
|
|
580
530
|
const formatReturn = hasTags ? 'string | T | Array<string | T>' : 'string';
|
|
581
531
|
translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
|
|
582
532
|
}
|
|
583
|
-
|
|
584
533
|
translationsType[encodeBackslash(key)] = translationFunctionString;
|
|
585
534
|
}
|
|
586
|
-
|
|
587
535
|
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
588
536
|
const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | ');
|
|
589
537
|
return `${banner}
|
|
590
538
|
|
|
591
|
-
${
|
|
539
|
+
${serializeModuleImports(imports, '@vocab/types')}
|
|
592
540
|
import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
|
|
593
541
|
|
|
594
542
|
const translations = createTranslationFile<${languagesUnionAsString}, ${serialiseObjectToType(translationsType)}>({${content}});
|
|
595
543
|
|
|
596
544
|
export default translations;`;
|
|
597
545
|
}
|
|
598
|
-
|
|
599
546
|
async function generateRuntime(loadedTranslation) {
|
|
600
547
|
const {
|
|
601
548
|
languages: loadedLanguages,
|
|
@@ -604,25 +551,23 @@ async function generateRuntime(loadedTranslation) {
|
|
|
604
551
|
trace('Generating types for', filePath);
|
|
605
552
|
const translationTypes = new Map();
|
|
606
553
|
let imports = new Set();
|
|
607
|
-
|
|
608
554
|
for (const key of loadedTranslation.keys) {
|
|
609
555
|
let params = {};
|
|
610
556
|
const messages = new Set();
|
|
611
557
|
let hasTags = false;
|
|
612
|
-
|
|
613
558
|
for (const translatedLanguage of Object.values(loadedLanguages)) {
|
|
614
559
|
if (translatedLanguage[key]) {
|
|
615
560
|
const ast = icuMessageformatParser.parse(translatedLanguage[key].message);
|
|
616
561
|
hasTags = hasTags || extractHasTags(ast);
|
|
617
|
-
const [parsedParams,
|
|
618
|
-
imports = new Set([...imports, ...
|
|
619
|
-
params = {
|
|
562
|
+
const [parsedParams, vocabTypesImports] = extractParamTypes(ast);
|
|
563
|
+
imports = new Set([...imports, ...vocabTypesImports]);
|
|
564
|
+
params = {
|
|
565
|
+
...params,
|
|
620
566
|
...parsedParams
|
|
621
567
|
};
|
|
622
568
|
messages.add(`'${encodeWithinSingleQuotes(translatedLanguage[key].message)}'`);
|
|
623
569
|
}
|
|
624
570
|
}
|
|
625
|
-
|
|
626
571
|
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string';
|
|
627
572
|
translationTypes.set(key, {
|
|
628
573
|
params,
|
|
@@ -631,10 +576,10 @@ async function generateRuntime(loadedTranslation) {
|
|
|
631
576
|
returnType
|
|
632
577
|
});
|
|
633
578
|
}
|
|
634
|
-
|
|
635
|
-
const prettierConfig = await prettier__default['default'].resolveConfig(filePath);
|
|
579
|
+
const prettierConfig = await prettier__default["default"].resolveConfig(filePath);
|
|
636
580
|
const serializedTranslationType = serialiseTranslationRuntime(translationTypes, imports, loadedTranslation);
|
|
637
|
-
const declaration = prettier__default[
|
|
581
|
+
const declaration = prettier__default["default"].format(serializedTranslationType, {
|
|
582
|
+
...prettierConfig,
|
|
638
583
|
parser: 'typescript'
|
|
639
584
|
});
|
|
640
585
|
const outputFilePath = getTSFileFromDevLanguageFile(filePath);
|
|
@@ -643,22 +588,19 @@ async function generateRuntime(loadedTranslation) {
|
|
|
643
588
|
}
|
|
644
589
|
function watch(config) {
|
|
645
590
|
const cwd = config.projectRoot || process.cwd();
|
|
646
|
-
const watcher = chokidar__default[
|
|
591
|
+
const watcher = chokidar__default["default"].watch([getDevTranslationFileGlob(config), getAltTranslationFileGlob(config), getTranslationFolderGlob(config)], {
|
|
647
592
|
cwd,
|
|
648
593
|
ignored: config.ignore ? [...config.ignore, '**/node_modules/**'] : ['**/node_modules/**'],
|
|
649
594
|
ignoreInitial: true
|
|
650
595
|
});
|
|
651
|
-
|
|
652
596
|
const onTranslationChange = async relativePath => {
|
|
653
597
|
trace(`Detected change for file ${relativePath}`);
|
|
654
598
|
let targetFile;
|
|
655
|
-
|
|
656
599
|
if (isDevLanguageFile(relativePath)) {
|
|
657
|
-
targetFile = path__default[
|
|
600
|
+
targetFile = path__default["default"].resolve(cwd, relativePath);
|
|
658
601
|
} else if (isAltLanguageFile(relativePath)) {
|
|
659
|
-
targetFile = getDevLanguageFileFromAltLanguageFile(path__default[
|
|
602
|
+
targetFile = getDevLanguageFileFromAltLanguageFile(path__default["default"].resolve(cwd, relativePath));
|
|
660
603
|
}
|
|
661
|
-
|
|
662
604
|
if (targetFile) {
|
|
663
605
|
try {
|
|
664
606
|
const loadedTranslation = loadTranslation({
|
|
@@ -668,23 +610,19 @@ function watch(config) {
|
|
|
668
610
|
await generateRuntime(loadedTranslation);
|
|
669
611
|
} catch (e) {
|
|
670
612
|
// eslint-disable-next-line no-console
|
|
671
|
-
console.log('Failed to generate types for', relativePath);
|
|
672
|
-
|
|
613
|
+
console.log('Failed to generate types for', relativePath);
|
|
614
|
+
// eslint-disable-next-line no-console
|
|
673
615
|
console.error(e);
|
|
674
616
|
}
|
|
675
617
|
}
|
|
676
618
|
};
|
|
677
|
-
|
|
678
619
|
const onNewDirectory = async relativePath => {
|
|
679
620
|
trace('Detected new directory', relativePath);
|
|
680
|
-
|
|
681
621
|
if (!isTranslationDirectory(relativePath, config)) {
|
|
682
622
|
trace('Ignoring non-translation directory:', relativePath);
|
|
683
623
|
return;
|
|
684
624
|
}
|
|
685
|
-
|
|
686
|
-
const newFilePath = path__default['default'].join(relativePath, devTranslationFileName);
|
|
687
|
-
|
|
625
|
+
const newFilePath = path__default["default"].join(relativePath, devTranslationFileName);
|
|
688
626
|
if (!fs.existsSync(newFilePath)) {
|
|
689
627
|
await fs.promises.writeFile(newFilePath, JSON.stringify({}, null, 2));
|
|
690
628
|
trace('Created new empty translation file:', newFilePath);
|
|
@@ -692,7 +630,6 @@ function watch(config) {
|
|
|
692
630
|
trace(`New directory already contains translation file. Skipping creation. Existing file ${newFilePath}`);
|
|
693
631
|
}
|
|
694
632
|
};
|
|
695
|
-
|
|
696
633
|
watcher.on('addDir', onNewDirectory);
|
|
697
634
|
watcher.on('add', onTranslationChange).on('change', onTranslationChange);
|
|
698
635
|
return () => watcher.close();
|
|
@@ -704,28 +641,24 @@ async function compile({
|
|
|
704
641
|
fallbacks: 'all',
|
|
705
642
|
includeNodeModules: false
|
|
706
643
|
}, config);
|
|
707
|
-
|
|
708
644
|
for (const loadedTranslation of translations) {
|
|
709
645
|
await generateRuntime(loadedTranslation);
|
|
710
646
|
}
|
|
711
|
-
|
|
712
647
|
if (shouldWatch) {
|
|
713
648
|
trace('Listening for changes to files...');
|
|
714
649
|
return watch(config);
|
|
715
650
|
}
|
|
716
651
|
}
|
|
717
|
-
|
|
718
652
|
async function writeIfChanged(filepath, contents) {
|
|
719
653
|
let hasChanged = true;
|
|
720
|
-
|
|
721
654
|
try {
|
|
722
655
|
const existingContents = await fs.promises.readFile(filepath, {
|
|
723
656
|
encoding: 'utf-8'
|
|
724
657
|
});
|
|
725
658
|
hasChanged = existingContents !== contents;
|
|
726
|
-
} catch (e) {
|
|
659
|
+
} catch (e) {
|
|
660
|
+
// ignore error, likely a file doesn't exist error so we want to write anyway
|
|
727
661
|
}
|
|
728
|
-
|
|
729
662
|
if (hasChanged) {
|
|
730
663
|
await fs.promises.writeFile(filepath, contents, {
|
|
731
664
|
encoding: 'utf-8'
|
|
@@ -736,36 +669,28 @@ async function writeIfChanged(filepath, contents) {
|
|
|
736
669
|
/* eslint-disable no-console */
|
|
737
670
|
function findMissingKeys(loadedTranslation, devLanguageName, altLanguages) {
|
|
738
671
|
const devLanguage = loadedTranslation.languages[devLanguageName];
|
|
739
|
-
|
|
740
672
|
if (!devLanguage) {
|
|
741
673
|
throw new Error(`Failed to load dev language: ${loadedTranslation.filePath}`);
|
|
742
674
|
}
|
|
743
|
-
|
|
744
675
|
const result = {};
|
|
745
676
|
let valid = true;
|
|
746
677
|
const requiredKeys = Object.keys(devLanguage);
|
|
747
|
-
|
|
748
678
|
if (requiredKeys.length > 0) {
|
|
749
679
|
for (const altLanguageName of altLanguages) {
|
|
750
680
|
var _loadedTranslation$la;
|
|
751
|
-
|
|
752
681
|
const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {};
|
|
753
|
-
|
|
754
682
|
for (const key of requiredKeys) {
|
|
755
683
|
var _altLanguage$key;
|
|
756
|
-
|
|
757
684
|
if (typeof ((_altLanguage$key = altLanguage[key]) === null || _altLanguage$key === void 0 ? void 0 : _altLanguage$key.message) !== 'string') {
|
|
758
685
|
if (!result[altLanguageName]) {
|
|
759
686
|
result[altLanguageName] = [];
|
|
760
687
|
}
|
|
761
|
-
|
|
762
688
|
result[altLanguageName].push(key);
|
|
763
689
|
valid = false;
|
|
764
690
|
}
|
|
765
691
|
}
|
|
766
692
|
}
|
|
767
693
|
}
|
|
768
|
-
|
|
769
694
|
return [valid, result];
|
|
770
695
|
}
|
|
771
696
|
async function validate(config) {
|
|
@@ -774,21 +699,17 @@ async function validate(config) {
|
|
|
774
699
|
includeNodeModules: true
|
|
775
700
|
}, config);
|
|
776
701
|
let valid = true;
|
|
777
|
-
|
|
778
702
|
for (const loadedTranslation of allTranslations) {
|
|
779
703
|
const [translationValid, result] = findMissingKeys(loadedTranslation, config.devLanguage, getAltLanguages(config));
|
|
780
|
-
|
|
781
704
|
if (!translationValid) {
|
|
782
705
|
valid = false;
|
|
783
|
-
console.log(chalk__default[
|
|
784
|
-
|
|
706
|
+
console.log(chalk__default["default"].red`Incomplete translations: "${chalk__default["default"].bold(loadedTranslation.relativePath)}"`);
|
|
785
707
|
for (const lang of Object.keys(result)) {
|
|
786
708
|
const missingKeys = result[lang];
|
|
787
|
-
console.log(chalk__default[
|
|
709
|
+
console.log(chalk__default["default"].yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', '));
|
|
788
710
|
}
|
|
789
711
|
}
|
|
790
712
|
}
|
|
791
|
-
|
|
792
713
|
return valid;
|
|
793
714
|
}
|
|
794
715
|
|
|
@@ -798,10 +719,9 @@ class ValidationError extends Error {
|
|
|
798
719
|
this.code = code;
|
|
799
720
|
this.rawMessage = message;
|
|
800
721
|
}
|
|
801
|
-
|
|
802
722
|
}
|
|
803
723
|
|
|
804
|
-
const validator = new Validator__default[
|
|
724
|
+
const validator = new Validator__default["default"]();
|
|
805
725
|
const schema = {
|
|
806
726
|
$$strict: true,
|
|
807
727
|
devLanguage: {
|
|
@@ -866,102 +786,85 @@ const schema = {
|
|
|
866
786
|
}
|
|
867
787
|
};
|
|
868
788
|
const checkConfigFile = validator.compile(schema);
|
|
869
|
-
|
|
870
789
|
const splitMap = (message, callback) => message.split(' ,').map(v => callback(v)).join(' ,');
|
|
871
|
-
|
|
872
790
|
function validateConfig(c) {
|
|
873
|
-
trace('Validating configuration file');
|
|
874
|
-
|
|
791
|
+
trace('Validating configuration file');
|
|
792
|
+
// Note: checkConfigFile mutates the config file by applying defaults
|
|
875
793
|
const isValid = checkConfigFile(c);
|
|
876
|
-
|
|
877
794
|
if (isValid !== true) {
|
|
878
795
|
throw new ValidationError('InvalidStructure', (Array.isArray(isValid) ? isValid : []).map(v => {
|
|
879
796
|
if (v.type === 'objectStrict') {
|
|
880
|
-
return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default[
|
|
797
|
+
return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default["default"].cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk__default["default"].green)}`;
|
|
881
798
|
}
|
|
882
|
-
|
|
883
799
|
if (v.field) {
|
|
884
800
|
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));
|
|
801
|
+
return (_v$message = v.message) === null || _v$message === void 0 ? void 0 : _v$message.replace(v.field, chalk__default["default"].cyan(v.field));
|
|
887
802
|
}
|
|
888
|
-
|
|
889
803
|
return v.message;
|
|
890
804
|
}).join(' \n'));
|
|
891
805
|
}
|
|
806
|
+
const languageStrings = c.languages.map(v => v.name);
|
|
892
807
|
|
|
893
|
-
|
|
894
|
-
|
|
808
|
+
// Dev Language should exist in languages
|
|
895
809
|
if (!languageStrings.includes(c.devLanguage)) {
|
|
896
|
-
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default[
|
|
810
|
+
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default["default"].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
|
|
897
811
|
}
|
|
898
|
-
|
|
899
812
|
const foundLanguages = [];
|
|
900
|
-
|
|
901
813
|
for (const lang of c.languages) {
|
|
902
814
|
// Languages must only exist once
|
|
903
815
|
if (foundLanguages.includes(lang.name)) {
|
|
904
|
-
throw new ValidationError('DuplicateLanguage', `The language "${chalk__default[
|
|
816
|
+
throw new ValidationError('DuplicateLanguage', `The language "${chalk__default["default"].bold.cyan(lang.name)}" was defined multiple times.`);
|
|
905
817
|
}
|
|
818
|
+
foundLanguages.push(lang.name);
|
|
906
819
|
|
|
907
|
-
|
|
908
|
-
|
|
820
|
+
// Any extends must be in languages
|
|
909
821
|
if (lang.extends && !languageStrings.includes(lang.extends)) {
|
|
910
|
-
throw new ValidationError('InvalidExtends', `The language "${chalk__default[
|
|
822
|
+
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
823
|
}
|
|
912
824
|
}
|
|
913
|
-
|
|
914
825
|
const foundGeneratedLanguages = [];
|
|
915
|
-
|
|
916
826
|
for (const generatedLang of c.generatedLanguages || []) {
|
|
917
827
|
// Generated languages must only exist once
|
|
918
828
|
if (foundGeneratedLanguages.includes(generatedLang.name)) {
|
|
919
|
-
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default[
|
|
829
|
+
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" was defined multiple times.`);
|
|
920
830
|
}
|
|
831
|
+
foundGeneratedLanguages.push(generatedLang.name);
|
|
921
832
|
|
|
922
|
-
|
|
923
|
-
|
|
833
|
+
// Generated language names must not conflict with language names
|
|
924
834
|
if (languageStrings.includes(generatedLang.name)) {
|
|
925
|
-
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default[
|
|
926
|
-
}
|
|
927
|
-
|
|
835
|
+
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" is already defined as a language.`);
|
|
836
|
+
}
|
|
928
837
|
|
|
838
|
+
// Any extends must be in languages
|
|
929
839
|
if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) {
|
|
930
|
-
throw new ValidationError('InvalidExtends', `The generated language "${chalk__default[
|
|
840
|
+
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
841
|
}
|
|
932
842
|
}
|
|
933
|
-
|
|
934
843
|
trace('Configuration file is valid');
|
|
935
844
|
return true;
|
|
936
845
|
}
|
|
937
|
-
|
|
938
846
|
function createConfig(configFilePath) {
|
|
939
|
-
const cwd = path__default[
|
|
847
|
+
const cwd = path__default["default"].dirname(configFilePath);
|
|
940
848
|
return {
|
|
941
849
|
projectRoot: cwd,
|
|
942
850
|
...require(configFilePath)
|
|
943
851
|
};
|
|
944
852
|
}
|
|
945
|
-
|
|
946
853
|
async function resolveConfig(customConfigFilePath) {
|
|
947
|
-
const configFilePath = customConfigFilePath ? path__default[
|
|
948
|
-
|
|
854
|
+
const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : await findUp__default["default"]('vocab.config.js');
|
|
949
855
|
if (configFilePath) {
|
|
950
856
|
trace(`Resolved configuration file to ${configFilePath}`);
|
|
951
857
|
return createConfig(configFilePath);
|
|
952
858
|
}
|
|
953
|
-
|
|
954
859
|
trace('No configuration file found');
|
|
955
860
|
return null;
|
|
956
861
|
}
|
|
957
862
|
function resolveConfigSync(customConfigFilePath) {
|
|
958
|
-
const configFilePath = customConfigFilePath ? path__default[
|
|
959
|
-
|
|
863
|
+
const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : findUp__default["default"].sync('vocab.config.js');
|
|
960
864
|
if (configFilePath) {
|
|
961
865
|
trace(`Resolved configuration file to ${configFilePath}`);
|
|
962
866
|
return createConfig(configFilePath);
|
|
963
867
|
}
|
|
964
|
-
|
|
965
868
|
trace('No configuration file found');
|
|
966
869
|
return null;
|
|
967
870
|
}
|