@vocab/core 1.0.3 → 1.1.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.
@@ -10,6 +10,8 @@ var chokidar = require('chokidar');
10
10
  var chalk = require('chalk');
11
11
  var debug = require('debug');
12
12
  var glob = require('fast-glob');
13
+ var IntlMessageFormat = require('intl-messageformat');
14
+ var printer = require('@formatjs/icu-messageformat-parser/printer');
13
15
  var findUp = require('find-up');
14
16
  var Validator = require('fastest-validator');
15
17
 
@@ -21,6 +23,7 @@ var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
21
23
  var chalk__default = /*#__PURE__*/_interopDefault(chalk);
22
24
  var debug__default = /*#__PURE__*/_interopDefault(debug);
23
25
  var glob__default = /*#__PURE__*/_interopDefault(glob);
26
+ var IntlMessageFormat__default = /*#__PURE__*/_interopDefault(IntlMessageFormat);
24
27
  var findUp__default = /*#__PURE__*/_interopDefault(findUp);
25
28
  var Validator__default = /*#__PURE__*/_interopDefault(Validator);
26
29
 
@@ -108,11 +111,78 @@ function getTranslationMessages(translations) {
108
111
  return mapValues(translations, v => v.message);
109
112
  }
110
113
 
114
+ function generateLanguageFromTranslations({
115
+ baseTranslations,
116
+ generator
117
+ }) {
118
+ if (!generator.transformElement && !generator.transformMessage) {
119
+ return baseTranslations;
120
+ }
121
+
122
+ const translationKeys = Object.keys(baseTranslations);
123
+ const generatedTranslations = {};
124
+
125
+ for (const translationKey of translationKeys) {
126
+ const translation = baseTranslations[translationKey];
127
+ let transformedMessage = translation.message;
128
+
129
+ if (generator.transformElement) {
130
+ const messageAst = new IntlMessageFormat__default['default'](translation.message).getAst();
131
+ const transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement));
132
+ transformedMessage = printer.printAST(transformedAst);
133
+ }
134
+
135
+ if (generator.transformMessage) {
136
+ transformedMessage = generator.transformMessage(transformedMessage);
137
+ }
138
+
139
+ generatedTranslations[translationKey] = {
140
+ message: transformedMessage
141
+ };
142
+ }
143
+
144
+ return generatedTranslations;
145
+ }
146
+
147
+ function transformMessageFormatElement(transformElement) {
148
+ return messageFormatElement => {
149
+ const transformedMessageFormatElement = { ...messageFormatElement
150
+ };
151
+
152
+ switch (transformedMessageFormatElement.type) {
153
+ case icuMessageformatParser.TYPE.literal:
154
+ const transformedValue = transformElement(transformedMessageFormatElement.value);
155
+ transformedMessageFormatElement.value = transformedValue;
156
+ break;
157
+
158
+ case icuMessageformatParser.TYPE.select:
159
+ case icuMessageformatParser.TYPE.plural:
160
+ const transformedOptions = { ...transformedMessageFormatElement.options
161
+ };
162
+
163
+ for (const key of Object.keys(transformedOptions)) {
164
+ transformedOptions[key].value = transformedOptions[key].value.map(transformMessageFormatElement(transformElement));
165
+ }
166
+
167
+ break;
168
+
169
+ case icuMessageformatParser.TYPE.tag:
170
+ const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement));
171
+ transformedMessageFormatElement.children = transformedChildren;
172
+ break;
173
+ }
174
+
175
+ return transformedMessageFormatElement;
176
+ };
177
+ }
178
+
111
179
  function getUniqueKey(key, namespace) {
112
180
  return `${key}.${namespace}`;
113
181
  }
