Skip to content

Add a virtual module that imports all translations for a file #29

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 1 commit into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions __tests__/fixtures/ftl/da/importer.js.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
key = Translations for js file da
1 change: 1 addition & 0 deletions __tests__/fixtures/ftl/en/importer.js.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
key = Translations for js file
4 changes: 4 additions & 0 deletions __tests__/fixtures/importer.js
Original file line number Diff line number Diff line change
@@ -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)
31 changes: 31 additions & 0 deletions __tests__/frameworks/vite/external.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
"
`)
})
})
69 changes: 51 additions & 18 deletions src/plugins/external-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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)
},
Expand All @@ -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') {
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
interface ExternalPluginOptionsBase {
locales: string[]
checkSyntax?: boolean
virtualModuleName?: string
}

interface ExternalPluginOptionsFolder extends ExternalPluginOptionsBase {
Expand Down