@vocab/core 1.2.0 → 1.2.2

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