From 0f60d1aa2af9ecdea83d26d2776a28c2007e52f6 Mon Sep 17 00:00:00 2001 From: Ivan Demchuk Date: Wed, 5 Oct 2022 16:42:56 +0300 Subject: [PATCH] Add a virtual module that imports all translations for a file --- __tests__/fixtures/ftl/da/importer.js.ftl | 1 + __tests__/fixtures/ftl/en/importer.js.ftl | 1 + __tests__/fixtures/importer.js | 4 ++ __tests__/frameworks/vite/external.spec.ts | 31 ++++++++++ src/plugins/external-plugin.ts | 69 ++++++++++++++++------ src/types.ts | 1 + 6 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 __tests__/fixtures/ftl/da/importer.js.ftl create mode 100644 __tests__/fixtures/ftl/en/importer.js.ftl create mode 100644 __tests__/fixtures/importer.js diff --git a/__tests__/fixtures/ftl/da/importer.js.ftl b/__tests__/fixtures/ftl/da/importer.js.ftl new file mode 100644 index 0000000..1ce88a7 --- /dev/null +++ b/__tests__/fixtures/ftl/da/importer.js.ftl @@ -0,0 +1 @@ +key = Translations for js file da diff --git a/__tests__/fixtures/ftl/en/importer.js.ftl b/__tests__/fixtures/ftl/en/importer.js.ftl new file mode 100644 index 0000000..169376c --- /dev/null +++ b/__tests__/fixtures/ftl/en/importer.js.ftl @@ -0,0 +1 @@ +key = Translations for js file diff --git a/__tests__/fixtures/importer.js b/__tests__/fixtures/importer.js new file mode 100644 index 0000000..9ca16cb --- /dev/null +++ b/__tests__/fixtures/importer.js @@ -0,0 +1,4 @@ +import translations from 'virtual:ftl-for-file' + +// eslint-disable-next-line no-console -- this is a test file +console.log(translations) diff --git a/__tests__/frameworks/vite/external.spec.ts b/__tests__/frameworks/vite/external.spec.ts index c2dd56c..ca529e4 100644 --- a/__tests__/frameworks/vite/external.spec.ts +++ b/__tests__/frameworks/vite/external.spec.ts @@ -71,4 +71,35 @@ describe('Vite external', () => { expect(code).toContain('external.setup.vue.ftl') expect(code).toMatchSnapshot() }) + + it('virtual:ftl-for-file', async () => { + // Arrange + // Act + const code = await compile({ + plugins: [ + vue3(), + ExternalFluentPlugin({ + baseDir: resolve(baseDir, 'fixtures'), + ftlDir: resolve(baseDir, 'fixtures/ftl'), + locales: ['en', 'da'], + }), + ], + }, '/fixtures/importer.js') + + // Assert + expect(code).toMatchInlineSnapshot(` + "=== /fixtures/importer.js === + import translations from '/@id/virtual:ftl-for-file?importer=/fixtures/importer.js' + + // eslint-disable-next-line no-console -- this is a test file + console.log(translations) + + + === virtual:ftl-for-file?importer=/fixtures/importer.js === + import en_ftl from '/fixtures/ftl/en/importer.js.ftl?import'; + import da_ftl from '/fixtures/ftl/da/importer.js.ftl?import'; + export default { 'en': en_ftl, 'da': da_ftl } + " + `) + }) }) diff --git a/src/plugins/external-plugin.ts b/src/plugins/external-plugin.ts index 8ef597d..f27282e 100644 --- a/src/plugins/external-plugin.ts +++ b/src/plugins/external-plugin.ts @@ -61,6 +61,7 @@ interface Dependency { export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) => { const resolvedOptions = { checkSyntax: true, + virtualModuleName: 'virtual:ftl-for-file', getFtlPath: undefined as ((locale: string, vuePath: string) => string) | undefined, ...options, } @@ -74,9 +75,52 @@ export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) => } } + const getTranslationsForFile = async (id: string) => { + const dependencies: Dependency[] = [] + for (const locale of options.locales) { + const ftlPath = normalizePath(resolvedOptions.getFtlPath(locale, id)) + const ftlExists = await fileExists(ftlPath) + + if (ftlExists) { + dependencies.push({ + locale, + ftlPath, + importVariable: `${makeLegalIdentifier(locale)}_ftl`, + }) + } + } + + return dependencies + } + return { name: 'unplugin-fluent-vue-external', enforce: meta.framework === 'webpack' ? 'post' : undefined, + resolveId(id, importer) { + if (id === resolvedOptions.virtualModuleName) + return `${id}?importer=${importer}` + }, + async load(id) { + if (!id.startsWith(resolvedOptions.virtualModuleName)) + return + + const importer = id.split('?importer=')[1] + + const translations = await getTranslationsForFile(importer) + + for (const { ftlPath } of translations) + this.addWatchFile(ftlPath) + + let code = '' + for (const { ftlPath, importVariable } of translations) + code += `import ${importVariable} from '${ftlPath}';\n` + + code += `export default { ${translations + .map(({ locale, importVariable }) => `'${locale}': ${importVariable}`) + .join(', ')} }\n` + + return code + }, transformInclude(id: string) { return isVue(id) || isFtl(id) }, @@ -86,32 +130,21 @@ export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) => const { insertPos, target } = getInsertInfo(source) - const dependencies: Dependency[] = [] - for (const locale of options.locales) { - const ftlPath = normalizePath(resolvedOptions.getFtlPath(locale, id)) - const ftlExists = await fileExists(ftlPath) - - if (ftlExists) { - this.addWatchFile(ftlPath) + const translations = await getTranslationsForFile(id) - dependencies.push({ - locale, - ftlPath, - importVariable: `${makeLegalIdentifier(locale)}_ftl`, - }) - } - } + for (const { ftlPath } of translations) + this.addWatchFile(ftlPath) - for (const dep of dependencies) + for (const dep of translations) magic.prepend(`import ${dep.importVariable} from '${dep.ftlPath}';\n`) magic.appendLeft(insertPos, `${target}.fluent = ${target}.fluent || {};\n`) - for (const dep of dependencies) + for (const dep of translations) magic.appendLeft(insertPos, `${target}.fluent['${dep.locale}'] = ${dep.importVariable}\n`) magic.appendLeft(insertPos, ` const __HOT_API__ = import.meta.hot || import.meta.webpackHot if (__HOT_API__) { - __HOT_API__.accept([${dependencies.map(dep => `'${dep.ftlPath}'`).join(', ')}], () => { - ${dependencies.map(({ locale, importVariable }) => `${target}.fluent['${locale}'] = ${importVariable}`).join('\n')} + __HOT_API__.accept([${translations.map(dep => `'${dep.ftlPath}'`).join(', ')}], () => { + ${translations.map(({ locale, importVariable }) => `${target}.fluent['${locale}'] = ${importVariable}`).join('\n')} delete ${target}._fluent if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') { diff --git a/src/types.ts b/src/types.ts index 83ec1bb..e87391a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ interface ExternalPluginOptionsBase { locales: string[] checkSyntax?: boolean + virtualModuleName?: string } interface ExternalPluginOptionsFolder extends ExternalPluginOptionsBase {