This package implements simplified mechanism for internationalization (i18n) of components in Vue.js.
If you want to see all the bits and pieces in action check out the example
There are 2 parts to making this system work:
- Vite plugin
- Vue.js plugin or context creation (for webcomponents)
To install the Vite plugin first install the package:
npm install --save-dev @padcom/vite-plugin-vue-i18n
and then add it to your vite.config.js
:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import i18n from '@padcom/vite-plugin-vue-i18n'
export default defineConfig({
plugins: [
vue(),
i18n(),
]
})
Nothing really fancy, but needs to be done so that Vite understands how to process the <i18n>
blocks in .vue
files.
First things first, you need to install the part that provides the actual i18n capabilities:
npm install --save-dev @padcom/vue-i18n
In contrast to the original vue-i18n
plugin this one does not provide a global function that can inject messages from the global scope. Instead, everything is done using composition functions and Vue's native provide
/inject
.
For applications the easiest way to do it is to install the provided Vue.js plugin when creating the application:
import { createApp } from 'vue'
import { createI18n } from '@padcom/vue-i18n'
import App from './App.vue'
const messages = {
en: {
hello: 'Hello, world!',
},
}
createI18n({ messages })
createApp(App)
.mount('#app')
In the case of for example webcomponents, where the application instance is just not available there is no way to use a plugin approach like that. But no worries - this package has you covered! Instead of using a Vue.js plugin you can create the necessary context yourself:
Host.ce.vue
- a file acting as the webcomponent host for your application:
<template>
<SomeComponentThatWantsToUseTranslations />
</template>
<script lang="ts" setup>
import { createI18Context } from '@padcom/vue-i18n'
import SomeComponentThatWantsToUseTranslations from '...'
const messages = {
en: {
hello: 'Hello, world!',
},
}
createI18Context({ messages })
</script>
You can get the messages
from anywhere - here we just provide them in the same file to make it easier for presentation but in real life you might want to extract them to a folder structure like the original vue-i18n plugin suggests:
src
+ i18n
+ index.ts
+ locale
+ en.ts
+ de.ts
...
where src/i18n/index.ts
contains re-exports of all the different messages for languages:
export { default as en } from './locale/en'
export { default as de } from './locale/de'
and the individual message files just export the messages as follows:
export default {
hello: 'Hello, world!',
}
With that kind of structure, this is how you would import the messages when you want to import your messages in ./src/i18n.js
or when you create the injection context manually:
import * as messages from './locale'
export const i18n = createI18n({
messages,
})
and then in the main.ts
of your application you simply import the module:
import './i18n'
Of course this is the most extensive version of the configuration. You can do it all in main.ts
if your application is small enough:
import { createApp } from 'vue'
import { createI18n } from '@padcom/vue-i18n'
createI18n({
messages: {
en: {
hello: 'Hello, world!',
},
},
})
createApp(App).mount('#app')
Once you've installed and configured everything using this system is very simple:
<template>
<h1>{{ t('message') }}</h1>
</template>
<script lang="ts" setup>
import { useI18n } from '@padcom/vue-i18n'
const { t } = useI18n()
</script>
<i18n>
{
"en": {
"message": "Hello, world!",
},
"de": {
"message": "Hallo Welt!",
}
}
</i18n>
Sometimes you might have duplicated message ids. In general you should try to avoid it as it makes reasoning about the code more difficult. Nevertheless, real world sometimes makes us do things we will regret later on and this system is not going to stand in your way.
You can enforce the t
to use global messages by specifying the useScope
key when calling useI18n()
:
const { t } = useI18n({ useScope: 'global' })
Sometimes you might want your component to only use local translations. This system has you covered:
const { t } = useI18n({ useScope: 'local' })
The default value for useScope
is 'local-first'
, which means you'll get the the local value if defined or else the system will default to global scope.
The system implements a simplistic, but powerful resolution system to get the message.
There are 2 locales that you can choose from:
locale
fallbackLocale
Both are initialized to the country code of the client's browser (e.g. 'en'
). But you can change them however you'd like either when installing the application plugin:
createApp(App)
.use(i18n, {
messages,
locale: 'xy',
fallbackLocale: 'au'
})
.mount('#app')
or when creating the context:
const { locale, fallbackLocale } = createI18Context({
messages,
locale: 'xy',
fallbackLocale: 'au',
})
The message resolution is as follows:
- Try to resolve the given key using
locale
from<i18n>
provided keys - Try to resolve the given key using
fallbackLocale
from<i18n>
provided keys - Try to resolve the given key using global scope messages using
locale
- Try to resolve the given key using global scope messages using
fallbackLocale
When you want to react to when the locale is changed you'd use a Vue.js' watch to do so:
import { watch } from 'vue'
import { useI18n } from '@padcom/vue-i18n'
const { locale } = useI18n()
watch(locale, newLocale => {
console.log('Locale changed to', newLocale)
})
This internationalization system was created specifically because the original vue-i18n plugin makes it impossible to use it in the context of webcomponents. It's a design choice they have made and that's not going to change any time soon.
However, if you don't need webcomponents interoperability and you're happy with how the original plugin works or if you need some of the advanced features (like pluralization or message arguments) then you should definitely go with the original one. It's much more robust and this system will probably never grow to be as complete as the original one is.