@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.
@@ -1,4 +1,4 @@
1
- import { LoadedTranslation, UserConfig } from '@vocab/types';
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 '@vocab/types';
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 '@vocab/types';
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 '@vocab/types';
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 type { TranslationFile } from '@vocab/types';
6
+ export * from './types';
@@ -1,5 +1,5 @@
1
- import type { TranslationsByKey, UserConfig, LoadedTranslation, LanguageTarget } from '@vocab/types';
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 '@vocab/types';
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 '@vocab/types';
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 '@vocab/types';
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 '@vocab/types';
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
- if (value && typeof value === 'object') {
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 banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`;
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 { ${Array.from(importNames).join(', ')} } from '${moduleName}'`;
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 ? 'string | T | Array<string | T>' : 'string';
530
+ const formatReturn = hasTags && imports.has('FormatXMLElementFn') ? 'ReturnType<FormatXMLElementFn<T>>' : 'string';
531
531
  translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
532
532
  }
533
- translationsType[encodeBackslash(key)] = translationFunctionString;
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
- return `${banner}
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
- import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
540
- ${serializeModuleImports(imports, '@vocab/types')}
541
+ ${serializeTypeImports(imports, '@vocab/core')}
542
+ import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
541
543
 
542
- const translations = createTranslationFile<${languagesUnionAsString}, ${serialiseObjectToType(translationsType)}>({${content}});
544
+ const translations = createTranslationFile<
545
+ ${languagesUnionAsString},
546
+ ${serialiseObjectToType(translationsType)}
547
+ >({
548
+ ${languageEntries}
549
+ });
543
550
 
544
- export default translations;`;
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(`'${encodeWithinSingleQuotes(translatedLanguage[key].message)}'`);
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
- if (value && typeof value === 'object') {
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 banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`;
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 { ${Array.from(importNames).join(', ')} } from '${moduleName}'`;
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 ? 'string | T | Array<string | T>' : 'string';
530
+ const formatReturn = hasTags && imports.has('FormatXMLElementFn') ? 'ReturnType<FormatXMLElementFn<T>>' : 'string';
531
531
  translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
532
532
  }
533
- translationsType[encodeBackslash(key)] = translationFunctionString;
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
- return `${banner}
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
- import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
540
- ${serializeModuleImports(imports, '@vocab/types')}
541
+ ${serializeTypeImports(imports, '@vocab/core')}
542
+ import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
541
543
 
542
- const translations = createTranslationFile<${languagesUnionAsString}, ${serialiseObjectToType(translationsType)}>({${content}});
544
+ const translations = createTranslationFile<
545
+ ${languagesUnionAsString},
546
+ ${serialiseObjectToType(translationsType)}
547
+ >({
548
+ ${languageEntries}
549
+ });
543
550
 
544
- export default translations;`;
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(`'${encodeWithinSingleQuotes(translatedLanguage[key].message)}'`);
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);
@@ -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, isPluralElement, isDateElement, isTimeElement } from '@formatjs/icu-messageformat-parser';
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
- if (value && typeof value === 'object') {
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 banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`;
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 { ${Array.from(importNames).join(', ')} } from '${moduleName}'`;
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 ? 'string | T | Array<string | T>' : 'string';
514
+ const formatReturn = hasTags && imports.has('FormatXMLElementFn') ? 'ReturnType<FormatXMLElementFn<T>>' : 'string';
515
515
  translationFunctionString = `${formatGeneric}(values: ${serialiseObjectToType(params)}) => ${formatReturn}`;
516
516
  }
517
- translationsType[encodeBackslash(key)] = translationFunctionString;
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
- return `${banner}
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
- import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
524
- ${serializeModuleImports(imports, '@vocab/types')}
525
+ ${serializeTypeImports(imports, '@vocab/core')}
526
+ import { createLanguage, createTranslationFile } from '@vocab/core/runtime';
525
527
 
526
- const translations = createTranslationFile<${languagesUnionAsString}, ${serialiseObjectToType(translationsType)}>({${content}});
528
+ const translations = createTranslationFile<
529
+ ${languagesUnionAsString},
530
+ ${serialiseObjectToType(translationsType)}
531
+ >({
532
+ ${languageEntries}
533
+ });
527
534
 
528
- export default translations;`;
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(`'${encodeWithinSingleQuotes(translatedLanguage[key].message)}'`);
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-compiled-translation-import-order-20230328231631",
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",