@vocab/core 1.6.4 → 1.6.5-fix-messageformat-import-20250923004826

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