Guide
What is @fluejs/nuxt-i18n
?
@fluejs/nuxt-i18n
is a simple yet flexible module for Nuxt that helps localize your project. Here’s what the module offers:
- Definition of dictionaries (messages) in JSON files, with processing and chunk splitting during the build process
- Language prefixes in routing paths, enhancing SEO localization
- Built-in interpolation and pluralization when accessing the dictionary using @fluejs/txtag
Do I need it?
It’s partly inspired by nuxt-i18n
and nuxt-i18n-micro
, but this module is not fully compatible with them.
If you’re planning to migrate from nuxt-i18n
, I’d recommend choosing nuxt-i18n-micro. However, if you’re starting from scratch or have a small to medium-sized project, I believe @fluejs/nuxt-i18n
would be an excellent alternative.
Installation
npm install --save-dev @fluejs/nuxt-i18n
yarn add -D @fluejs/nuxt-i18n
Basic setup
Because @fluejs/nuxt-i18n
is a module for Nuxt, it needs to be registered.
nuxt.config.ts
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
/* locales and defaultLocale is required */
i18n: {
// list of locales
locales: ['en', 'ru'],
// default/fallback locale
defaultLocale: 'en',
/* path to your dictionary */
dictionary: '~/app/dictionary.json',
},
});
Now we need to create the dictionary JSON file, the path to which we specified in the configuration. A simple dictionary would look like this:
app/dictionary.json
{
"greeting": {
"en": "Hello!",
"ru": "Привет!"
}
}
In this case, we declare the key greeting
with locale keys en
and ru
(the locales correspond to those specified in the module's configuration).
Now we can access this key, for example, using the global $t
method in the component template. Depending on the user's preferred language, the corresponding text will be displayed based on the dictionary.
<template>
<p>{{ $t('greeting') }}</p>
</template>
Usage
$t
Type: (key: string, data: object | array) => string
A global property for accessing dictionary keys.
{
"greeting": {
"en": "Hello!",
"ru": "Привет!"
},
"nested": {
"greeting": {
"en": "Hello!",
"ru": "Привет!"
}
}
}
<template>
<p>{{ $t('greeting') }}</p>
<!-- access to nested property with dot notation -->
<p>{{ $t('nested.greeting') }}</p>
</template>
Interpolation
You can pass data as the second argument, either as an object
or an array
, enabling interpolation.
{
"author": {
"en": "Author: {name}"
},
"time": {
"en": "Time is {0}:{1}:{2}"
}
}
<template>
<p>{{ $t('author', { name: 'fl3nkey' }) }}</p>
<p>{{ $t('time', [12,55,01]) }}</p>
</template>
Pluralization
This method also has built-in pluralization, which works as follows:
{
"apples": {
"en": "{count}[apple|apples]",
"ru": "{count}[яблоко|яблока|яблок]"
},
"apples_with_custom_position": {
"en": "{count}[apple: %d|apples: %d]",
"ru": "{count}[яблоко: %d|яблока: %d|яблок: %d]"
}
}
<template>
<!-- output: "12 apples" -->
<p>{{ $t('apples', { count: 12 }) }}</p>
<!-- output: "apple: 1" -->
<p>{{ $t('apples_with_custom_position', { count: 1 }) }}</p>
</template>
$te
Type: (key: string) => boolean
A global property for checking whether a key exists in the dictionary.
<template>
<article>
<h1 v-if="$te('page.title')">
{{ $t('page.title') }}
</h1>
<p>
{{ $t('page.text') }}
</p>
</article>
</template>
useI18n
A composable that provides an API for working with the i18n
plugin.
const {
t,
te,
locales,
locale,
setLocale,
dictionaryState,
loadDictionary,
loadDictionaryRaw,
} = useI18n();
t
Type: (key: string, data: object | array) => string
Same as $t
te
Type: (key: string) => boolean
Same as $te
locales
Type: string[]
The array of available locales.
locale
Type: Ref<string>
The current locale ref variable.
setLocale
Type: (locale: string) => void
A method for changing the current locale.
dictionaryState
Type: Ref<Record<string, string>>
The current dictionary ref object.
loadDictionary
Type: (moduleName: string) => Promise<void>
A method for load a dictionary module.
Read more about modular dictionary
loadDictionaryRaw
Type: (data: Record<string, string>) => void
A method to load a custom dictionary object into the state.
Read more about externalize dictionary
useLocaleRoute
A composable for working with locale paths. Useful when you use a locale prefix in routing paths.
const {localeRoute, stripLocalePath} = useLocaleRoute();
localeRoute('/'); // /en
localeRoute({ name: 'dashboard' }); // /en/dashboard
localeRoute('/', 'ru'); // /ru
localeRoute({ name: 'dashboard' }, 'ru'); // /ru/dashboard
stripLocalePath('/en/dashboard'); // /dashboard
defineI18nRoutes
If you use custom routing with router.options.(ts|js)
, the module will not automatically add locale prefixes to the routes. For this, you can use this utility function.
import type { RouterConfig } from '@nuxt/schema';
export default {
routes: () => defineI18nRoutes([{
name: 'index',
path: '/',
component: () => import('~/pages/index.vue'),
}]),
} satisfies RouterConfig;
Components
I18n
A global component which allows expressions to be replaced with an attribute or a slot
{
"privacy_link": {
"en": "You are agree with our {link}Privacy policy{/link}"
}
}
<template>
<I18n keypath="privacy_link">
<template #link="{ body }">
<a href="/privacy">{{ body }}</a>
</template>
</I18n>
</template>
Read more about TxTag component API
I18nLink
A global component that wraps NuxtLink
and automatically adds the current locale to the provided route.
<template>
<!-- <a href="/en">Go to home</a> -->
<I18nLink to="/">
Go to home
</I18nLink>
<!-- <a href="/en/dashoard">Go to dashboard</a> -->
<I18nLink :to="{ name: 'dashboard' }">
Go to dashboard
</I18nLink>
</template>
Hooks
i18n:setLocale
Type: (locale: string) => HookResult
A hook that will be triggered after the setLocale
method is called.
nuxtApp.hook('i18n:setLocale', (locale) => {
console.log(locale); // new locale
});
i18n:refreshDictionary
Type: () => HookResult
A hook that will be called when the dictionary state needs to be updated, such as when switching locales.
nuxtApp.hook('i18n:refreshDictionary', () => {
// load your external dictionary
});
i18n:detectLocale
Type: (locale: Ref<string>) => HookResult
A hook that will be called when a user locale needs to be determined.
NOTE
This hook must be registered BEFORE the module registers the i18n
plugin.
NOTE
Use only if autoDetectLocale
is false
.
nuxtApp.hook('i18n:detectLocale', (locale) => {
locale.value = 'your locale';
});
Dictionary
A dictionary is an object that describes messages for each locale. The module accepts only JSON files as the source. The object described in the JSON file can also be nested, but in the end, the property should consist of an object in the format "{locale}": "{message}"
.
{
"message": {
"en": "Message",
"ru": "Сообщение"
},
"nested": {
"message": {
"en": "Message",
"ru": "Сообщение"
}
}
}
After build, this dictionary is converted into a flat object and split into chunks per module and per locale.
_nuxt/example_default_en_chunk.js
export default {
"message": "Message",
"nested.message": "Message"
}
_nuxt/example_default_ru_chunk.js
export default {
"message": "Сообщение",
"nested.message": "Сообщение"
}
These chunks will be loaded at runtime based on the current locale.
Modular dictionary
The dictionary can also be "modular," and modules can be lazily loaded, for example, for a specific page or section of the application. Modules are defined in advance in nuxt.config.(ts|js)
.
NOTE
default
module will be automatically loaded during initialization
NOTE
For modules, a prefix for the key will not be created automatically, so if you have identical keys between dictionary modules, they will overwrite each other
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
i18n: {
dictionary: {
default: '~/app/dictionary/default.json',
auth: '~/app/dictionary/auth.json',
dashboard: '~/app/dictionary/dashboard.json',
}
},
});
Afterward, you can load the module using the loadDictionary method.
pages/auth.vue
<template>
<p>${{ $t('auth_greeting') }}</p>
</template>
<script setup lang="ts">
const {loadDictionary} = useI18n();
await loadDictionary('auth');
</script>
pages/dashboard.vue
<template>
<h1>${{ $t('dashboard.title') }}</h1>
</template>
<script setup lang="ts">
const {loadDictionary} = useI18n();
await loadDictionary('dashboard');
</script>
Externalize dictionary
The module also supports scenarios where you plan to store, manage, and retrieve the dictionary on the other side (e.g., API, CMS).
In this case, you can omit the dictionary
property in the module configuration.
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
i18n: {
locales: ['en', 'ru'],
defaultLocale: 'en'
},
});
Create your own plugin for example. Use loadDictionaryRaw for load data to dictionary state.
export default defineNuxtPlugin(async (nuxtApp) => {
const {locale, loadDictionaryRaw} = useI18n();
const loadDictionary = async () => {
const data = await $fetch('/api/dictionary', {
query: {
locale: locale.value,
}
});
// pass data to dictionary state
loadDictionaryRaw(data);
}
// refresh dictionary state with new data (e.g. on setLocale)
nuxtApp.hook('i18n:refreshDictionary', loadDictionary);
await callOnce(loadDictionary);
});
Routing
If you need a unique path per locale for each page, this module provides such functionality. To enable it, set router: true
in the module configuration.
After that, all pages will be wrapped in a parent dynamic route, where the first parameter will accept the locale.
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
i18n: {
locales: ['en', 'ru'],
defaultLocale: 'en',
router: true,
},
});
// now available as /:locale
"/pages/index.vue"
// now available as /:locale/dashboard
"/pages/dashboard.vue"
Additionally, if no valid locale is found in the path, the user will be redirected to their preferred locale (from the available ones).
You’ll now need to keep in mind that locales are included in the paths when creating and working with routes. To simplify this, you can use I18nLink and useLocaleRoute.
<template>
<I18nLink to="/dashboard">Go to dashboard</I18nLink>
<NuxtLink :to="localeRoute('/catalog')">Go to catalog</NuxtLink>
</template>
<script setup lang="ts">
const {localeRoute, stripLocalePath} = useLocaleRoute();
const route = useRoute();
const isIndexRoute = computed(() => stripLocalePath(route.path) === '/');
</script>
Changing the locale in the route using router.push
will not update the current locale. You still need to call setLocale
<template>
<!-- wont work -->
<NuxtLink :to="localeRoute($route.fullPath, 'en')">
change to en
</NuxtLink>
<!-- will work -->
<NuxtLink :to="localeRoute($route.fullPath, 'en')"
custom
v-slot="{ href }">
<a :href="href"
@click.prevent="setLocale('en')">
change to en
</a>
</NuxtLink>
</template>
<script setup lang="ts">
const {setLocale} = useI18n();
const {localeRoute} = useLocaleRoute();
</script>
Module options
Below are the properties that the module accepts. To configure it, use the i18n
key in nuxt.config.(ts|js)
.
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
i18n: {
/* ... */
},
});
locales
Type: string[]
Required
List of available locales. It is recommended to use values from the ISO standard (ISO 639), as these values will also be used to determine the user's preferred language (based on Accept-Language
and navigator.languages
).
defaultLocale
Type: string
Required
The default locale. It should match the value specified in locales
. It will be used as a fallback if the user's language cannot be determined or if the corresponding locale is not found in the dictionary.
localeLanguages
Type: Record<string, string>
Default: undefined
An object that additionally describes which language a locale belongs to when determining the user's language. The key represents the language, and the value represents the corresponding locale.
Here are a few examples:
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
i18n: {
locales: ['en', 'ru'],
defaultLocale: 'ru',
localeLanguages: {
'en-GB': 'en',
'en-US': 'en',
}
},
});
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
i18n: {
locales: ['en-GB', 'en-US'],
defaultLocale: 'en-US',
localeLanguages: {
en: 'en-GB',
}
},
});
autoDetectLocale
Type: boolean
Default: true
A property that indicates whether to automatically detect the user's locale based on Accept-Language
and navigator.languages
.
For custom locale detection, you can use the i18n:detectLocale hook.
dictionary
Type: string | string[] | Record<string, string | string[]>
Default: undefined
This property is a reference to the JSON files of your dictionary.
export default defineNuxtConfig({
modules: ['@fluejs/nuxt-i18n'],
i18n: {
// as string
dictionary: '~/app/dictionary.json',
// as array
dictionary: [
'~/app/dictionary1.json',
'~/app/dictionary2.json'
],
// as modules
dictionary: {
default: ['~/app/dictionary1.json', '~/app/dictionary2.json'],
auth: '~/app/auth.json',
}
},
});
If you are using only an "external" dictionary, this property can be left undefined.
router
Type: boolean
Default: false
A property that indicates whether to use a locale prefix in routing paths.
meta
Type: boolean
Default: false
A property that indicates whether to embed meta tags for locales. Works only when router
property is true
.
The module will add <link rel="canonical">
and <link rel="alternate" hreflang="{locale}">
to the <head>
of your page.
metaOrigin
Type: string
Default: undefined
The URL origin for meta tags.
cookieKey
Type: string
Default: i18n-locale
The cookie key where the user's preferred locale is stored.
cookieMaxAge
Type: number
Default: 31536000
(1 year)
reloadOnSetLocale
Type: boolean
Default: false
If true
, the page will reload with the new locale when setLocale is called.
shallowDictionaryRef
Type: boolean
Default: true
If true
, the dictionary ref state will not deep reactivity but will be more performance-optimized.
The nuance is that calling loadDictionary or loadDictionaryRaw will not trigger changes when shallowDictionaryRef
is true
. If you expect deeply reactive behavior from the dictionary state, set it to false
.
<template>
<div>
{{ $t('example') }}
</div>
</template>
<script setup lang="ts">
const {loadDictionaryRaw, dictionaryState} = useI18n();
const load = () => {
loadDictionaryRaw({ example: 'Hello!' });
// trigger dictionary ref if shallow
triggerRef(dictionaryState);
}
onMounted(() => load());
</script>