@vocab/core 1.1.2 → 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.
@@ -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['default'](`vocab:core`);
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['default'].dirname(tsFilePath);
78
- const result = path__default['default'].normalize(path__default['default'].join(directory, devTranslationFileName));
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['default'].dirname(altLanguageFilePath);
84
- const result = path__default['default'].normalize(path__default['default'].join(directory, devTranslationFileName));
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['default'].dirname(devLanguageFilePath);
90
- const result = path__default['default'].normalize(path__default['default'].join(directory, 'index.ts'));
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['default'].dirname(devLanguageFilePath);
96
- const result = path__default['default'].normalize(path__default['default'].join(directory, `${language}.translations.json`));
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['default'].normalize(result);
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['default'](translation.message).getAst();
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 = { ...messageFormatElement
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 = { ...transformedMessageFormatElement.options
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,89 +221,87 @@ 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['default'].dirname(relativePath).replace(/^src\//, '').replace(/\//g, '_');
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['default'].red('Error loading translation:'), ...params);
247
+ console.error(chalk__default["default"].red('Error loading translation:'), ...params);
280
248
  }
281
-
282
- function getTranslationsFromFile(translations, {
249
+ function getTranslationsFromFile(translationFileContents, {
283
250
  isAltLanguage,
284
- filePath
251
+ filePath,
252
+ withTags
285
253
  }) {
286
- if (!translations || typeof translations !== 'object') {
287
- throw new Error(`Unable to read translation file ${filePath}. Translations must be an object`);
254
+ if (!translationFileContents || typeof translationFileContents !== 'object') {
255
+ throw new Error(`Unable to read translation file ${filePath}. Translations must be an object.`);
288
256
  }
289
-
290
257
  const {
291
258
  $namespace,
259
+ _meta,
292
260
  ...keys
293
- } = translations;
294
-
261
+ } = translationFileContents;
295
262
  if (isAltLanguage && $namespace) {
296
263
  printValidationError(`Found $namespace in alt language file in ${filePath}. $namespace is only used in the dev language and will be ignored.`);
297
264
  }
298
-
299
265
  if (!isAltLanguage && $namespace && typeof $namespace !== 'string') {
300
266
  printValidationError(`Found non-string $namespace in language file in ${filePath}. $namespace must be a string.`);
301
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
+ }
302
271
 
272
+ // Never return tags if we're fetching translations for an alt language
273
+ const includeTags = !isAltLanguage && withTags;
303
274
  const validKeys = {};
304
-
305
- for (const [translationKey, translation] of Object.entries(keys)) {
275
+ for (const [translationKey, {
276
+ tags,
277
+ ...translation
278
+ }] of Object.entries(keys)) {
306
279
  if (typeof translation === 'string') {
307
280
  printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
308
281
  continue;
309
282
  }
310
-
311
283
  if (!translation) {
312
284
  printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
313
285
  continue;
314
286
  }
315
-
316
287
  if (!translation.message || typeof translation.message !== 'string') {
317
288
  printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
318
289
  continue;
319
290
  }
320
-
321
- validKeys[translationKey] = translation;
291
+ validKeys[translationKey] = {
292
+ ...translation,
293
+ tags: includeTags ? tags : undefined
294
+ };
322
295
  }
323
-
296
+ const metadata = {
297
+ tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined
298
+ };
324
299
  return {
325
300
  $namespace,
326
- keys: validKeys
301
+ keys: validKeys,
302
+ metadata
327
303
  };
328
304
  }
329
-
330
305
  function loadAltLanguageFile({
331
306
  filePath,
332
307
  languageName,
@@ -344,15 +319,12 @@ function loadAltLanguageFile({
344
319
  fallbacks
345
320
  });
346
321
  trace(`Loading alt language file with precedence: ${fallbackLanguageOrder.slice().reverse().join(' -> ')}`);
347
-
348
322
  for (const fallbackLanguage of fallbackLanguageOrder) {
349
323
  if (fallbackLanguage !== devLanguage) {
350
324
  try {
351
325
  const altFilePath = getAltLanguageFilePath(filePath, fallbackLanguage);
352
326
  delete require.cache[altFilePath];
353
-
354
327
  const translationFile = require(altFilePath);
355
-
356
328
  const {
357
329
  keys: fallbackLanguageTranslation
358
330
  } = getTranslationsFromFile(translationFile, {
@@ -371,41 +343,46 @@ function loadAltLanguageFile({
371
343
  Object.assign(altLanguageTranslation, devTranslation);
372
344
  }
373
345
  }
374
-
375
346
  return altLanguageTranslation;
376
347
  }
348
+ function stripTagsFromTranslations(translations) {
349
+ return Object.fromEntries(Object.entries(translations).map(([key, {
350
+ tags,
351
+ ...rest
352
+ }]) => [key, rest]));
353
+ }
377
354
  function loadTranslation({
378
355
  filePath,
379
- fallbacks
356
+ fallbacks,
357
+ withTags
380
358
  }, userConfig) {
381
359
  trace(`Loading translation file in "${fallbacks}" fallback mode: "${filePath}"`);
382
360
  const languageSet = {};
383
361
  delete require.cache[filePath];
384
-
385
362
  const translationContent = require(filePath);
386
-
387
- const relativePath = path__default['default'].relative(userConfig.projectRoot || process.cwd(), filePath);
363
+ const relativePath = path__default["default"].relative(userConfig.projectRoot || process.cwd(), filePath);
388
364
  const {
389
365
  $namespace,
390
- keys: devTranslation
366
+ keys: devTranslation,
367
+ metadata
391
368
  } = getTranslationsFromFile(translationContent, {
392
369
  filePath,
393
- isAltLanguage: false
370
+ isAltLanguage: false,
371
+ withTags
394
372
  });
395
373
  const namespace = typeof $namespace === 'string' ? $namespace : getNamespaceByFilePath(relativePath, userConfig);
396
374
  trace(`Found file ${filePath}. Using namespace ${namespace}`);
397
375
  languageSet[userConfig.devLanguage] = devTranslation;
376
+ const devTranslationNoTags = withTags ? stripTagsFromTranslations(devTranslation) : devTranslation;
398
377
  const altLanguages = getAltLanguages(userConfig);
399
-
400
378
  for (const languageName of altLanguages) {
401
379
  languageSet[languageName] = loadAltLanguageFile({
402
380
  filePath,
403
381
  languageName,
404
- devTranslation,
382
+ devTranslation: devTranslationNoTags,
405
383
  fallbacks
406
384
  }, userConfig);
407
385
  }
408
-
409
386
  for (const generatedLanguage of userConfig.generatedLanguages || []) {
410
387
  const {
411
388
  name: generatedLanguageName,
@@ -418,24 +395,25 @@ function loadTranslation({
418
395
  generator
419
396
  });
420
397
  }
421
-
422
398
  return {
423
399
  filePath,
424
400
  keys: Object.keys(devTranslation),
425
401
  namespace,
426
402
  relativePath,
427
- languages: languageSet
403
+ languages: languageSet,
404
+ metadata
428
405
  };
429
406
  }
430
407
  async function loadAllTranslations({
431
408
  fallbacks,
432
- includeNodeModules
409
+ includeNodeModules,
410
+ withTags
433
411
  }, config) {
434
412
  const {
435
413
  projectRoot,
436
414
  ignore = []
437
415
  } = config;
438
- const translationFiles = await glob__default['default'](getDevTranslationFileGlob(config), {
416
+ const translationFiles = await glob__default["default"](getDevTranslationFileGlob(config), {
439
417
  ignore: includeNodeModules ? ignore : [...ignore, '**/node_modules/**'],
440
418
  absolute: true,
441
419
  cwd: projectRoot
@@ -443,45 +421,37 @@ async function loadAllTranslations({
443
421
  trace(`Found ${translationFiles.length} translation files`);
444
422
  const result = await Promise.all(translationFiles.map(filePath => loadTranslation({
445
423
  filePath,
446
- fallbacks
424
+ fallbacks,
425
+ withTags
447
426
  }, config)));
448
427
  const keys = new Set();
449
-
450
428
  for (const loadedTranslation of result) {
451
429
  for (const key of loadedTranslation.keys) {
452
430
  const uniqueKey = getUniqueKey(key, loadedTranslation.namespace);
453
-
454
431
  if (keys.has(uniqueKey)) {
455
432
  trace(`Duplicate keys found`);
456
433
  throw new Error(`Duplicate keys found. Key with namespace ${loadedTranslation.namespace} and key ${key} was found multiple times.`);
457
434
  }
458
-
459
435
  keys.add(uniqueKey);
460
436
  }
461
437
  }
462
-
463
438
  return result;
464
439
  }
465
440
 
466
441
  const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'");
467
-
468
442
  const encodeBackslash = v => v.replace(/\\/g, '\\\\');
469
-
470
443
  function extractHasTags(ast) {
471
444
  return ast.some(element => {
472
445
  if (icuMessageformatParser.isSelectElement(element)) {
473
446
  const children = Object.values(element.options).map(o => o.value);
474
447
  return children.some(child => extractHasTags(child));
475
448
  }
476
-
477
449
  return icuMessageformatParser.isTagElement(element);
478
450
  });
479
451
  }
480
-
481
452
  function extractParamTypes(ast) {
482
453
  let params = {};
483
454
  let imports = new Set();
484
-
485
455
  for (const element of ast) {
486
456
  if (icuMessageformatParser.isArgumentElement(element)) {
487
457
  params[element.value] = 'string';
@@ -489,6 +459,15 @@ function extractParamTypes(ast) {
489
459
  params[element.value] = 'number';
490
460
  } else if (icuMessageformatParser.isPluralElement(element)) {
491
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
+ }
492
471
  } else if (icuMessageformatParser.isDateElement(element) || icuMessageformatParser.isTimeElement(element)) {
493
472
  params[element.value] = 'Date | number';
494
473
  } else if (icuMessageformatParser.isTagElement(element)) {
@@ -496,29 +475,27 @@ function extractParamTypes(ast) {
496
475
  imports.add(`import { FormatXMLElementFn } from '@vocab/types';`);
497
476
  const [subParams, subImports] = extractParamTypes(element.children);
498
477
  imports = new Set([...imports, ...subImports]);
499
- params = { ...params,
478
+ params = {
479
+ ...params,
500
480
  ...subParams
501
481
  };
502
482
  } else if (icuMessageformatParser.isSelectElement(element)) {
503
483
  params[element.value] = Object.keys(element.options).map(o => `'${o}'`).join(' | ');
504
484
  const children = Object.values(element.options).map(o => o.value);
505
-
506
485
  for (const child of children) {
507
486
  const [subParams, subImports] = extractParamTypes(child);
508
487
  imports = new Set([...imports, ...subImports]);
509
- params = { ...params,
488
+ params = {
489
+ ...params,
510
490
  ...subParams
511
491
  };
512
492
  }
513
493
  }
514
494
  }
515
-
516
495
  return [params, imports];
517
496
  }
518
-
519
497
  function serialiseObjectToType(v) {
520
498
  let result = '';
521
-
522
499
  for (const [key, value] of Object.entries(v)) {
523
500
  if (value && typeof value === 'object') {
524
501
  result += `'${encodeWithinSingleQuotes(key)}': ${serialiseObjectToType(value)},`;
@@ -526,32 +503,25 @@ function serialiseObjectToType(v) {
526
503
  result += `'${encodeWithinSingleQuotes(key)}': ${value},`;
527
504
  }
528
505
  }
529
-
530
506
  return `{ ${result} }`;
531
507
  }
532
-
533
508
  const banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`;
534
-
535
509
  function serialiseTranslationRuntime(value, imports, loadedTranslation) {
536
510
  trace('Serialising translations:', loadedTranslation);
537
511
  const translationsType = {};
538
-
539
512
  for (const [key, {
540
513
  params,
541
514
  message,
542
515
  hasTags
543
516
  }] of value.entries()) {
544
517
  let translationFunctionString = `() => ${message}`;
545
-
546
518
  if (Object.keys(params).length > 0) {
547
519
  const formatGeneric = hasTags ? '<T = string>' : '';
548
520
  const formatReturn = hasTags ? 'string | T | Array<string | T>' : 'string';
549
521
  translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
550
522
  }
551
-
552
523
  translationsType[encodeBackslash(key)] = translationFunctionString;
553
524
  }
554
-
555
525
  const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
556
526
  const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | ');
557
527
  return `${banner}
@@ -563,34 +533,31 @@ function serialiseTranslationRuntime(value, imports, loadedTranslation) {
563
533
 
564
534
  export default translations;`;
565
535
  }
566
-
567
536
  async function generateRuntime(loadedTranslation) {
568
537
  const {
569
538
  languages: loadedLanguages,
570
539
  filePath
571
540
  } = loadedTranslation;
572
- trace('Generating types for', loadedTranslation.filePath);
541
+ trace('Generating types for', filePath);
573
542
  const translationTypes = new Map();
574
543
  let imports = new Set();
575
-
576
544
  for (const key of loadedTranslation.keys) {
577
545
  let params = {};
578
546
  const messages = new Set();
579
547
  let hasTags = false;
580
-
581
548
  for (const translatedLanguage of Object.values(loadedLanguages)) {
582
549
  if (translatedLanguage[key]) {
583
550
  const ast = icuMessageformatParser.parse(translatedLanguage[key].message);
584
551
  hasTags = hasTags || extractHasTags(ast);
585
552
  const [parsedParams, parsedImports] = extractParamTypes(ast);
586
553
  imports = new Set([...imports, ...parsedImports]);
587
- params = { ...params,
554
+ params = {
555
+ ...params,
588
556
  ...parsedParams
589
557
  };
590
558
  messages.add(`'${encodeWithinSingleQuotes(translatedLanguage[key].message)}'`);
591
559
  }
592
560
  }
593
-
594
561
  const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string';
595
562
  translationTypes.set(key, {
596
563
  params,
@@ -599,10 +566,10 @@ async function generateRuntime(loadedTranslation) {
599
566
  returnType
600
567
  });
601
568
  }
602
-
603
- const prettierConfig = await prettier__default['default'].resolveConfig(filePath);
569
+ const prettierConfig = await prettier__default["default"].resolveConfig(filePath);
604
570
  const serializedTranslationType = serialiseTranslationRuntime(translationTypes, imports, loadedTranslation);
605
- const declaration = prettier__default['default'].format(serializedTranslationType, { ...prettierConfig,
571
+ const declaration = prettier__default["default"].format(serializedTranslationType, {
572
+ ...prettierConfig,
606
573
  parser: 'typescript'
607
574
  });
608
575
  const outputFilePath = getTSFileFromDevLanguageFile(filePath);
@@ -611,48 +578,41 @@ async function generateRuntime(loadedTranslation) {
611
578
  }
612
579
  function watch(config) {
613
580
  const cwd = config.projectRoot || process.cwd();
614
- const watcher = chokidar__default['default'].watch([getDevTranslationFileGlob(config), getAltTranslationFileGlob(config), getTranslationFolderGlob(config)], {
581
+ const watcher = chokidar__default["default"].watch([getDevTranslationFileGlob(config), getAltTranslationFileGlob(config), getTranslationFolderGlob(config)], {
615
582
  cwd,
616
583
  ignored: config.ignore ? [...config.ignore, '**/node_modules/**'] : ['**/node_modules/**'],
617
584
  ignoreInitial: true
618
585
  });
619
-
620
586
  const onTranslationChange = async relativePath => {
621
587
  trace(`Detected change for file ${relativePath}`);
622
588
  let targetFile;
623
-
624
589
  if (isDevLanguageFile(relativePath)) {
625
- targetFile = path__default['default'].resolve(cwd, relativePath);
590
+ targetFile = path__default["default"].resolve(cwd, relativePath);
626
591
  } else if (isAltLanguageFile(relativePath)) {
627
- targetFile = getDevLanguageFileFromAltLanguageFile(path__default['default'].resolve(cwd, relativePath));
592
+ targetFile = getDevLanguageFileFromAltLanguageFile(path__default["default"].resolve(cwd, relativePath));
628
593
  }
629
-
630
594
  if (targetFile) {
631
595
  try {
632
- const loadedTranslation = await loadTranslation({
596
+ const loadedTranslation = loadTranslation({
633
597
  filePath: targetFile,
634
598
  fallbacks: 'all'
635
599
  }, config);
636
600
  await generateRuntime(loadedTranslation);
637
601
  } catch (e) {
638
602
  // eslint-disable-next-line no-console
639
- console.log('Failed to generate types for', relativePath); // eslint-disable-next-line no-console
640
-
603
+ console.log('Failed to generate types for', relativePath);
604
+ // eslint-disable-next-line no-console
641
605
  console.error(e);
642
606
  }
643
607
  }
644
608
  };
645
-
646
609
  const onNewDirectory = async relativePath => {
647
610
  trace('Detected new directory', relativePath);
648
-
649
611
  if (!isTranslationDirectory(relativePath, config)) {
650
612
  trace('Ignoring non-translation directory:', relativePath);
651
613
  return;
652
614
  }
653
-
654
- const newFilePath = path__default['default'].join(relativePath, devTranslationFileName);
655
-
615
+ const newFilePath = path__default["default"].join(relativePath, devTranslationFileName);
656
616
  if (!fs.existsSync(newFilePath)) {
657
617
  await fs.promises.writeFile(newFilePath, JSON.stringify({}, null, 2));
658
618
  trace('Created new empty translation file:', newFilePath);
@@ -660,7 +620,6 @@ function watch(config) {
660
620
  trace(`New directory already contains translation file. Skipping creation. Existing file ${newFilePath}`);
661
621
  }
662
622
  };
663
-
664
623
  watcher.on('addDir', onNewDirectory);
665
624
  watcher.on('add', onTranslationChange).on('change', onTranslationChange);
666
625
  return () => watcher.close();
@@ -672,28 +631,24 @@ async function compile({
672
631
  fallbacks: 'all',
673
632
  includeNodeModules: false
674
633
  }, config);
675
-
676
634
  for (const loadedTranslation of translations) {
677
635
  await generateRuntime(loadedTranslation);
678
636
  }
679
-
680
637
  if (shouldWatch) {
681
638
  trace('Listening for changes to files...');
682
639
  return watch(config);
683
640
  }
684
641
  }
685
-
686
642
  async function writeIfChanged(filepath, contents) {
687
643
  let hasChanged = true;
688
-
689
644
  try {
690
645
  const existingContents = await fs.promises.readFile(filepath, {
691
646
  encoding: 'utf-8'
692
647
  });
693
648
  hasChanged = existingContents !== contents;
694
- } catch (e) {// ignore error, likely a file doesn't exist error so we want to write anyway
649
+ } catch (e) {
650
+ // ignore error, likely a file doesn't exist error so we want to write anyway
695
651
  }
696
-
697
652
  if (hasChanged) {
698
653
  await fs.promises.writeFile(filepath, contents, {
699
654
  encoding: 'utf-8'
@@ -704,36 +659,28 @@ async function writeIfChanged(filepath, contents) {
704
659
  /* eslint-disable no-console */
705
660
  function findMissingKeys(loadedTranslation, devLanguageName, altLanguages) {
706
661
  const devLanguage = loadedTranslation.languages[devLanguageName];
707
-
708
662
  if (!devLanguage) {
709
663
  throw new Error(`Failed to load dev language: ${loadedTranslation.filePath}`);
710
664
  }
711
-
712
665
  const result = {};
713
666
  let valid = true;
714
667
  const requiredKeys = Object.keys(devLanguage);
715
-
716
668
  if (requiredKeys.length > 0) {
717
669
  for (const altLanguageName of altLanguages) {
718
670
  var _loadedTranslation$la;
719
-
720
671
  const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {};
721
-
722
672
  for (const key of requiredKeys) {
723
673
  var _altLanguage$key;
724
-
725
674
  if (typeof ((_altLanguage$key = altLanguage[key]) === null || _altLanguage$key === void 0 ? void 0 : _altLanguage$key.message) !== 'string') {
726
675
  if (!result[altLanguageName]) {
727
676
  result[altLanguageName] = [];
728
677
  }
729
-
730
678
  result[altLanguageName].push(key);
731
679
  valid = false;
732
680
  }
733
681
  }
734
682
  }
735
683
  }
736
-
737
684
  return [valid, result];
738
685
  }
739
686
  async function validate(config) {
@@ -742,21 +689,17 @@ async function validate(config) {
742
689
  includeNodeModules: true
743
690
  }, config);
744
691
  let valid = true;
745
-
746
692
  for (const loadedTranslation of allTranslations) {
747
693
  const [translationValid, result] = findMissingKeys(loadedTranslation, config.devLanguage, getAltLanguages(config));
748
-
749
694
  if (!translationValid) {
750
695
  valid = false;
751
- console.log(chalk__default['default'].red`Incomplete translations: "${chalk__default['default'].bold(loadedTranslation.relativePath)}"`);
752
-
696
+ console.log(chalk__default["default"].red`Incomplete translations: "${chalk__default["default"].bold(loadedTranslation.relativePath)}"`);
753
697
  for (const lang of Object.keys(result)) {
754
698
  const missingKeys = result[lang];
755
- console.log(chalk__default['default'].yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', '));
699
+ console.log(chalk__default["default"].yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', '));
756
700
  }
757
701
  }
758
702
  }
759
-
760
703
  return valid;
761
704
  }
762
705
 
@@ -766,10 +709,9 @@ class ValidationError extends Error {
766
709
  this.code = code;
767
710
  this.rawMessage = message;
768
711
  }
769
-
770
712
  }
771
713
 
772
- const validator = new Validator__default['default']();
714
+ const validator = new Validator__default["default"]();
773
715
  const schema = {
774
716
  $$strict: true,
775
717
  devLanguage: {
@@ -834,102 +776,85 @@ const schema = {
834
776
  }
835
777
  };
836
778
  const checkConfigFile = validator.compile(schema);
837
-
838
779
  const splitMap = (message, callback) => message.split(' ,').map(v => callback(v)).join(' ,');
839
-
840
780
  function validateConfig(c) {
841
- trace('Validating configuration file'); // Note: checkConfigFile mutates the config file by applying defaults
842
-
781
+ trace('Validating configuration file');
782
+ // Note: checkConfigFile mutates the config file by applying defaults
843
783
  const isValid = checkConfigFile(c);
844
-
845
784
  if (isValid !== true) {
846
785
  throw new ValidationError('InvalidStructure', (Array.isArray(isValid) ? isValid : []).map(v => {
847
786
  if (v.type === 'objectStrict') {
848
- return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default['default'].cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk__default['default'].green)}`;
787
+ return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default["default"].cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk__default["default"].green)}`;
849
788
  }
850
-
851
789
  if (v.field) {
852
790
  var _v$message;
853
-
854
- 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));
855
792
  }
856
-
857
793
  return v.message;
858
794
  }).join(' \n'));
859
795
  }
796
+ const languageStrings = c.languages.map(v => v.name);
860
797
 
861
- const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages
862
-
798
+ // Dev Language should exist in languages
863
799
  if (!languageStrings.includes(c.devLanguage)) {
864
- throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default['default'].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
800
+ throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default["default"].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
865
801
  }
866
-
867
802
  const foundLanguages = [];
868
-
869
803
  for (const lang of c.languages) {
870
804
  // Languages must only exist once
871
805
  if (foundLanguages.includes(lang.name)) {
872
- throw new ValidationError('DuplicateLanguage', `The language "${chalk__default['default'].bold.cyan(lang.name)}" was defined multiple times.`);
806
+ throw new ValidationError('DuplicateLanguage', `The language "${chalk__default["default"].bold.cyan(lang.name)}" was defined multiple times.`);
873
807
  }
808
+ foundLanguages.push(lang.name);
874
809
 
875
- foundLanguages.push(lang.name); // Any extends must be in languages
876
-
810
+ // Any extends must be in languages
877
811
  if (lang.extends && !languageStrings.includes(lang.extends)) {
878
- 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(', ')}.`);
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(', ')}.`);
879
813
  }
880
814
  }
881
-
882
815
  const foundGeneratedLanguages = [];
883
-
884
816
  for (const generatedLang of c.generatedLanguages || []) {
885
817
  // Generated languages must only exist once
886
818
  if (foundGeneratedLanguages.includes(generatedLang.name)) {
887
- throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}" was defined multiple times.`);
819
+ throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" was defined multiple times.`);
888
820
  }
821
+ foundGeneratedLanguages.push(generatedLang.name);
889
822
 
890
- foundGeneratedLanguages.push(generatedLang.name); // Generated language names must not conflict with language names
891
-
823
+ // Generated language names must not conflict with language names
892
824
  if (languageStrings.includes(generatedLang.name)) {
893
- throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}" is already defined as a language.`);
894
- } // Any extends must be in languages
895
-
825
+ throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" is already defined as a language.`);
826
+ }
896
827
 
828
+ // Any extends must be in languages
897
829
  if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) {
898
- 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(', ')}.`);
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(', ')}.`);
899
831
  }
900
832
  }
901
-
902
833
  trace('Configuration file is valid');
903
834
  return true;
904
835
  }
905
-
906
836
  function createConfig(configFilePath) {
907
- const cwd = path__default['default'].dirname(configFilePath);
837
+ const cwd = path__default["default"].dirname(configFilePath);
908
838
  return {
909
839
  projectRoot: cwd,
910
840
  ...require(configFilePath)
911
841
  };
912
842
  }
913
-
914
843
  async function resolveConfig(customConfigFilePath) {
915
- const configFilePath = customConfigFilePath ? path__default['default'].resolve(customConfigFilePath) : await findUp__default['default']('vocab.config.js');
916
-
844
+ const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : await findUp__default["default"]('vocab.config.js');
917
845
  if (configFilePath) {
918
846
  trace(`Resolved configuration file to ${configFilePath}`);
919
847
  return createConfig(configFilePath);
920
848
  }
921
-
922
849
  trace('No configuration file found');
923
850
  return null;
924
851
  }
925
852
  function resolveConfigSync(customConfigFilePath) {
926
- const configFilePath = customConfigFilePath ? path__default['default'].resolve(customConfigFilePath) : findUp__default['default'].sync('vocab.config.js');
927
-
853
+ const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : findUp__default["default"].sync('vocab.config.js');
928
854
  if (configFilePath) {
929
855
  trace(`Resolved configuration file to ${configFilePath}`);
930
856
  return createConfig(configFilePath);
931
857
  }
932
-
933
858
  trace('No configuration file found');
934
859
  return null;
935
860
  }