vite-plugin-translations.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import {
  2. createCompilationErrorMessage,
  3. createCompiledCatalog,
  4. getCatalogForFile,
  5. getCatalogs,
  6. } from '@lingui/cli/api';
  7. import { getConfig } from '@lingui/conf';
  8. import * as fs from 'fs';
  9. import * as path from 'path';
  10. import type { Plugin } from 'vite';
  11. export interface TranslationsPluginOptions {
  12. /**
  13. * Array of paths to .po files to merge with built-in translations
  14. */
  15. externalPoFiles?: string[];
  16. /**
  17. * Path to the built-in locales directory
  18. */
  19. localesDir?: string;
  20. /**
  21. * Output path for merged translations within the build output (e.g., 'i18n')
  22. */
  23. outputPath?: string;
  24. packageRoot: string;
  25. }
  26. type TranslationFile = {
  27. name: string;
  28. path: string;
  29. };
  30. /**
  31. * @description
  32. * This Vite plugin compiles
  33. * @param options
  34. */
  35. export function translationsPlugin(options: TranslationsPluginOptions): Plugin {
  36. const { externalPoFiles = [], localesDir = 'src/i18n/locales', outputPath = 'assets/i18n' } = options;
  37. const linguiConfig = getConfig({ configPath: path.join(options.packageRoot, 'lingui.config.js') });
  38. const catalogsPromise = getCatalogs(linguiConfig);
  39. async function compileTranslations(files: TranslationFile[], emitFile: any) {
  40. const catalogs = await catalogsPromise;
  41. for (const file of files) {
  42. const catalogRelativePath = path.relative(options.packageRoot, file.path);
  43. const fileCatalog = getCatalogForFile(catalogRelativePath, catalogs);
  44. const { locale, catalog } = fileCatalog;
  45. const { messages } = await catalog.getTranslations(locale, {
  46. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  47. fallbackLocales: { default: linguiConfig.sourceLocale! },
  48. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  49. sourceLocale: linguiConfig.sourceLocale!,
  50. });
  51. const { source: code, errors } = createCompiledCatalog(locale, messages, {
  52. namespace: 'es',
  53. pseudoLocale: linguiConfig.pseudoLocale,
  54. });
  55. if (errors.length) {
  56. const message = createCompilationErrorMessage(locale, errors);
  57. throw new Error(
  58. message +
  59. `These errors fail build because \`failOnCompileError=true\` in Lingui Vite plugin configuration.`,
  60. );
  61. }
  62. // Emit the compiled JavaScript file to the build output
  63. const outputFileName = path.posix.join(outputPath, `${locale}.js`);
  64. emitFile({
  65. type: 'asset',
  66. fileName: outputFileName,
  67. source: code,
  68. });
  69. }
  70. }
  71. return {
  72. name: 'vendure:compile-translations',
  73. async generateBundle() {
  74. // This runs during the bundle generation phase - emit files directly to build output
  75. try {
  76. const resolvedLocalesDir = path.resolve(options.packageRoot, localesDir);
  77. // Get all built-in .po files
  78. const builtInFiles = fs
  79. .readdirSync(resolvedLocalesDir)
  80. .filter(file => file.endsWith('.po'))
  81. .map(file => ({
  82. name: file,
  83. path: path.join(resolvedLocalesDir, file),
  84. }));
  85. await compileTranslations(builtInFiles, this.emitFile);
  86. this.info(`✓ Processed ${builtInFiles.length} translation files to ${outputPath}`);
  87. } catch (error) {
  88. this.error(
  89. `Translation plugin error: ${error instanceof Error ? error.message : String(error)}`,
  90. );
  91. }
  92. },
  93. };
  94. }