@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.
- package/CHANGELOG.md +28 -0
- package/README.md +148 -1
- package/dist/declarations/src/generate-language.d.ts +5 -0
- package/dist/declarations/src/load-translations.d.ts +18 -2
- package/dist/declarations/src/validate/index.d.ts +1 -1
- package/dist/vocab-core.cjs.dev.js +191 -44
- package/dist/vocab-core.cjs.prod.js +191 -44
- package/dist/vocab-core.esm.js +191 -45
- package/package.json +27 -2
- package/src/ValidationError.ts +0 -9
- package/src/compile.ts +0 -329
- package/src/config.test.ts +0 -39
- package/src/config.ts +0 -144
- package/src/icu-handler.ts +0 -35
- package/src/index.ts +0 -14
- package/src/load-translations.ts +0 -317
- package/src/logger.ts +0 -9
- package/src/runtime.test.ts +0 -153
- package/src/runtime.ts +0 -12
- package/src/translation-file.ts +0 -54
- package/src/utils.test.ts +0 -78
- package/src/utils.ts +0 -143
- package/src/validate/index.test.ts +0 -64
- package/src/validate/index.ts +0 -81
package/dist/vocab-core.esm.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { existsSync, promises } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { parse, isSelectElement, isTagElement, isArgumentElement, isNumberElement, isPluralElement, isDateElement, isTimeElement } from '@formatjs/icu-messageformat-parser';
|
|
3
|
+
import { TYPE, parse, isSelectElement, isTagElement, isArgumentElement, isNumberElement, isPluralElement, isDateElement, isTimeElement } from '@formatjs/icu-messageformat-parser';
|
|
4
4
|
import prettier from 'prettier';
|
|
5
5
|
import chokidar from 'chokidar';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import debug from 'debug';
|
|
8
8
|
import glob from 'fast-glob';
|
|
9
|
+
import IntlMessageFormat from 'intl-messageformat';
|
|
10
|
+
import { printAST } from '@formatjs/icu-messageformat-parser/printer';
|
|
9
11
|
import findUp from 'find-up';
|
|
10
12
|
import Validator from 'fastest-validator';
|
|
11
13
|
|
|
@@ -93,11 +95,78 @@ function getTranslationMessages(translations) {
|
|
|
93
95
|
return mapValues(translations, v => v.message);
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
function generateLanguageFromTranslations({
|
|
99
|
+
baseTranslations,
|
|
100
|
+
generator
|
|
101
|
+
}) {
|
|
102
|
+
if (!generator.transformElement && !generator.transformMessage) {
|
|
103
|
+
return baseTranslations;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const translationKeys = Object.keys(baseTranslations);
|
|
107
|
+
const generatedTranslations = {};
|
|
108
|
+
|
|
109
|
+
for (const translationKey of translationKeys) {
|
|
110
|
+
const translation = baseTranslations[translationKey];
|
|
111
|
+
let transformedMessage = translation.message;
|
|
112
|
+
|
|
113
|
+
if (generator.transformElement) {
|
|
114
|
+
const messageAst = new IntlMessageFormat(translation.message).getAst();
|
|
115
|
+
const transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement));
|
|
116
|
+
transformedMessage = printAST(transformedAst);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (generator.transformMessage) {
|
|
120
|
+
transformedMessage = generator.transformMessage(transformedMessage);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
generatedTranslations[translationKey] = {
|
|
124
|
+
message: transformedMessage
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return generatedTranslations;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function transformMessageFormatElement(transformElement) {
|
|
132
|
+
return messageFormatElement => {
|
|
133
|
+
const transformedMessageFormatElement = { ...messageFormatElement
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
switch (transformedMessageFormatElement.type) {
|
|
137
|
+
case TYPE.literal:
|
|
138
|
+
const transformedValue = transformElement(transformedMessageFormatElement.value);
|
|
139
|
+
transformedMessageFormatElement.value = transformedValue;
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case TYPE.select:
|
|
143
|
+
case TYPE.plural:
|
|
144
|
+
const transformedOptions = { ...transformedMessageFormatElement.options
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
for (const key of Object.keys(transformedOptions)) {
|
|
148
|
+
transformedOptions[key].value = transformedOptions[key].value.map(transformMessageFormatElement(transformElement));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
case TYPE.tag:
|
|
154
|
+
const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement));
|
|
155
|
+
transformedMessageFormatElement.children = transformedChildren;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return transformedMessageFormatElement;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
96
163
|
function getUniqueKey(key, namespace) {
|
|
97
164
|
return `${key}.${namespace}`;
|
|
98
165
|
}
|
|
99
|
-
|
|
100
|
-
|
|
166
|
+
function mergeWithDevLanguageTranslation({
|
|
167
|
+
translation,
|
|
168
|
+
devTranslation
|
|
169
|
+
}) {
|
|
101
170
|
// Only use keys from the dev translation
|
|
102
171
|
const keys = Object.keys(devTranslation);
|
|
103
172
|
const newLanguage = {};
|
|
@@ -128,7 +197,7 @@ function getLanguageFallbacks({
|
|
|
128
197
|
return languageFallbackMap;
|
|
129
198
|
}
|
|
130
199
|
|
|
131
|
-
function
|
|
200
|
+
function getLanguageHierarchy({
|
|
132
201
|
languages
|
|
133
202
|
}) {
|
|
134
203
|
const hierarchyMap = new Map();
|
|
@@ -137,19 +206,45 @@ function getLanguageHierarcy({
|
|
|
137
206
|
});
|
|
138
207
|
|
|
139
208
|
for (const lang of languages) {
|
|
140
|
-
const
|
|
209
|
+
const langHierarchy = [];
|
|
141
210
|
let currLang = lang.extends;
|
|
142
211
|
|
|
143
212
|
while (currLang) {
|
|
144
|
-
|
|
213
|
+
langHierarchy.push(currLang);
|
|
145
214
|
currLang = fallbacks.get(currLang);
|
|
146
215
|
}
|
|
147
216
|
|
|
148
|
-
hierarchyMap.set(lang.name,
|
|
217
|
+
hierarchyMap.set(lang.name, langHierarchy);
|
|
149
218
|
}
|
|
150
219
|
|
|
151
220
|
return hierarchyMap;
|
|
152
221
|
}
|
|
222
|
+
function getFallbackLanguageOrder({
|
|
223
|
+
languages,
|
|
224
|
+
languageName,
|
|
225
|
+
devLanguage,
|
|
226
|
+
fallbacks
|
|
227
|
+
}) {
|
|
228
|
+
const languageHierarchy = getLanguageHierarchy({
|
|
229
|
+
languages
|
|
230
|
+
}).get(languageName);
|
|
231
|
+
|
|
232
|
+
if (!languageHierarchy) {
|
|
233
|
+
throw new Error(`Missing language hierarchy for ${languageName}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const fallbackLanguageOrder = [languageName];
|
|
237
|
+
|
|
238
|
+
if (fallbacks !== 'none') {
|
|
239
|
+
fallbackLanguageOrder.unshift(...languageHierarchy.reverse());
|
|
240
|
+
|
|
241
|
+
if (fallbacks === 'all' && fallbackLanguageOrder[0] !== devLanguage) {
|
|
242
|
+
fallbackLanguageOrder.unshift(devLanguage);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return fallbackLanguageOrder;
|
|
247
|
+
}
|
|
153
248
|
|
|
154
249
|
function getNamespaceByFilePath(relativePath, {
|
|
155
250
|
translationsDirectorySuffix = defaultTranslationDirSuffix
|
|
@@ -193,17 +288,17 @@ function getTranslationsFromFile(translations, {
|
|
|
193
288
|
|
|
194
289
|
for (const [translationKey, translation] of Object.entries(keys)) {
|
|
195
290
|
if (typeof translation === 'string') {
|
|
196
|
-
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {
|
|
291
|
+
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
|
|
197
292
|
continue;
|
|
198
293
|
}
|
|
199
294
|
|
|
200
295
|
if (!translation) {
|
|
201
|
-
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {
|
|
296
|
+
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
|
|
202
297
|
continue;
|
|
203
298
|
}
|
|
204
299
|
|
|
205
300
|
if (!translation.message || typeof translation.message !== 'string') {
|
|
206
|
-
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {
|
|
301
|
+
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
|
|
207
302
|
continue;
|
|
208
303
|
}
|
|
209
304
|
|
|
@@ -225,54 +320,44 @@ function loadAltLanguageFile({
|
|
|
225
320
|
devLanguage,
|
|
226
321
|
languages
|
|
227
322
|
}) {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
languages
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const fallbackLanguages = [languageName];
|
|
238
|
-
|
|
239
|
-
if (fallbacks !== 'none') {
|
|
240
|
-
fallbackLanguages.unshift(...languageHierarchy);
|
|
241
|
-
|
|
242
|
-
if (fallbacks === 'all' && fallbackLanguages[0] !== devLanguage) {
|
|
243
|
-
fallbackLanguages.unshift(devLanguage);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
trace(`Loading alt language file with precendence: ${fallbackLanguages.slice().reverse().join(' -> ')}`);
|
|
323
|
+
const altLanguageTranslation = {};
|
|
324
|
+
const fallbackLanguageOrder = getFallbackLanguageOrder({
|
|
325
|
+
languages,
|
|
326
|
+
languageName,
|
|
327
|
+
devLanguage,
|
|
328
|
+
fallbacks
|
|
329
|
+
});
|
|
330
|
+
trace(`Loading alt language file with precedence: ${fallbackLanguageOrder.slice().reverse().join(' -> ')}`);
|
|
248
331
|
|
|
249
|
-
for (const
|
|
250
|
-
if (
|
|
332
|
+
for (const fallbackLanguage of fallbackLanguageOrder) {
|
|
333
|
+
if (fallbackLanguage !== devLanguage) {
|
|
251
334
|
try {
|
|
252
|
-
const altFilePath = getAltLanguageFilePath(filePath,
|
|
335
|
+
const altFilePath = getAltLanguageFilePath(filePath, fallbackLanguage);
|
|
253
336
|
delete require.cache[altFilePath];
|
|
254
337
|
|
|
255
338
|
const translationFile = require(altFilePath);
|
|
256
339
|
|
|
257
340
|
const {
|
|
258
|
-
keys
|
|
341
|
+
keys: fallbackLanguageTranslation
|
|
259
342
|
} = getTranslationsFromFile(translationFile, {
|
|
260
343
|
filePath: altFilePath,
|
|
261
344
|
isAltLanguage: true
|
|
262
345
|
});
|
|
263
|
-
Object.assign(
|
|
346
|
+
Object.assign(altLanguageTranslation, mergeWithDevLanguageTranslation({
|
|
347
|
+
translation: fallbackLanguageTranslation,
|
|
348
|
+
devTranslation
|
|
349
|
+
}));
|
|
264
350
|
} catch (e) {
|
|
265
|
-
trace(`Missing alt language file ${getAltLanguageFilePath(filePath,
|
|
351
|
+
trace(`Missing alt language file ${getAltLanguageFilePath(filePath, fallbackLanguage)}
|
|
266
352
|
`);
|
|
267
353
|
}
|
|
268
354
|
} else {
|
|
269
|
-
Object.assign(
|
|
355
|
+
Object.assign(altLanguageTranslation, devTranslation);
|
|
270
356
|
}
|
|
271
357
|
}
|
|
272
358
|
|
|
273
|
-
return
|
|
359
|
+
return altLanguageTranslation;
|
|
274
360
|
}
|
|
275
|
-
|
|
276
361
|
function loadTranslation({
|
|
277
362
|
filePath,
|
|
278
363
|
fallbacks
|
|
@@ -305,6 +390,19 @@ function loadTranslation({
|
|
|
305
390
|
}, userConfig);
|
|
306
391
|
}
|
|
307
392
|
|
|
393
|
+
for (const generatedLanguage of userConfig.generatedLanguages || []) {
|
|
394
|
+
const {
|
|
395
|
+
name: generatedLanguageName,
|
|
396
|
+
generator
|
|
397
|
+
} = generatedLanguage;
|
|
398
|
+
const baseLanguage = generatedLanguage.extends || userConfig.devLanguage;
|
|
399
|
+
const baseTranslations = languageSet[baseLanguage];
|
|
400
|
+
languageSet[generatedLanguageName] = generateLanguageFromTranslations({
|
|
401
|
+
baseTranslations,
|
|
402
|
+
generator
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
308
406
|
return {
|
|
309
407
|
filePath,
|
|
310
408
|
keys: Object.keys(devTranslation),
|
|
@@ -588,7 +686,7 @@ async function writeIfChanged(filepath, contents) {
|
|
|
588
686
|
}
|
|
589
687
|
|
|
590
688
|
/* eslint-disable no-console */
|
|
591
|
-
function findMissingKeys(loadedTranslation, devLanguageName,
|
|
689
|
+
function findMissingKeys(loadedTranslation, devLanguageName, altLanguages) {
|
|
592
690
|
const devLanguage = loadedTranslation.languages[devLanguageName];
|
|
593
691
|
|
|
594
692
|
if (!devLanguage) {
|
|
@@ -600,7 +698,7 @@ function findMissingKeys(loadedTranslation, devLanguageName, altLangauges) {
|
|
|
600
698
|
const requiredKeys = Object.keys(devLanguage);
|
|
601
699
|
|
|
602
700
|
if (requiredKeys.length > 0) {
|
|
603
|
-
for (const altLanguageName of
|
|
701
|
+
for (const altLanguageName of altLanguages) {
|
|
604
702
|
var _loadedTranslation$la;
|
|
605
703
|
|
|
606
704
|
const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {};
|
|
@@ -676,6 +774,35 @@ const schema = {
|
|
|
676
774
|
}
|
|
677
775
|
}
|
|
678
776
|
},
|
|
777
|
+
generatedLanguages: {
|
|
778
|
+
type: 'array',
|
|
779
|
+
items: {
|
|
780
|
+
type: 'object',
|
|
781
|
+
props: {
|
|
782
|
+
name: {
|
|
783
|
+
type: 'string'
|
|
784
|
+
},
|
|
785
|
+
extends: {
|
|
786
|
+
type: 'string',
|
|
787
|
+
optional: true
|
|
788
|
+
},
|
|
789
|
+
generator: {
|
|
790
|
+
type: 'object',
|
|
791
|
+
props: {
|
|
792
|
+
transformElement: {
|
|
793
|
+
type: 'function',
|
|
794
|
+
optional: true
|
|
795
|
+
},
|
|
796
|
+
transformMessage: {
|
|
797
|
+
type: 'function',
|
|
798
|
+
optional: true
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
optional: true
|
|
805
|
+
},
|
|
679
806
|
translationsDirectorySuffix: {
|
|
680
807
|
type: 'string',
|
|
681
808
|
optional: true
|
|
@@ -713,13 +840,12 @@ function validateConfig(c) {
|
|
|
713
840
|
|
|
714
841
|
return v.message;
|
|
715
842
|
}).join(' \n'));
|
|
716
|
-
}
|
|
717
|
-
|
|
843
|
+
}
|
|
718
844
|
|
|
719
|
-
const languageStrings = c.languages.map(v => v.name);
|
|
845
|
+
const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages
|
|
720
846
|
|
|
721
847
|
if (!languageStrings.includes(c.devLanguage)) {
|
|
722
|
-
throw new ValidationError('InvalidDevLanguage', `
|
|
848
|
+
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk.bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
|
|
723
849
|
}
|
|
724
850
|
|
|
725
851
|
const foundLanguages = [];
|
|
@@ -737,6 +863,26 @@ function validateConfig(c) {
|
|
|
737
863
|
}
|
|
738
864
|
}
|
|
739
865
|
|
|
866
|
+
const foundGeneratedLanguages = [];
|
|
867
|
+
|
|
868
|
+
for (const generatedLang of c.generatedLanguages || []) {
|
|
869
|
+
// Generated languages must only exist once
|
|
870
|
+
if (foundGeneratedLanguages.includes(generatedLang.name)) {
|
|
871
|
+
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk.bold.cyan(generatedLang.name)}" was defined multiple times.`);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
foundGeneratedLanguages.push(generatedLang.name); // Generated language names must not conflict with language names
|
|
875
|
+
|
|
876
|
+
if (languageStrings.includes(generatedLang.name)) {
|
|
877
|
+
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
|
+
|
|
880
|
+
|
|
881
|
+
if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) {
|
|
882
|
+
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
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
740
886
|
trace('Configuration file is valid');
|
|
741
887
|
return true;
|
|
742
888
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vocab/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"main": "dist/vocab-core.cjs.js",
|
|
5
5
|
"module": "dist/vocab-core.esm.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package.json": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"module": "./dist/vocab-core.esm.js",
|
|
10
|
+
"default": "./dist/vocab-core.cjs.js"
|
|
11
|
+
},
|
|
12
|
+
"./icu-handler": {
|
|
13
|
+
"module": "./icu-handler/dist/vocab-core-icu-handler.esm.js",
|
|
14
|
+
"default": "./icu-handler/dist/vocab-core-icu-handler.cjs.js"
|
|
15
|
+
},
|
|
16
|
+
"./runtime": {
|
|
17
|
+
"module": "./runtime/dist/vocab-core-runtime.esm.js",
|
|
18
|
+
"default": "./runtime/dist/vocab-core-runtime.cjs.js"
|
|
19
|
+
},
|
|
20
|
+
"./translation-file": {
|
|
21
|
+
"module": "./translation-file/dist/vocab-core-translation-file.esm.js",
|
|
22
|
+
"default": "./translation-file/dist/vocab-core-translation-file.cjs.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
6
25
|
"author": "SEEK",
|
|
7
26
|
"license": "MIT",
|
|
8
27
|
"preconstruct": {
|
|
@@ -13,9 +32,15 @@
|
|
|
13
32
|
"translation-file.ts"
|
|
14
33
|
]
|
|
15
34
|
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"runtime",
|
|
38
|
+
"icu-handler",
|
|
39
|
+
"translation-file"
|
|
40
|
+
],
|
|
16
41
|
"dependencies": {
|
|
17
42
|
"@formatjs/icu-messageformat-parser": "^2.0.10",
|
|
18
|
-
"@vocab/types": "^1.0
|
|
43
|
+
"@vocab/types": "^1.1.0",
|
|
19
44
|
"chalk": "^4.1.0",
|
|
20
45
|
"chokidar": "^3.4.3",
|
|
21
46
|
"debug": "^4.3.1",
|
package/src/ValidationError.ts
DELETED