vue-i18n est le plugin d’internationalisation standard pour Vue. La version 9 et les suivantes ciblent Vue 3 et utilisent par défaut la Composition API, ce qui explique la plupart des confusions avec les tutoriels plus anciens.
Configuration
npm install vue-i18n@9
// src/i18n.js
import { createI18n } from "vue-i18n";
import en from "./locales/en.json";
export default createI18n({
legacy: false, // requis pour la Composition API
globalInjection: true, // rend $t disponible dans les templates
locale: "en",
fallbackLocale: "en",
messages: { en },
});
// src/main.js
import { createApp } from "vue";
import i18n from "./i18n";
import App from "./App.vue";
createApp(App).use(i18n).mount("#app");
legacy: false est le réglage qui compte. Omettez-le et l’instance tourne en mode de compatibilité Vue 2, useI18n() lève une exception, et chaque exemple Composition API trouvé en ligne échoue d’une façon qui ne pointe pas clairement vers cette option.
Utilisation
Dans un template, avec globalInjection :
<template>
<h1>{{ $t("dashboard.title") }}</h1>
<p>{{ $t("dashboard.welcome", { name: user.firstName }) }}</p>
</template>
Dans setup :
<script setup>
import { useI18n } from "vue-i18n";
const { t, n, d, locale } = useI18n();
</script>
<template>
<h1>{{ t("dashboard.title") }}</h1>
</template>
t traduit, n formate les nombres, d formate les dates, et locale est une ref que vous pouvez assigner pour changer de langue.
Le fichier de messages est du JSON imbriqué ordinaire :
{
"dashboard": {
"title": "Dashboard",
"welcome": "Welcome back, {name}"
}
}
Notez la syntaxe d’interpolation : accolades simples, à la différence des doubles accolades d’i18next. Si vous migrez entre les deux, c’est la première chose à convertir.
Interpolation
L’interpolation nommée est la forme à utiliser :
{ "invitation": "{inviter} invited you to {project}" }
L’interpolation par liste existe — {0}, {1} avec un argument tableau — et devrait être évitée pour la raison habituelle : les placeholders positionnels ne peuvent pas être réordonnés par un traducteur, et l’ordre des mots diffère selon les langues. Voir l’aide-mémoire des formats de placeholder.
Il existe aussi les messages liés, qui permettent à un message de référencer un autre :
{
"brand": "Acme",
"footer": "@:brand — all rights reserved"
}
Utile pour un nom de produit qui apparaît dans de nombreuses chaînes et pourrait changer. À utiliser avec parcimonie : un traducteur voyant @:brand n’a aucune idée de ce qui sera substitué, et dans les langues à genre grammatical, la valeur liée peut forcer un changement dans les mots environnants.
Pluralisation
vue-i18n sépare les formes de pluriel par un pipe à l’intérieur d’une seule chaîne :
{
"messages": "no messages | one message | {count} messages"
}
t("messages", 0); // => "no messages"
t("messages", 1); // => "one message"
t("messages", 5); // => "5 messages"
Avec trois formes, la première est utilisée pour zéro ; avec deux formes, la première est le singulier et la seconde le pluriel.
Cette syntaxe est compacte, et c’est aussi le point le plus faible de vue-i18n pour une localisation sérieuse. La règle intégrée gère deux, parfois trois formes. Les langues qui en ont besoin de quatre ou six — polonais, arabe, russe, gallois — exigent une règle personnalisée enregistrée sur l’instance :
createI18n({
legacy: false,
pluralRules: {
ru(choice, choicesLength) {
const n = Math.abs(choice) % 100;
const n1 = n % 10;
if (n > 10 && n < 20) return 2;
if (n1 > 1 && n1 < 5) return 1;
if (n1 === 1) return 0;
return 2;
},
},
});
Écrire ces règles à la main pour chaque langue est source d’erreurs, et se tromper subtilement sur l’une d’elles produit un bug qui n’apparaît qu’à certains nombres. Si vous prenez en charge des langues aux règles de pluriel complexes, envisagez sérieusement la prise en charge du format de message ICU à la place — @intlify/message-compiler accepte la syntaxe ICU, qui exprime les catégories de pluriel par leur nom et délègue les règles à CLDR. Voir ICU MessageFormat et les règles de pluriel par langue.
Nombres et dates
Déclarez les formats sur l’instance, puis utilisez-les par leur nom :
createI18n({
numberFormats: {
en: { currency: { style: "currency", currency: "USD" } },
fr: { currency: { style: "currency", currency: "EUR" } },
},
datetimeFormats: {
en: { short: { year: "numeric", month: "short", day: "numeric" } },
},
});
{{ n(total, "currency") }}
{{ d(createdAt, "short") }}
Ces fonctions délèguent à Intl en coulisses, ce qui vous donne gratuitement les bons séparateurs, le bon placement des symboles et le bon comportement calendaire pour chaque locale.
Charger les locales à la demande
Livrer chaque langue dans le bundle devient gaspilleur au-delà de deux ou trois. Chargez à la demande :
export async function setLocale(locale) {
if (!i18n.global.availableLocales.includes(locale)) {
const messages = await import(`./locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages.default);
}
i18n.global.locale.value = locale;
document.querySelector("html").setAttribute("lang", locale);
}
Appelez cette fonction depuis un garde de navigation du router afin que les messages soient présents avant que la route de destination ne s’affiche :
router.beforeEach(async (to) => {
await setLocale(to.params.locale ?? "en");
});
Définir l’attribut lang sur <html> n’est pas une décoration optionnelle — cela pilote la prononciation des lecteurs d’écran, les invites de traduction du navigateur, et le choix des polices pour le texte CJK.
Blocs dans les composants monofichiers
vue-i18n prend en charge un bloc <i18n> qui colocalise les messages avec le composant :
<i18n>
{ "en": { "title": "Settings" } }
</i18n>
Cela fait bonne impression en démo et c’est mal adapté à de vrais flux de traduction : les chaînes sont dispersées dans des centaines de fichiers .vue, si bien qu’il n’y a aucun fichier à confier à un traducteur ni aucun moyen de voir l’ensemble des messages. Gardez les messages dans des fichiers de locale dédiés.
Synchroniser les traductions
wti push # envoie la locale source
wti pull # récupère les locales traduites
wti diff # ce qu’un push changerait
Poussez à chaque merge sur votre branche désignée, récupérez chaque nuit dans une pull request. Le flux de travail de localisation basé sur Git couvre pourquoi une seule branche doit pousser.
Sécurité de typage
vue-i18n peut dériver son type de clé à partir d’un schéma, si bien qu’une clé mal saisie devient une erreur de compilation plutôt qu’une chaîne affichant la clé elle-même :
import type en from "./locales/en.json";
type MessageSchema = typeof en;
const i18n = createI18n<[MessageSchema], "en" | "fr">({
legacy: false,
locale: "en",
messages: { en: enMessages },
});
Les paramètres génériques vous donnent l’autocomplétion sur t() et une erreur quand une clé est supprimée du JSON mais toujours référencée. Dix lignes, et cela supprime toute une classe d’échec silencieux.
Compilation des messages au moment du build
Par défaut, vue-i18n embarque un compilateur de messages runtime qui parse vos chaînes de messages dans le navigateur. Vous pouvez plutôt les compiler au moment du build, ce qui est à la fois plus rapide et plus léger :
// vite.config.js
import vueI18n from "@intlify/unplugin-vue-i18n/vite";
export default {
plugins: [
vueI18n({ include: path.resolve(__dirname, "./src/locales/**") }),
],
};
Cela remplace le build complet par la version runtime-only et précompile les messages en fonctions. Le compromis est que les messages doivent être connus au moment du build, donc cela ne se combine pas avec la récupération de fichiers de locale depuis une API — mais cela se combine bien avec le chargement différé par import() dynamique vu plus haut, puisque ce sont toujours des modules connus au moment du build.
Cela fait aussi apparaître les messages malformés comme des erreurs de build plutôt que des erreurs runtime, ce qui vaut le coup en soi.
Tests
Montez les composants avec une vraie instance i18n plutôt que de stubber $t. Stubber cache exactement les échecs que vous voulez détecter — une clé manquante, une forme de pluriel qui ne se résout pas :
import { createI18n } from "vue-i18n";
import { mount } from "@vue/test-utils";
const i18n = createI18n({ legacy: false, locale: "en", messages: { en } });
mount(Component, { global: { plugins: [i18n] } });
Faites des assertions sur le comportement plutôt que sur le texte traduit, afin qu’un changement de formulation ne casse pas le test. Ajoutez ensuite une vérification d’exhaustivité séparée en CI : chaque clé présente dans chaque locale, et chaque clé pluralisée portant le nombre de formes que sa langue exige.
Erreurs courantes
- Omettre
legacy: false, puis se demander pourquoiuseI18n()lève une exception. - Supposer que la règle de pluriel par défaut suffit pour les langues avec plus de deux formes.
- Utiliser des blocs
<i18n>, qui fragmentent l’ensemble des messages au-delà de la portée d’un traducteur. - L’interpolation positionnelle
{0}, qui ne peut pas être réordonnée. - Oublier l’attribut
langlors du changement de locale. - Formater les nombres manuellement au lieu d’utiliser
n.
Frequently asked questions
- Comment utiliser vue-i18n avec la Composition API ?
- Créez l’instance i18n avec legacy : false, puis appelez useI18n() dans setup pour obtenir t, n et d. Sans legacy : false, l’instance tourne en mode de compatibilité Vue 2 et useI18n lève une exception.
- Comment fonctionne la pluralisation dans vue-i18n ?
- Les formes de pluriel sont séparées par un caractère pipe dans une seule chaîne, et sélectionnées par le nombre passé à t. Le jeu de règles par défaut gère deux formes ; les langues qui en ont besoin de plus nécessitent une règle de pluralisation personnalisée enregistrée sur l’instance.
- Quelle est la différence entre $t et t dans vue-i18n ?
- $t est la propriété globale disponible dans les templates en mode legacy ou avec l’injection globale. t est la fonction renvoyée par useI18n() dans la Composition API. Elles résolvent les mêmes messages ; la différence tient à la façon de les obtenir.
- Comment charger les fichiers de locale à la demande dans vue-i18n ?
- Importez dynamiquement les messages de la locale, enregistrez-les avec i18n.global.setLocaleMessage, puis basculez la locale. Faire cela dans un garde de navigation du router garantit que les messages sont chargés avant que la route de destination ne s’affiche.
Keep reading
-
React i18next : configuration et flux de travail
Configurer i18next dans une application React : namespaces, pluriels, interpolation, Trans pour le balisage imbriqué, chargement différé, et synchronisation des fichiers de traduction.
-
ICU MessageFormat : un guide pratique
La syntaxe des pluriels, de la sélection par genre, du formatage des nombres et des dates dans une seule chaîne de message — avec les règles d’échappement et les erreurs que font systématiquement les traducteurs.
-
Règles de pluriel par langue : le guide CLDR complet
Un tableau de référence des catégories de pluriel CLDR pour 163 langues, plus pourquoi one ne veut pas dire 1 et comment chaque framework i18n attend que vous écriviez les règles.
-
Traduire des fichiers JSON (documentation)
Comment WebTranslateIt analyse les structures JSON utilisées par vue-i18n, y compris les clés imbriquées et les tableaux.
Translate your app without the spreadsheet round-trip
WebTranslateIt reads the file formats and placeholder syntax described on this page, validates them as translators work, and syncs the results straight back into your repository.