114
-
115
- function mergeWithDevLanguage(translation, devTranslation) {
182
+ function mergeWithDevLanguageTranslation({
183
+ translation,
184
+ devTranslation
185
+ }) {
116
186
  // Only use keys from the dev translation
117
187
  const keys = Object.keys(devTranslation);
118
188
  const newLanguage = {};
@@ -143,7 +213,7 @@ function getLanguageFallbacks({
143
213
  return languageFallbackMap;
144
214
  }
145
215
 
146
- function getLanguageHierarcy({
216
+ function getLanguageHierarchy({
147
217
  languages
148
218
  }) {
149
219
  const hierarchyMap = new Map();
@@ -152,19 +222,45 @@ function getLanguageHierarcy({
152
222
  });
153
223
 
154
224
  for (const lang of languages) {
155
- const langHierachy = [];
225
+ const langHierarchy = [];
156
226
  let currLang = lang.extends;
157
227
 
158
228
  while (currLang) {
159
- langHierachy.push(currLang);
229
+ langHierarchy.push(currLang);
160
230
  currLang = fallbacks.get(currLang);
161
231
  }
162
232
 
163
- hierarchyMap.set(lang.name, langHierachy);
233
+ hierarchyMap.set(lang.name, langHierarchy);
164
234
  }
165
235
 
166
236
  return hierarchyMap;
167
237
  }
238
+ function getFallbackLanguageOrder({
239
+ languages,
240
+ languageName,
241
+ devLanguage,
242
+ fallbacks
243
+ }) {
244
+ const languageHierarchy = getLanguageHierarchy({
245
+ languages
246
+ }).get(languageName);
247
+
248
+ if (!languageHierarchy) {
249
+ throw new Error(`Missing language hierarchy for ${languageName}`);
250
+ }
251
+
252
+ const fallbackLanguageOrder = [languageName];
253
+
254
+ if (fallbacks !== 'none') {
255
+ fallbackLanguageOrder.unshift(...languageHierarchy.reverse());
256
+
257
+ if (fallbacks === 'all' && fallbackLanguageOrder[0] !== devLanguage) {
258
+ fallbackLanguageOrder.unshift(devLanguage);
259
+ }
260
+ }
261
+
262
+ return fallbackLanguageOrder;
263
+ }
168
264
 
169
265
  function getNamespaceByFilePath(relativePath, {
170
266
  translationsDirectorySuffix = defaultTranslationDirSuffix
@@ -208,17 +304,17 @@ function getTranslationsFromFile(translations, {
208
304
 
209
305
  for (const [translationKey, translation] of Object.entries(keys)) {
210
306
  if (typeof translation === 'string') {
211
- printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
307
+ printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
212
308
  continue;
213
309
  }
214
310
 
215
311
  if (!translation) {
216
- printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
312
+ printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
217
313
  continue;
218
314
  }
219
315
 
220
316
  if (!translation.message || typeof translation.message !== 'string') {
221
- printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
317
+ printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
222
318
  continue;
223
319
  }
224
320
 
@@ -240,54 +336,44 @@ function loadAltLanguageFile({
240
336
  devLanguage,
241
337
  languages
242
338
  }) {
243
- const result = {};
244
- const languageHierarchy = getLanguageHierarcy({
245
- languages
246
- }).get(languageName);
247
-
248
- if (!languageHierarchy) {
249
- throw new Error(`Missing language hierarchy for ${languageName}`);
250
- }
251
-
252
- const fallbackLanguages = [languageName];
253
-
254
- if (fallbacks !== 'none') {
255
- fallbackLanguages.unshift(...languageHierarchy);
256
-
257
- if (fallbacks === 'all' && fallbackLanguages[0] !== devLanguage) {
258
- fallbackLanguages.unshift(devLanguage);
259
- }
260
- }
261
-
262
- trace(`Loading alt language file with precendence: ${fallbackLanguages.slice().reverse().join(' -> ')}`);
339
+ const altLanguageTranslation = {};
340
+ const fallbackLanguageOrder = getFallbackLanguageOrder({
341
+ languages,
342
+ languageName,
343
+ devLanguage,
344
+ fallbacks
345
+ });
346
+ trace(`Loading alt language file with precedence: ${fallbackLanguageOrder.slice().reverse().join(' -> ')}`);
263
347
 
264
- for (const fallbackLang of fallbackLanguages) {
265
- if (fallbackLang !== devLanguage) {
348
+ for (const fallbackLanguage of fallbackLanguageOrder) {
349
+ if (fallbackLanguage !== devLanguage) {
266
350
  try {
267
- const altFilePath = getAltLanguageFilePath(filePath, fallbackLang);
351
+ const altFilePath = getAltLanguageFilePath(filePath, fallbackLanguage);
268
352
  delete require.cache[altFilePath];
269
353
 
270
354
  const translationFile = require(altFilePath);
271
355
 
272
356
  const {
273
- keys
357
+ keys: fallbackLanguageTranslation
274
358
  } = getTranslationsFromFile(translationFile, {
275
359
  filePath: altFilePath,
276
360
  isAltLanguage: true
277
361
  });
278
- Object.assign(result, mergeWithDevLanguage(keys, devTranslation));
362
+ Object.assign(altLanguageTranslation, mergeWithDevLanguageTranslation({
363
+ translation: fallbackLanguageTranslation,
364
+ devTranslation
365
+ }));
279
366
  } catch (e) {
280
- trace(`Missing alt language file ${getAltLanguageFilePath(filePath, fallbackLang)}
367
+ trace(`Missing alt language file ${getAltLanguageFilePath(filePath, fallbackLanguage)}
281
368
  `);
282
369
  }
283
370
  } else {
284
- Object.assign(result, devTranslation);
371
+ Object.assign(altLanguageTranslation, devTranslation);
285
372
  }
286
373
  }
287
374
 
288
- return result;
375
+ return altLanguageTranslation;
289
376
  }
290
-
291
377
  function loadTranslation({
292
378
  filePath,
293
379
  fallbacks
@@ -320,6 +406,19 @@ function loadTranslation({
320
406
  }, userConfig);
321
407
  }
322
408
 
409
+ for (const generatedLanguage of userConfig.generatedLanguages || []) {
410
+ const {
411
+ name: generatedLanguageName,
412
+ generator
413
+ } = generatedLanguage;
414
+ const baseLanguage = generatedLanguage.extends || userConfig.devLanguage;
415
+ const baseTranslations = languageSet[baseLanguage];
416
+ languageSet[generatedLanguageName] = generateLanguageFromTranslations({
417
+ baseTranslations,
418
+ generator
419
+ });
420
+ }
421
+
323
422
  return {
324
423
  filePath,
325
424
  keys: Object.keys(devTranslation),
@@ -603,7 +702,7 @@ async function writeIfChanged(filepath, contents) {
603
702
  }
604
703
 
605
704
  /* eslint-disable no-console */
606
- function findMissingKeys(loadedTranslation, devLanguageName, altLangauges) {
705
+ function findMissingKeys(loadedTranslation, devLanguageName, altLanguages) {
607
706
  const devLanguage = loadedTranslation.languages[devLanguageName];
608
707
 
609
708
  if (!devLanguage) {
@@ -615,7 +714,7 @@ function findMissingKeys(loadedTranslation, devLanguageName, altLangauges) {
615
714
  const requiredKeys = Object.keys(devLanguage);
616
715
 
617
716
  if (requiredKeys.length > 0) {
618
- for (const altLanguageName of altLangauges) {
717
+ for (const altLanguageName of altLanguages) {
619
718
  var _loadedTranslation$la;
620
719
 
621
720
  const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {};
@@ -691,6 +790,35 @@ const schema = {
691
790
  }
692
791
  }
693
792
  },
793
+ generatedLanguages: {
794
+ type: 'array',
795
+ items: {
796
+ type: 'object',
797
+ props: {
798
+ name: {
799
+ type: 'string'
800
+ },
801
+ extends: {
802
+ type: 'string',
803
+ optional: true
804
+ },
805
+ generator: {
806
+ type: 'object',
807
+ props: {
808
+ transformElement: {
809
+ type: 'function',
810
+ optional: true
811
+ },
812
+ transformMessage: {
813
+ type: 'function',
814
+ optional: true
815
+ }
816
+ }
817
+ }
818
+ }
819
+ },
820
+ optional: true
821
+ },
694
822
  translationsDirectorySuffix: {
695
823
  type: 'string',
696
824
  optional: true
@@ -728,13 +856,12 @@ function validateConfig(c) {
728
856
 
729
857
  return v.message;
730
858
  }).join(' \n'));
731
- } // Dev Language should exist in languages
732
-
859
+ }
733
860
 
734
- const languageStrings = c.languages.map(v => v.name);
861
+ const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages
735
862
 
736
863
  if (!languageStrings.includes(c.devLanguage)) {
737
- throw new ValidationError('InvalidDevLanguage', `InvalidDevLanguage: The dev language "${chalk__default['default'].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
864
+ throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default['default'].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
738
865
  }
739
866
 
740
867
  const foundLanguages = [];
@@ -752,6 +879,26 @@ function validateConfig(c) {
752
879
  }
753
880
  }
754
881
 
882
+ const foundGeneratedLanguages = [];
883
+
884
+ for (const generatedLang of c.generatedLanguages || []) {
885
+ // Generated languages must only exist once
886
+ if (foundGeneratedLanguages.includes(generatedLang.name)) {
887
+ throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}" was defined multiple times.`);
888
+ }
889
+
890
+ foundGeneratedLanguages.push(generatedLang.name); // Generated language names must not conflict with language names
891
+
892
+ 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
+
896
+
897
+ 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(', ')}.`);
899
+ }
900
+ }
901
+
755
902
  trace('Configuration file is valid');
756
903
  return true;
757
904
  }