@vocab/core 0.0.0-compiled-translation-import-order-20230328231631 → 0.0.0-global-key-support-20231025223328
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/dist/declarations/src/compile.d.ts +1 -1
- package/dist/declarations/src/config.d.ts +1 -1
- package/dist/declarations/src/generate-language.d.ts +1 -1
- package/dist/declarations/src/icu-handler.d.ts +1 -1
- package/dist/declarations/src/index.d.ts +1 -1
- package/dist/declarations/src/load-translations.d.ts +2 -2
- package/dist/declarations/src/runtime.d.ts +1 -1
- package/dist/declarations/src/translation-file.d.ts +1 -1
- package/dist/declarations/src/types.d.ts +117 -0
- package/dist/declarations/src/utils.d.ts +1 -1
- package/dist/declarations/src/validate/index.d.ts +1 -1
- package/dist/vocab-core.cjs.dev.js +30 -23
- package/dist/vocab-core.cjs.prod.js +30 -23
- package/dist/vocab-core.esm.js +31 -24
- package/package.json +1 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LoadedTranslation, UserConfig } from '
|
|
1
|
+
import type { LoadedTranslation, UserConfig } from './types';
|
|
2
2
|
export declare function generateRuntime(loadedTranslation: LoadedTranslation): Promise<void>;
|
|
3
3
|
export declare function watch(config: UserConfig): () => Promise<void>;
|
|
4
4
|
export declare function compile({ watch: shouldWatch }: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UserConfig } from '
|
|
1
|
+
import type { UserConfig } from './types';
|
|
2
2
|
export declare function validateConfig(c: UserConfig): boolean;
|
|
3
3
|
export declare function resolveConfig(customConfigFilePath?: string): Promise<UserConfig | null>;
|
|
4
4
|
export declare function resolveConfigSync(customConfigFilePath?: string): UserConfig | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MessageGenerator, TranslationsByKey } from '
|
|
1
|
+
import type { MessageGenerator, TranslationsByKey } from './types';
|
|
2
2
|
export declare function generateLanguageFromTranslations({ baseTranslations, generator, }: {
|
|
3
3
|
baseTranslations: TranslationsByKey<string>;
|
|
4
4
|
generator: MessageGenerator;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { ParsedICUMessages, TranslationMessagesByKey } from '
|
|
1
|
+
import type { ParsedICUMessages, TranslationMessagesByKey } from './types';
|
|
2
2
|
export declare const getParsedICUMessages: (m: TranslationMessagesByKey, locale: string) => ParsedICUMessages<any>;
|
|
@@ -3,4 +3,4 @@ export { validate } from './validate';
|
|
|
3
3
|
export { resolveConfig, resolveConfigSync, validateConfig } from './config';
|
|
4
4
|
export { getAltLanguages, getAltLanguageFilePath, getDevLanguageFileFromTsFile, } from './utils';
|
|
5
5
|
export { getUniqueKey, loadAllTranslations, loadTranslation, } from './load-translations';
|
|
6
|
-
export
|
|
6
|
+
export * from './types';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TranslationsByKey, UserConfig, LoadedTranslation, LanguageTarget } from '
|
|
2
|
-
import { Fallback } from './utils';
|
|
1
|
+
import type { TranslationsByKey, UserConfig, LoadedTranslation, LanguageTarget } from './types';
|
|
2
|
+
import { type Fallback } from './utils';
|
|
3
3
|
export declare function getUniqueKey(key: string, namespace: string): string;
|
|
4
4
|
export declare function mergeWithDevLanguageTranslation({ translation, devTranslation, }: {
|
|
5
5
|
translation: TranslationsByKey;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { TranslationModule, TranslationMessagesByKey } from '
|
|
1
|
+
import type { TranslationModule, TranslationMessagesByKey } from './types';
|
|
2
2
|
export { createTranslationFile } from './translation-file';
|
|
3
3
|
export declare const createLanguage: (module: TranslationMessagesByKey) => TranslationModule<any>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { TranslationModuleByLanguage, LanguageName, ParsedFormatFnByKey, TranslationFile } from '
|
|
1
|
+
import type { TranslationModuleByLanguage, LanguageName, ParsedFormatFnByKey, TranslationFile } from './types';
|
|
2
2
|
export declare function createTranslationFile<Language extends LanguageName, FormatFnByKey extends ParsedFormatFnByKey>(translationsByLanguage: TranslationModuleByLanguage<Language, FormatFnByKey>): TranslationFile<Language, FormatFnByKey>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export type { FormatXMLElementFn } from 'intl-messageformat';
|
|
2
|
+
export type LanguageName = string;
|
|
3
|
+
export type TranslationKey = string;
|
|
4
|
+
export type TranslationMessage = string;
|
|
5
|
+
export type ParsedFormatFn = (parts: any) => any;
|
|
6
|
+
export type ParsedFormatFnByKey = Record<string, ParsedFormatFn>;
|
|
7
|
+
/**
|
|
8
|
+
* Equivalent to the `string` type, but tricks the language server into prodiving
|
|
9
|
+
* suggestions for string literals passed into the `Suggestions` generic parameter
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* Accept any string, but suggest specific animals
|
|
13
|
+
* ```
|
|
14
|
+
* type AnyAnimal = StringWithSuggestions<"cat" | "dog">;
|
|
15
|
+
* // Suggests cat and dog, but accepts any string
|
|
16
|
+
* const animal: AnyAnimal = "";
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export type StringWithSuggestions<Suggestions extends string> = Suggestions | Omit<string, Suggestions>;
|
|
20
|
+
/**
|
|
21
|
+
* ParsedICUMessage A strictly typed formatter from intl-messageformat
|
|
22
|
+
*/
|
|
23
|
+
interface ParsedICUMessage<FormatFn extends ParsedFormatFn> {
|
|
24
|
+
format: FormatFn;
|
|
25
|
+
}
|
|
26
|
+
export type ParsedICUMessages<FormatFnByKey extends ParsedFormatFnByKey> = {
|
|
27
|
+
[key in keyof FormatFnByKey]: ParsedICUMessage<FormatFnByKey[key]>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* TranslationModule is a wrapper around a potentially asynchronously loaded set of ParsedICUMessages
|
|
31
|
+
*/
|
|
32
|
+
export type TranslationModule<FormatFnByKey extends ParsedFormatFnByKey> = {
|
|
33
|
+
getValue: (locale: string) => ParsedICUMessages<FormatFnByKey> | undefined;
|
|
34
|
+
load: () => Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
export type TranslationModuleByLanguage<Language extends LanguageName, FormatFnByKey extends ParsedFormatFnByKey> = Record<Language, TranslationModule<FormatFnByKey>>;
|
|
37
|
+
/**
|
|
38
|
+
* TranslationFile contains a record of TranslationModules per language, exposing a set of methods to load and return the module by language
|
|
39
|
+
*/
|
|
40
|
+
export type TranslationFile<Language extends LanguageName, FormatFnByKey extends ParsedFormatFnByKey> = {
|
|
41
|
+
/**
|
|
42
|
+
* Retrieve messages. If not loaded, will attempt to load messages and resolve once complete.
|
|
43
|
+
*/
|
|
44
|
+
getMessages: (language: Language, locale?: string) => Promise<ParsedICUMessages<FormatFnByKey>>;
|
|
45
|
+
/**
|
|
46
|
+
* Retrieve already loaded messages. Will return null if no messages have been loaded.
|
|
47
|
+
*/
|
|
48
|
+
getLoadedMessages: (language: Language, locale?: string) => ParsedICUMessages<FormatFnByKey> | null;
|
|
49
|
+
/**
|
|
50
|
+
* Load messages for the given language. Resolving once complete.
|
|
51
|
+
*/
|
|
52
|
+
load: (language: Language) => Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
export type TranslationKeys<Translations extends TranslationFile<any, ParsedFormatFnByKey>> = keyof Awaited<ReturnType<Translations['getMessages']>>;
|
|
55
|
+
export interface LanguageTarget {
|
|
56
|
+
name: LanguageName;
|
|
57
|
+
extends?: LanguageName;
|
|
58
|
+
}
|
|
59
|
+
export interface MessageGenerator {
|
|
60
|
+
transformElement?: (element: string) => string;
|
|
61
|
+
transformMessage?: (message: string) => string;
|
|
62
|
+
}
|
|
63
|
+
export interface GeneratedLanguageTarget {
|
|
64
|
+
name: LanguageName;
|
|
65
|
+
extends?: LanguageName;
|
|
66
|
+
generator: MessageGenerator;
|
|
67
|
+
}
|
|
68
|
+
export interface UserConfig {
|
|
69
|
+
/**
|
|
70
|
+
* The root directory to compile and validate translations
|
|
71
|
+
*/
|
|
72
|
+
projectRoot?: string;
|
|
73
|
+
/**
|
|
74
|
+
* The language used in translations.json
|
|
75
|
+
*/
|
|
76
|
+
devLanguage: LanguageName;
|
|
77
|
+
/**
|
|
78
|
+
* An array of languages to build for
|
|
79
|
+
*/
|
|
80
|
+
languages: Array<LanguageTarget>;
|
|
81
|
+
/**
|
|
82
|
+
* An array of languages to generate from existing translations
|
|
83
|
+
*/
|
|
84
|
+
generatedLanguages?: Array<GeneratedLanguageTarget>;
|
|
85
|
+
/**
|
|
86
|
+
* A custom suffix to name vocab translation directories
|
|
87
|
+
*/
|
|
88
|
+
translationsDirectorySuffix?: string;
|
|
89
|
+
/**
|
|
90
|
+
* An array of glob paths to ignore from compilation and validation
|
|
91
|
+
*/
|
|
92
|
+
ignore?: Array<string>;
|
|
93
|
+
}
|
|
94
|
+
export type Tags = Array<string>;
|
|
95
|
+
export interface TranslationFileMetadata {
|
|
96
|
+
tags?: Tags;
|
|
97
|
+
}
|
|
98
|
+
export interface TranslationData {
|
|
99
|
+
message: TranslationMessage;
|
|
100
|
+
description?: string;
|
|
101
|
+
tags?: Tags;
|
|
102
|
+
globalKey?: string;
|
|
103
|
+
}
|
|
104
|
+
export type TranslationsByKey<Key extends TranslationKey = string> = Record<Key, TranslationData>;
|
|
105
|
+
export type TranslationFileContents = TranslationsByKey & {
|
|
106
|
+
_meta?: TranslationFileMetadata;
|
|
107
|
+
};
|
|
108
|
+
export type TranslationMessagesByKey<Key extends TranslationKey = string> = Record<Key, TranslationMessage>;
|
|
109
|
+
export type TranslationsByLanguage<Key extends TranslationKey = string> = Record<LanguageName, TranslationsByKey<Key>>;
|
|
110
|
+
export type LoadedTranslation<Key extends TranslationKey = string> = {
|
|
111
|
+
namespace: string;
|
|
112
|
+
keys: Array<Key>;
|
|
113
|
+
filePath: string;
|
|
114
|
+
relativePath: string;
|
|
115
|
+
languages: TranslationsByLanguage<Key>;
|
|
116
|
+
metadata: TranslationFileMetadata;
|
|
117
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LanguageName, LanguageTarget, TranslationsByKey, TranslationMessagesByKey, UserConfig } from '
|
|
1
|
+
import type { LanguageName, LanguageTarget, TranslationsByKey, TranslationMessagesByKey, UserConfig } from './types';
|
|
2
2
|
export declare const defaultTranslationDirSuffix = ".vocab";
|
|
3
3
|
export declare const devTranslationFileName = "translations.json";
|
|
4
4
|
export type Fallback = 'none' | 'valid' | 'all';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { UserConfig, LoadedTranslation, LanguageName } from '
|
|
1
|
+
import type { UserConfig, LoadedTranslation, LanguageName } from '../types';
|
|
2
2
|
export declare function findMissingKeys(loadedTranslation: LoadedTranslation, devLanguageName: LanguageName, altLanguages: Array<LanguageName>): readonly [boolean, Record<string, string[]>];
|
|
3
3
|
export declare function validate(config: UserConfig): Promise<boolean>;
|
|
@@ -433,16 +433,21 @@ async function loadAllTranslations({
|
|
|
433
433
|
throw new Error(`Duplicate keys found. Key with namespace ${loadedTranslation.namespace} and key ${key} was found multiple times.`);
|
|
434
434
|
}
|
|
435
435
|
keys.add(uniqueKey);
|
|
436
|
+
const globalKey = loadedTranslation.languages[config.devLanguage][key].globalKey;
|
|
437
|
+
if (globalKey) {
|
|
438
|
+
if (keys.has(globalKey)) {
|
|
439
|
+
throw new Error(`Duplicate keys found. Key with global key ${globalKey} and key ${key} was found multiple times`);
|
|
440
|
+
}
|
|
441
|
+
keys.add(globalKey);
|
|
442
|
+
}
|
|
436
443
|
}
|
|
437
444
|
}
|
|
438
445
|
return result;
|
|
439
446
|
}
|
|
440
447
|
|
|
441
|
-
const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'");
|
|
442
|
-
const encodeBackslash = v => v.replace(/\\/g, '\\\\');
|
|
443
448
|
function extractHasTags(ast) {
|
|
444
449
|
return ast.some(element => {
|
|
445
|
-
if (icuMessageformatParser.isSelectElement(element)) {
|
|
450
|
+
if (icuMessageformatParser.isSelectElement(element) || icuMessageformatParser.isPluralElement(element)) {
|
|
446
451
|
const children = Object.values(element.options).map(o => o.value);
|
|
447
452
|
return children.some(child => extractHasTags(child));
|
|
448
453
|
}
|
|
@@ -500,21 +505,16 @@ function extractParamTypes(ast, currentParams) {
|
|
|
500
505
|
function serialiseObjectToType(v) {
|
|
501
506
|
let result = '';
|
|
502
507
|
for (const [key, value] of Object.entries(v)) {
|
|
503
|
-
|
|
504
|
-
result += `'${encodeWithinSingleQuotes(key)}': ${serialiseObjectToType(value)},`;
|
|
505
|
-
} else {
|
|
506
|
-
result += `'${encodeWithinSingleQuotes(key)}': ${value},`;
|
|
507
|
-
}
|
|
508
|
+
result += `${JSON.stringify(key)}: ${value && typeof value === 'object' ? serialiseObjectToType(value) : value},`;
|
|
508
509
|
}
|
|
509
510
|
return `{ ${result} }`;
|
|
510
511
|
}
|
|
511
|
-
const
|
|
512
|
-
const serializeModuleImports = (imports, moduleName) => {
|
|
512
|
+
const serializeTypeImports = (imports, moduleName) => {
|
|
513
513
|
if (imports.size === 0) {
|
|
514
514
|
return '';
|
|
515
515
|
}
|
|
516
516
|
const importNames = Array.from(imports);
|
|
517
|
-
return `import { ${
|
|
517
|
+
return `import type { ${importNames.join(', ')} } from '${moduleName}';`;
|
|
518
518
|
};
|
|
519
519
|
function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
520
520
|
trace('Serialising translations:', loadedTranslation);
|
|
@@ -527,21 +527,30 @@ function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
|
527
527
|
let translationFunctionString = `() => ${message}`;
|
|
528
528
|
if (Object.keys(params).length > 0) {
|
|
529
529
|
const formatGeneric = hasTags ? '<T = string>' : '';
|
|
530
|
-
const formatReturn = hasTags
|
|
530
|
+
const formatReturn = hasTags && imports.has('FormatXMLElementFn') ? 'ReturnType<FormatXMLElementFn<T>>' : 'string';
|
|
531
531
|
translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
|
|
532
532
|
}
|
|
533
|
-
translationsType[
|
|
533
|
+
translationsType[key] = translationFunctionString;
|
|
534
534
|
}
|
|
535
|
-
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
536
535
|
const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | ');
|
|
537
|
-
|
|
536
|
+
const languageEntries = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `${JSON.stringify(languageName)}: createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
537
|
+
return (/* ts */`
|
|
538
|
+
// This file is automatically generated by Vocab.
|
|
539
|
+
// To make changes update translation.json files directly.
|
|
538
540
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
+
${serializeTypeImports(imports, '@vocab/core')}
|
|
542
|
+
import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
|
|
541
543
|
|
|
542
|
-
|
|
544
|
+
const translations = createTranslationFile<
|
|
545
|
+
${languagesUnionAsString},
|
|
546
|
+
${serialiseObjectToType(translationsType)}
|
|
547
|
+
>({
|
|
548
|
+
${languageEntries}
|
|
549
|
+
});
|
|
543
550
|
|
|
544
|
-
|
|
551
|
+
export default translations;
|
|
552
|
+
`
|
|
553
|
+
);
|
|
545
554
|
}
|
|
546
555
|
async function generateRuntime(loadedTranslation) {
|
|
547
556
|
const {
|
|
@@ -562,15 +571,13 @@ async function generateRuntime(loadedTranslation) {
|
|
|
562
571
|
const [parsedParams, vocabTypesImports] = extractParamTypes(ast, params);
|
|
563
572
|
imports = new Set([...imports, ...vocabTypesImports]);
|
|
564
573
|
params = parsedParams;
|
|
565
|
-
messages.add(
|
|
574
|
+
messages.add(JSON.stringify(translatedLanguage[key].message));
|
|
566
575
|
}
|
|
567
576
|
}
|
|
568
|
-
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string';
|
|
569
577
|
translationTypes.set(key, {
|
|
570
578
|
params,
|
|
571
579
|
hasTags,
|
|
572
|
-
message: Array.from(messages).join(' | ')
|
|
573
|
-
returnType
|
|
580
|
+
message: Array.from(messages).join(' | ')
|
|
574
581
|
});
|
|
575
582
|
}
|
|
576
583
|
const prettierConfig = await prettier__default["default"].resolveConfig(filePath);
|
|
@@ -433,16 +433,21 @@ async function loadAllTranslations({
|
|
|
433
433
|
throw new Error(`Duplicate keys found. Key with namespace ${loadedTranslation.namespace} and key ${key} was found multiple times.`);
|
|
434
434
|
}
|
|
435
435
|
keys.add(uniqueKey);
|
|
436
|
+
const globalKey = loadedTranslation.languages[config.devLanguage][key].globalKey;
|
|
437
|
+
if (globalKey) {
|
|
438
|
+
if (keys.has(globalKey)) {
|
|
439
|
+
throw new Error(`Duplicate keys found. Key with global key ${globalKey} and key ${key} was found multiple times`);
|
|
440
|
+
}
|
|
441
|
+
keys.add(globalKey);
|
|
442
|
+
}
|
|
436
443
|
}
|
|
437
444
|
}
|
|
438
445
|
return result;
|
|
439
446
|
}
|
|
440
447
|
|
|
441
|
-
const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'");
|
|
442
|
-
const encodeBackslash = v => v.replace(/\\/g, '\\\\');
|
|
443
448
|
function extractHasTags(ast) {
|
|
444
449
|
return ast.some(element => {
|
|
445
|
-
if (icuMessageformatParser.isSelectElement(element)) {
|
|
450
|
+
if (icuMessageformatParser.isSelectElement(element) || icuMessageformatParser.isPluralElement(element)) {
|
|
446
451
|
const children = Object.values(element.options).map(o => o.value);
|
|
447
452
|
return children.some(child => extractHasTags(child));
|
|
448
453
|
}
|
|
@@ -500,21 +505,16 @@ function extractParamTypes(ast, currentParams) {
|
|
|
500
505
|
function serialiseObjectToType(v) {
|
|
501
506
|
let result = '';
|
|
502
507
|
for (const [key, value] of Object.entries(v)) {
|
|
503
|
-
|
|
504
|
-
result += `'${encodeWithinSingleQuotes(key)}': ${serialiseObjectToType(value)},`;
|
|
505
|
-
} else {
|
|
506
|
-
result += `'${encodeWithinSingleQuotes(key)}': ${value},`;
|
|
507
|
-
}
|
|
508
|
+
result += `${JSON.stringify(key)}: ${value && typeof value === 'object' ? serialiseObjectToType(value) : value},`;
|
|
508
509
|
}
|
|
509
510
|
return `{ ${result} }`;
|
|
510
511
|
}
|
|
511
|
-
const
|
|
512
|
-
const serializeModuleImports = (imports, moduleName) => {
|
|
512
|
+
const serializeTypeImports = (imports, moduleName) => {
|
|
513
513
|
if (imports.size === 0) {
|
|
514
514
|
return '';
|
|
515
515
|
}
|
|
516
516
|
const importNames = Array.from(imports);
|
|
517
|
-
return `import { ${
|
|
517
|
+
return `import type { ${importNames.join(', ')} } from '${moduleName}';`;
|
|
518
518
|
};
|
|
519
519
|
function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
520
520
|
trace('Serialising translations:', loadedTranslation);
|
|
@@ -527,21 +527,30 @@ function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
|
527
527
|
let translationFunctionString = `() => ${message}`;
|
|
528
528
|
if (Object.keys(params).length > 0) {
|
|
529
529
|
const formatGeneric = hasTags ? '<T = string>' : '';
|
|
530
|
-
const formatReturn = hasTags
|
|
530
|
+
const formatReturn = hasTags && imports.has('FormatXMLElementFn') ? 'ReturnType<FormatXMLElementFn<T>>' : 'string';
|
|
531
531
|
translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
|
|
532
532
|
}
|
|
533
|
-
translationsType[
|
|
533
|
+
translationsType[key] = translationFunctionString;
|
|
534
534
|
}
|
|
535
|
-
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
536
535
|
const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | ');
|
|
537
|
-
|
|
536
|
+
const languageEntries = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `${JSON.stringify(languageName)}: createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
537
|
+
return (/* ts */`
|
|
538
|
+
// This file is automatically generated by Vocab.
|
|
539
|
+
// To make changes update translation.json files directly.
|
|
538
540
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
+
${serializeTypeImports(imports, '@vocab/core')}
|
|
542
|
+
import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
|
|
541
543
|
|
|
542
|
-
|
|
544
|
+
const translations = createTranslationFile<
|
|
545
|
+
${languagesUnionAsString},
|
|
546
|
+
${serialiseObjectToType(translationsType)}
|
|
547
|
+
>({
|
|
548
|
+
${languageEntries}
|
|
549
|
+
});
|
|
543
550
|
|
|
544
|
-
|
|
551
|
+
export default translations;
|
|
552
|
+
`
|
|
553
|
+
);
|
|
545
554
|
}
|
|
546
555
|
async function generateRuntime(loadedTranslation) {
|
|
547
556
|
const {
|
|
@@ -562,15 +571,13 @@ async function generateRuntime(loadedTranslation) {
|
|
|
562
571
|
const [parsedParams, vocabTypesImports] = extractParamTypes(ast, params);
|
|
563
572
|
imports = new Set([...imports, ...vocabTypesImports]);
|
|
564
573
|
params = parsedParams;
|
|
565
|
-
messages.add(
|
|
574
|
+
messages.add(JSON.stringify(translatedLanguage[key].message));
|
|
566
575
|
}
|
|
567
576
|
}
|
|
568
|
-
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string';
|
|
569
577
|
translationTypes.set(key, {
|
|
570
578
|
params,
|
|
571
579
|
hasTags,
|
|
572
|
-
message: Array.from(messages).join(' | ')
|
|
573
|
-
returnType
|
|
580
|
+
message: Array.from(messages).join(' | ')
|
|
574
581
|
});
|
|
575
582
|
}
|
|
576
583
|
const prettierConfig = await prettier__default["default"].resolveConfig(filePath);
|
package/dist/vocab-core.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, promises } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { TYPE, parse, isSelectElement, isTagElement, isArgumentElement, isNumberElement,
|
|
3
|
+
import { TYPE, parse, isSelectElement, isPluralElement, isTagElement, isArgumentElement, isNumberElement, 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';
|
|
@@ -417,16 +417,21 @@ async function loadAllTranslations({
|
|
|
417
417
|
throw new Error(`Duplicate keys found. Key with namespace ${loadedTranslation.namespace} and key ${key} was found multiple times.`);
|
|
418
418
|
}
|
|
419
419
|
keys.add(uniqueKey);
|
|
420
|
+
const globalKey = loadedTranslation.languages[config.devLanguage][key].globalKey;
|
|
421
|
+
if (globalKey) {
|
|
422
|
+
if (keys.has(globalKey)) {
|
|
423
|
+
throw new Error(`Duplicate keys found. Key with global key ${globalKey} and key ${key} was found multiple times`);
|
|
424
|
+
}
|
|
425
|
+
keys.add(globalKey);
|
|
426
|
+
}
|
|
420
427
|
}
|
|
421
428
|
}
|
|
422
429
|
return result;
|
|
423
430
|
}
|
|
424
431
|
|
|
425
|
-
const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'");
|
|
426
|
-
const encodeBackslash = v => v.replace(/\\/g, '\\\\');
|
|
427
432
|
function extractHasTags(ast) {
|
|
428
433
|
return ast.some(element => {
|
|
429
|
-
if (isSelectElement(element)) {
|
|
434
|
+
if (isSelectElement(element) || isPluralElement(element)) {
|
|
430
435
|
const children = Object.values(element.options).map(o => o.value);
|
|
431
436
|
return children.some(child => extractHasTags(child));
|
|
432
437
|
}
|
|
@@ -484,21 +489,16 @@ function extractParamTypes(ast, currentParams) {
|
|
|
484
489
|
function serialiseObjectToType(v) {
|
|
485
490
|
let result = '';
|
|
486
491
|
for (const [key, value] of Object.entries(v)) {
|
|
487
|
-
|
|
488
|
-
result += `'${encodeWithinSingleQuotes(key)}': ${serialiseObjectToType(value)},`;
|
|
489
|
-
} else {
|
|
490
|
-
result += `'${encodeWithinSingleQuotes(key)}': ${value},`;
|
|
491
|
-
}
|
|
492
|
+
result += `${JSON.stringify(key)}: ${value && typeof value === 'object' ? serialiseObjectToType(value) : value},`;
|
|
492
493
|
}
|
|
493
494
|
return `{ ${result} }`;
|
|
494
495
|
}
|
|
495
|
-
const
|
|
496
|
-
const serializeModuleImports = (imports, moduleName) => {
|
|
496
|
+
const serializeTypeImports = (imports, moduleName) => {
|
|
497
497
|
if (imports.size === 0) {
|
|
498
498
|
return '';
|
|
499
499
|
}
|
|
500
500
|
const importNames = Array.from(imports);
|
|
501
|
-
return `import { ${
|
|
501
|
+
return `import type { ${importNames.join(', ')} } from '${moduleName}';`;
|
|
502
502
|
};
|
|
503
503
|
function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
504
504
|
trace('Serialising translations:', loadedTranslation);
|
|
@@ -511,21 +511,30 @@ function serialiseTranslationRuntime(value, imports, loadedTranslation) {
|
|
|
511
511
|
let translationFunctionString = `() => ${message}`;
|
|
512
512
|
if (Object.keys(params).length > 0) {
|
|
513
513
|
const formatGeneric = hasTags ? '<T = string>' : '';
|
|
514
|
-
const formatReturn = hasTags
|
|
514
|
+
const formatReturn = hasTags && imports.has('FormatXMLElementFn') ? 'ReturnType<FormatXMLElementFn<T>>' : 'string';
|
|
515
515
|
translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
|
|
516
516
|
}
|
|
517
|
-
translationsType[
|
|
517
|
+
translationsType[key] = translationFunctionString;
|
|
518
518
|
}
|
|
519
|
-
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
520
519
|
const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | ');
|
|
521
|
-
|
|
520
|
+
const languageEntries = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `${JSON.stringify(languageName)}: createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(',');
|
|
521
|
+
return (/* ts */`
|
|
522
|
+
// This file is automatically generated by Vocab.
|
|
523
|
+
// To make changes update translation.json files directly.
|
|
522
524
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
+
${serializeTypeImports(imports, '@vocab/core')}
|
|
526
|
+
import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
|
|
525
527
|
|
|
526
|
-
|
|
528
|
+
const translations = createTranslationFile<
|
|
529
|
+
${languagesUnionAsString},
|
|
530
|
+
${serialiseObjectToType(translationsType)}
|
|
531
|
+
>({
|
|
532
|
+
${languageEntries}
|
|
533
|
+
});
|
|
527
534
|
|
|
528
|
-
|
|
535
|
+
export default translations;
|
|
536
|
+
`
|
|
537
|
+
);
|
|
529
538
|
}
|
|
530
539
|
async function generateRuntime(loadedTranslation) {
|
|
531
540
|
const {
|
|
@@ -546,15 +555,13 @@ async function generateRuntime(loadedTranslation) {
|
|
|
546
555
|
const [parsedParams, vocabTypesImports] = extractParamTypes(ast, params);
|
|
547
556
|
imports = new Set([...imports, ...vocabTypesImports]);
|
|
548
557
|
params = parsedParams;
|
|
549
|
-
messages.add(
|
|
558
|
+
messages.add(JSON.stringify(translatedLanguage[key].message));
|
|
550
559
|
}
|
|
551
560
|
}
|
|
552
|
-
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string';
|
|
553
561
|
translationTypes.set(key, {
|
|
554
562
|
params,
|
|
555
563
|
hasTags,
|
|
556
|
-
message: Array.from(messages).join(' | ')
|
|
557
|
-
returnType
|
|
564
|
+
message: Array.from(messages).join(' | ')
|
|
558
565
|
});
|
|
559
566
|
}
|
|
560
567
|
const prettierConfig = await prettier.resolveConfig(filePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vocab/core",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-global-key-support-20231025223328",
|
|
4
4
|
"main": "dist/vocab-core.cjs.js",
|
|
5
5
|
"module": "dist/vocab-core.esm.js",
|
|
6
6
|
"exports": {
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@formatjs/icu-messageformat-parser": "^2.0.10",
|
|
43
|
-
"@vocab/types": "^1.2.0",
|
|
44
43
|
"chalk": "^4.1.0",
|
|
45
44
|
"chokidar": "^3.4.3",
|
|
46
45
|
"debug": "^4.3.1",
|