An extension for vue-i18n that provides namespaced lazy-loading of messages, as opposed to the default behavior of vue-i18n, which always loads all messages for a specific locale.
Large applications that use vue-i18n for internationaliztion often have a large amount of message files for different parts of the application. For better performance, it is desirable to only load a set of messages when they're actually needed, instead of loading all messages for a specific locale all at once.
- Only supports vue-i18n's Composition API; does not support legacy mode
- Currently only supports Vite's
import.meta.glob
for loading message files - Message directory must be organized in a specific way
npm i @modernice/vue-i18n-modules
pnpm i @modernice/vue-i18n-modules
yarn add @modernice/vue-i18n-modules
npm i @modernice/nuxt-i18n-modules
pnpm i @modernice/nuxt-i18n-modules
yarn add @modernice/nuxt-i18n-modules
Add the module to your nuxt.config
:
// nuxt.config.ts
export default defineNuxtModule({
modules: ['@modernice/nuxt-i18n-modules'],
i18nModules: {
dictionary: 'path/to/messages', // defaults to ./dictionary
initial: ['foo', 'bar'] // initial messages are loaded for every page
}
})
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
import { createExtension, createPlugin } from 'vue-i18n-modules'
import { createGlobLoader } from 'vue-i18n-modules/vite'
// First create the vue-i18n instance.
const i18n = createI18n({
// Legacy mode is not supported!
legacy: false,
})
// Setup a loader for message files in the `./messages` directory.
const loader = createGlobLoader(import.meta.glob('./messages/**/*.json'), {
// The prefix allows you to reference/load message modules without
// having to specify the full path to the directory.
prefix: './messages/'
})
// Create the extension from the vue-i18n instance and module loader.
const extension = createExtension({ i18n, loader })
// Create the Vue plugin from the extension.
const plugin = createPlugin(extension)
createApp()
.use(i18n)
.use(plugin)
.mount('#app')
Given that your message modules are in the ./messages
directory, you must
create a directory structure like this:
./messages
./foo
./en.json
./de.json
./fr.json
./foobar
./en.json
./de.json
./fr.json
./bar
./en.json
./de.json
./fr.json
./barbaz
./en.json
./de.json
./fr.json
The above directory structure defines 4 modules: "foo", "bar", "foo.foobar", and "bar.barbaz". Each module provides its messages for the locales that are configured in the vue-i18n instance.
Given that ./messages/foo/foobar/en.json
has the following contents:
{
"page": {
"title": "Hello, Foo!"
}
}
You can lazy-load and translate the title like this:
// Within a Vue setup function
import { useExtension } from 'vue-i18n-modules'
const { loadModule, translate } = useExtension()
// Loads the messages of the "foo.foobar" module.
await loadModule('foo.foobar')
// Translate the title in the currently active locale.
const title = translate('foo.foobar', 'page.title')
The translate
function utilities vue-i18n's translate function internally,
so you can pass vue-i18n's translate options to this function.
You can define types for your message modules by augmenting the DefineModules
interface. When defining at least one module, this extension enforces strictness
in module names passed to the translate
function.
For example, you could create ./messages/foo/foobar/index.ts
to define the
type of the foo.foobar
module:
// ./messages/foo/foobar/index.ts
import type foobar from './en.json'
/**
* Foobar messages.
*/
export type Foobar = typeof foobar
Then augment the DefineModules
interface to enable strict typing:
// in a project-wide *.d.ts file
import { Foobar } from './messages/foo/foobar/index'
declare module '@modernice/vue-i18n-modules' {
interface DefineModules {
'foo.foobar': Foobar
}
}
If at least one module is defined, the translate
function only accepts defined
module names instead of arbitrary strings. The following will raise a TypeScript
error:
// Within a Vue setup function
import { useExtension } from 'vue-i18n-modules'
const { loadModule, translate } = useExtension()
// This will not work
await loadModule('unknown-module-name')
// This won't work either
const title = translate('unknown-module-name', 'some.key')
// This also won't work because the key does not exist in the file
const title = translate('foo.foobar', 'some.key')
Instead of useExtension()
, you can call useMessages()
to get a translate
function for a specific module:
// Within a Vue setup function
import { useMessages } from 'vue-i18n-modules'
const { translate } = useMessages('foo.foobar', {
// Immediately load the module within `onServerPrefetch`
// and/or `onMounted`.
load: true,
})
// Now you can omit the module name
const title = translate('page.title')
A middleware for loading message modules is provided for Nuxt applications:
// Within a Vue setup function
definePageMeta({
middleware: 'i18n:messages',
// Load the "foo" and "foo.bar" message modules in a
// middleware before rendering the page.
translate: ['foo', 'foo.bar'],
})