Skip to content

Commit 0f60d1a

Browse files
committed
Add a virtual module that imports all translations for a file
1 parent 34f669b commit 0f60d1a

File tree

6 files changed

+89
-18
lines changed

6 files changed

+89
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
key = Translations for js file da
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
key = Translations for js file

__tests__/fixtures/importer.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import translations from 'virtual:ftl-for-file'
2+
3+
// eslint-disable-next-line no-console -- this is a test file
4+
console.log(translations)

__tests__/frameworks/vite/external.spec.ts

+31
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,35 @@ describe('Vite external', () => {
7171
expect(code).toContain('external.setup.vue.ftl')
7272
expect(code).toMatchSnapshot()
7373
})
74+
75+
it('virtual:ftl-for-file', async () => {
76+
// Arrange
77+
// Act
78+
const code = await compile({
79+
plugins: [
80+
vue3(),
81+
ExternalFluentPlugin({
82+
baseDir: resolve(baseDir, 'fixtures'),
83+
ftlDir: resolve(baseDir, 'fixtures/ftl'),
84+
locales: ['en', 'da'],
85+
}),
86+
],
87+
}, '/fixtures/importer.js')
88+
89+
// Assert
90+
expect(code).toMatchInlineSnapshot(`
91+
"=== /fixtures/importer.js ===
92+
import translations from '/@id/virtual:ftl-for-file?importer=/fixtures/importer.js'
93+
94+
// eslint-disable-next-line no-console -- this is a test file
95+
console.log(translations)
96+
97+
98+
=== virtual:ftl-for-file?importer=/fixtures/importer.js ===
99+
import en_ftl from '/fixtures/ftl/en/importer.js.ftl?import';
100+
import da_ftl from '/fixtures/ftl/da/importer.js.ftl?import';
101+
export default { 'en': en_ftl, 'da': da_ftl }
102+
"
103+
`)
104+
})
74105
})

src/plugins/external-plugin.ts

+51-18
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ interface Dependency {
6161
export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) => {
6262
const resolvedOptions = {
6363
checkSyntax: true,
64+
virtualModuleName: 'virtual:ftl-for-file',
6465
getFtlPath: undefined as ((locale: string, vuePath: string) => string) | undefined,
6566
...options,
6667
}
@@ -74,9 +75,52 @@ export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) =>
7475
}
7576
}
7677

78+
const getTranslationsForFile = async (id: string) => {
79+
const dependencies: Dependency[] = []
80+
for (const locale of options.locales) {
81+
const ftlPath = normalizePath(resolvedOptions.getFtlPath(locale, id))
82+
const ftlExists = await fileExists(ftlPath)
83+
84+
if (ftlExists) {
85+
dependencies.push({
86+
locale,
87+
ftlPath,
88+
importVariable: `${makeLegalIdentifier(locale)}_ftl`,
89+
})
90+
}
91+
}
92+
93+
return dependencies
94+
}
95+
7796
return {
7897
name: 'unplugin-fluent-vue-external',
7998
enforce: meta.framework === 'webpack' ? 'post' : undefined,
99+
resolveId(id, importer) {
100+
if (id === resolvedOptions.virtualModuleName)
101+
return `${id}?importer=${importer}`
102+
},
103+
async load(id) {
104+
if (!id.startsWith(resolvedOptions.virtualModuleName))
105+
return
106+
107+
const importer = id.split('?importer=')[1]
108+
109+
const translations = await getTranslationsForFile(importer)
110+
111+
for (const { ftlPath } of translations)
112+
this.addWatchFile(ftlPath)
113+
114+
let code = ''
115+
for (const { ftlPath, importVariable } of translations)
116+
code += `import ${importVariable} from '${ftlPath}';\n`
117+
118+
code += `export default { ${translations
119+
.map(({ locale, importVariable }) => `'${locale}': ${importVariable}`)
120+
.join(', ')} }\n`
121+
122+
return code
123+
},
80124
transformInclude(id: string) {
81125
return isVue(id) || isFtl(id)
82126
},
@@ -86,32 +130,21 @@ export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) =>
86130

87131
const { insertPos, target } = getInsertInfo(source)
88132

89-
const dependencies: Dependency[] = []
90-
for (const locale of options.locales) {
91-
const ftlPath = normalizePath(resolvedOptions.getFtlPath(locale, id))
92-
const ftlExists = await fileExists(ftlPath)
93-
94-
if (ftlExists) {
95-
this.addWatchFile(ftlPath)
133+
const translations = await getTranslationsForFile(id)
96134

97-
dependencies.push({
98-
locale,
99-
ftlPath,
100-
importVariable: `${makeLegalIdentifier(locale)}_ftl`,
101-
})
102-
}
103-
}
135+
for (const { ftlPath } of translations)
136+
this.addWatchFile(ftlPath)
104137

105-
for (const dep of dependencies)
138+
for (const dep of translations)
106139
magic.prepend(`import ${dep.importVariable} from '${dep.ftlPath}';\n`)
107140
magic.appendLeft(insertPos, `${target}.fluent = ${target}.fluent || {};\n`)
108-
for (const dep of dependencies)
141+
for (const dep of translations)
109142
magic.appendLeft(insertPos, `${target}.fluent['${dep.locale}'] = ${dep.importVariable}\n`)
110143
magic.appendLeft(insertPos, `
111144
const __HOT_API__ = import.meta.hot || import.meta.webpackHot
112145
if (__HOT_API__) {
113-
__HOT_API__.accept([${dependencies.map(dep => `'${dep.ftlPath}'`).join(', ')}], () => {
114-
${dependencies.map(({ locale, importVariable }) => `${target}.fluent['${locale}'] = ${importVariable}`).join('\n')}
146+
__HOT_API__.accept([${translations.map(dep => `'${dep.ftlPath}'`).join(', ')}], () => {
147+
${translations.map(({ locale, importVariable }) => `${target}.fluent['${locale}'] = ${importVariable}`).join('\n')}
115148
116149
delete ${target}._fluent
117150
if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
interface ExternalPluginOptionsBase {
22
locales: string[]
33
checkSyntax?: boolean
4+
virtualModuleName?: string
45
}
56

67
interface ExternalPluginOptionsFolder extends ExternalPluginOptionsBase {

0 commit comments

Comments
 (0)