translations.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import { LanguageCode } from '@vendure/common/lib/generated-types';
  2. import * as fs from 'fs-extra';
  3. import glob from 'glob';
  4. import * as path from 'path';
  5. import { Extension, Translations } from './types';
  6. import { logger } from './utils';
  7. /**
  8. * Given an array of extensions, returns a map of languageCode to all files specified by the
  9. * configured globs.
  10. */
  11. export function getAllTranslationFiles(
  12. extensions: Extension[],
  13. ): { [languageCode in LanguageCode]?: string[] } {
  14. // First collect all globs by language
  15. const allTranslationsWithGlobs: { [languageCode in LanguageCode]?: string[] } = {};
  16. for (const extension of extensions) {
  17. for (const [languageCode, globPattern] of Object.entries(extension.translations || {})) {
  18. const code = languageCode as LanguageCode;
  19. if (globPattern) {
  20. if (!allTranslationsWithGlobs[code]) {
  21. allTranslationsWithGlobs[code] = [globPattern];
  22. } else {
  23. // tslint:disable-next-line:no-non-null-assertion
  24. allTranslationsWithGlobs[code]!.push(globPattern);
  25. }
  26. }
  27. }
  28. }
  29. const allTranslationsWithFiles: { [languageCode in LanguageCode]?: string[] } = {};
  30. for (const [languageCode, globs] of Object.entries(allTranslationsWithGlobs)) {
  31. const code = languageCode as LanguageCode;
  32. allTranslationsWithFiles[code] = [];
  33. if (!globs) {
  34. continue;
  35. }
  36. for (const pattern of globs) {
  37. const files = glob.sync(pattern);
  38. // tslint:disable-next-line:no-non-null-assertion
  39. allTranslationsWithFiles[code]!.push(...files);
  40. }
  41. }
  42. return allTranslationsWithFiles;
  43. }
  44. export async function mergeExtensionTranslations(
  45. outputPath: string,
  46. translationFiles: { [languageCode in LanguageCode]?: string[] },
  47. ) {
  48. // Now merge them into the final language-speicific json files
  49. const i18nMessagesDir = path.join(outputPath, 'src/i18n-messages');
  50. for (const [languageCode, files] of Object.entries(translationFiles)) {
  51. if (!files) {
  52. continue;
  53. }
  54. const translationFile = path.join(i18nMessagesDir, `${languageCode}.json`);
  55. const translationBackupFile = path.join(i18nMessagesDir, `${languageCode}.json.bak`);
  56. if (fs.existsSync(translationBackupFile)) {
  57. // restore the original translations from the backup
  58. await fs.copy(translationBackupFile, translationFile);
  59. }
  60. let translations: any = {};
  61. if (fs.existsSync(translationFile)) {
  62. // create a backup of the original (unextended) translations
  63. await fs.copy(translationFile, translationBackupFile);
  64. try {
  65. translations = await fs.readJson(translationFile);
  66. } catch (e) {
  67. logger.error(`Could not load translation file: ${translationFile}`);
  68. logger.error(e);
  69. }
  70. }
  71. for (const file of files) {
  72. try {
  73. const contents = await fs.readJson(file);
  74. translations = mergeTranslations(translations, contents);
  75. } catch (e) {
  76. logger.error(`Could not load translation file: ${translationFile}`);
  77. logger.error(e);
  78. }
  79. }
  80. // write the final translation files to disk
  81. const sortedTranslations = sortTranslationKeys(translations);
  82. await fs.writeFile(translationFile, JSON.stringify(sortedTranslations, null, 2), 'utf8');
  83. }
  84. }
  85. /**
  86. * Sorts the contents of the translation files so the sections & keys are alphabetical.
  87. */
  88. function sortTranslationKeys(translations: Translations): Translations {
  89. const result: Translations = {};
  90. const sections = Object.keys(translations).sort();
  91. for (const section of sections) {
  92. const sortedTokens = Object.entries(translations[section])
  93. .sort(([keyA], [keyB]) => (keyA < keyB ? -1 : 1))
  94. .reduce((output, [key, val]) => ({ ...output, [key]: val }), {});
  95. result[section] = sortedTokens;
  96. }
  97. return result;
  98. }
  99. /**
  100. * Merges the second set of translations into the first, returning a new translations
  101. * object.
  102. */
  103. function mergeTranslations(t1: Translations, t2: Translations): Translations {
  104. const result = { ...t1 };
  105. for (const [section, translations] of Object.entries(t2)) {
  106. result[section] = {
  107. ...t1[section],
  108. ...translations,
  109. };
  110. }
  111. return result;
  112. }