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