Introduction
L'internationalisation (i18n) d'un SaaS est l'un des chantiers les plus sous-estimés du développement logiciel. Ce n'est pas "juste de la traduction", c'est une refonte en profondeur de la manière dont votre application gère le texte, les dates, les devises, le routage et le SEO. Mal anticipée, elle peut coûter des mois de refactoring. Bien pensée dès le départ, elle ouvre votre produit à un marché mondial sans friction.
Ce guide couvre l'ensemble du processus : des fondamentaux à l'implémentation technique avec Nuxt.js et nuxt-i18n, en passant par le SEO multilingue (hreflang), la gestion des paiements multi-devises avec Stripe, et les outils de traduction à l'échelle. Vous y trouverez du code prêt à l'emploi et les pièges concrets que nous avons rencontrés sur nos projets SaaS.

i18n, L10n, G11n : Comprendre les Enjeux d'un SaaS Global
Avant de plonger dans le code, clarifions trois concepts interdépendants qui structurent toute stratégie d'expansion internationale.
Internationalisation, Localisation et Globalisation
- Internationalisation (i18n) : Le processus de conception de votre application pour qu'elle puisse être adaptée à différentes langues et régions sans modifications d'ingénierie majeures. C'est la phase de préparation technique : externaliser les chaînes de texte, gérer les formats de date dynamiquement, supporter le RTL. Pensez à l'architecture de votre SaaS, qui doit être assez flexible pour supporter de multiples locales dès le départ.
- Localisation (L10n) : L'adaptation concrète du produit internationalisé pour une langue et une culture spécifiques. Au-delà de la traduction, cela inclut les formats de date, devises, unités de mesure, conventions d'adresse, images et même le ton éditorial.
- Globalisation (G11n) : La stratégie qui englobe i18n + L10n : planifier quels marchés cibler, dans quel ordre, avec quels niveaux de localisation.
Pourquoi Anticiper Dès le MVP ?
Ignorer l'i18n lors de la conception de votre MVP, c'est s'exposer à des coûts 3x plus élevés au moment de l'internationalisation :
- Refonte massive du code : chaque chaîne hardcodée doit être extraite, chaque affichage de date ou devise réécrit
- Risque d'erreurs : les oublis mènent à des bugs d'affichage coûteux en réputation
- Impact UX : une app mal internationalisée frustre les utilisateurs locaux et les pousse vers la concurrence
- Temps perdu : vos équipes passent des mois à corriger des problèmes fondamentaux au lieu de développer des features
En intégrant l'i18n dès le début, vous posez les fondations d'une architecture multi-tenant robuste, prête à accueillir de nouvelles langues avec agilité.
Les Pièges Classiques de l'Internationalisation
Au-delà de la Simple Traduction
La traduction des chaînes de caractères n'est que la surface. Votre application doit aussi gérer :
- Pluriels et grammaire : "Il y a
nutilisateur(s)" ne se traduit pas de la même manière en arabe (6 formes de pluriel), en polonais (3 formes) ou en japonais (pas de pluriel). Les systèmes modernes basés sur ICU Message Format gèrent ces cas. - Formats de date :
MM/DD/YYYY(US) vsDD/MM/YYYY(EU) vsYYYY/MM/DD(Asie). Ne jamais hardcoder. - Formats numériques et devises :
1,234.56(US) vs1.234,56(EU). La position du symbole monétaire varie ($100vs100 €). - RTL (Right-To-Left) : L'arabe et l'hébreu inversent toute l'interface : navigation, formulaires, icônes directionnelles. Votre CSS doit être conçu pour le supporter.
- Images contenant du texte : Impossible à traduire automatiquement. Privilégiez les icônes universelles ou le texte superposable en HTML/CSS.
- Tri et collation : L'ordre alphabétique varie (
Åen suédois n'est pas à la même position qu'en français). Le tri des listes doit utiliserIntl.Collator.
Concaténation : L'Erreur la Plus Fréquente
// ❌ Ne faites JAMAIS ça
const message = "Bienvenue " + user.name + ", vous avez " + count + " messages";
// ✅ Utilisez des clés de traduction avec interpolation
// fr.json: { "welcome": "Bienvenue {name}, vous avez {count} message | messages" }
t('welcome', { name: user.name, count })
L'ordre des mots change entre les langues. En japonais, le nom vient souvent en premier. En arabe, la structure de phrase est complètement différente. Toute concaténation manuelle cassera inévitablement dans au moins une langue.
Implémentation Technique avec Nuxt.js et nuxt-i18n
@nuxtjs/i18n est le module de référence pour l'internationalisation d'applications Nuxt.js. Construit sur vue-i18n, il gère le routage multilingue, le lazy loading des traductions et la détection automatique de la langue.
Installation et Configuration de Base
npx nuxi module add @nuxtjs/i18n
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{
code: 'fr',
language: 'fr-FR',
file: 'fr.json',
name: 'Français'
},
{
code: 'en',
language: 'en-US',
file: 'en.json',
name: 'English'
},
{
code: 'de',
language: 'de-DE',
file: 'de.json',
name: 'Deutsch'
}
],
defaultLocale: 'fr',
lazy: true,
langDir: 'locales/',
strategy: 'prefix_except_default'
}
})
Stratégies de Routage : Quel Préfixe Choisir ?
Le choix de la stratégie de routage impacte directement votre SEO et votre UX :
| Stratégie | URL FR | URL EN | SEO | Cas d'usage |
|---|---|---|---|---|
prefix_except_default | /pricing | /en/pricing | Excellent | SaaS avec une langue principale |
prefix | /fr/pricing | /en/pricing | Excellent | Marchés équivalents, pas de langue par défaut |
no_prefix | /pricing | /pricing | Mauvais | Apps internes, pas de SEO nécessaire |
Pour un SaaS ciblant l'international, prefix_except_default est généralement le meilleur choix : votre marché principal conserve des URLs propres, et les marchés secondaires ont des URLs clairement localisées.
Fichiers de Traduction et Interpolation
// locales/fr.json
{
"nav": {
"home": "Accueil",
"pricing": "Tarifs",
"login": "Connexion"
},
"dashboard": {
"welcome": "Bienvenue {name}",
"projects_count": "Aucun projet | {count} projet | {count} projets",
"last_login": "Dernière connexion : {date}"
},
"billing": {
"plan_price": "{price} / mois",
"trial_remaining": "Il vous reste {days} jour d'essai | Il vous reste {days} jours d'essai"
}
}
// locales/en.json
{
"nav": {
"home": "Home",
"pricing": "Pricing",
"login": "Sign in"
},
"dashboard": {
"welcome": "Welcome {name}",
"projects_count": "No projects | {count} project | {count} projects",
"last_login": "Last login: {date}"
},
"billing": {
"plan_price": "{price} / month",
"trial_remaining": "{days} trial day remaining | {days} trial days remaining"
}
}
Utilisation dans les Composants Vue
<template>
<div>
<h1>{{ $t('dashboard.welcome', { name: user.name }) }}</h1>
<p>{{ $t('dashboard.projects_count', { count: projects.length }, projects.length) }}</p>
<p>{{ $t('dashboard.last_login', { date: d(user.lastLogin, 'short') }) }}</p>
<!-- Sélecteur de langue -->
<select @change="switchLocale($event.target.value)">
<option
v-for="locale in availableLocales"
:key="locale.code"
:value="locale.code"
:selected="locale.code === $i18n.locale"
>
{{ locale.name }}
</option>
</select>
</div>
</template>
<script setup>
const { locale, locales, setLocale } = useI18n()
const switchLocalePath = useSwitchLocalePath()
const availableLocales = computed(() => locales.value)
async function switchLocale(code) {
await setLocale(code)
await navigateTo(switchLocalePath(code))
}
</script>
Formatage des Dates et Nombres avec Intl
Ne réinventez pas la roue : utilisez l'API Intl du navigateur, que vue-i18n intègre nativement :
// nuxt.config.ts : configuration des formats
i18n: {
datetimeFormats: {
fr: {
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }
},
en: {
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }
}
},
numberFormats: {
fr: {
currency: { style: 'currency', currency: 'EUR' }
},
en: {
currency: { style: 'currency', currency: 'USD' }
}
}
}
<!-- Résultat : "15/03/2026" en FR, "03/15/2026" en EN -->
<p>{{ d(date, 'short') }}</p>
<!-- Résultat : "49,00 €" en FR, "$49.00" en EN -->
<p>{{ n(4900, 'currency') }}</p>
SEO Multilingue : hreflang, Sitemap et Structure d'URL
L'internationalisation sans SEO multilingue, c'est construire un magasin sans y afficher d'enseigne dans la langue du quartier. Google doit comprendre quelle version de votre page servir à quel utilisateur.
Balises hreflang
Les balises hreflang indiquent aux moteurs de recherche les relations entre les versions linguistiques d'une même page. Sans elles, Google peut considérer vos pages traduites comme du contenu dupliqué.
<!-- Dans le <head> de chaque page -->
<link rel="alternate" hreflang="fr" href="https://votresaas.com/pricing" />
<link rel="alternate" hreflang="en" href="https://votresaas.com/en/pricing" />
<link rel="alternate" hreflang="de" href="https://votresaas.com/de/pricing" />
<link rel="alternate" hreflang="x-default" href="https://votresaas.com/pricing" />
Avec nuxt-i18n, ces balises sont générées automatiquement via le composable useHead et la configuration du module. Assurez-vous que baseUrl est configuré dans votre nuxt.config.ts :
i18n: {
baseUrl: 'https://votresaas.com'
}
Structure d'URL : Sous-répertoire vs Sous-domaine vs Domaine
| Structure | Exemple | SEO | Maintenance | Recommandation |
|---|---|---|---|---|
| Sous-répertoire | votresaas.com/en/ | Hérite de l'autorité du domaine | Facile — un seul déploiement | Recommandé pour la majorité des SaaS |
| Sous-domaine | en.votresaas.com | Autorité séparée, SEO plus lent | Moyenne — configurations DNS | Grand volume, équipes locales dédiées |
| Domaine séparé | votresaas.co.uk | Ciblage géo fort, autorité à construire | Complexe — plusieurs déploiements | Marchés très spécifiques (Chine, Russie) |
Pour un SaaS en croissance, les sous-répertoires (/en/, /de/) sont le choix optimal : vous conservez toute l'autorité SEO de votre domaine principal, et le déploiement reste simple. C'est d'ailleurs la stratégie que nous utilisons pour nos propres projets et recommandons dans notre guide sur le SEO technique.
Sitemap Multilingue
Générez un sitemap XML incluant les balises xhtml:link pour chaque version linguistique :
<url>
<loc>https://votresaas.com/pricing</loc>
<xhtml:link rel="alternate" hreflang="fr" href="https://votresaas.com/pricing"/>
<xhtml:link rel="alternate" hreflang="en" href="https://votresaas.com/en/pricing"/>
<xhtml:link rel="alternate" hreflang="de" href="https://votresaas.com/de/pricing"/>
</url>
Meta Tags Localisés
Chaque version linguistique doit avoir ses propres title et meta description, pas une simple traduction automatique, mais un contenu optimisé pour les requêtes de recherche locales :
// pages/pricing.vue
useHead({
title: computed(() => t('pricing.meta_title')),
meta: [
{ name: 'description', content: computed(() => t('pricing.meta_description')) }
]
})
Paiements Multi-Devises avec Stripe
L'internationalisation du billing est un sujet à part entière. Afficher des prix en euros à un client américain, c'est ajouter de la friction au moment le plus critique de votre funnel. Pour une intégration complète de Stripe dans votre SaaS, consultez notre guide dédié à l'intégration Stripe.
Stripe Multi-Currency : Les Fondamentaux
Stripe supporte nativement plus de 135 devises. La clé est de créer vos produits et prix avec des variantes par devise :
// Créer un prix en EUR et USD pour le même produit
const priceEUR = await stripe.prices.create({
product: 'prod_xxx',
unit_amount: 4900, // 49,00 €
currency: 'eur',
recurring: { interval: 'month' }
});
const priceUSD = await stripe.prices.create({
product: 'prod_xxx',
unit_amount: 5200, // $52.00
currency: 'usd',
recurring: { interval: 'month' }
});
Mapping Locale → Devise
const LOCALE_CURRENCY_MAP: Record<string, { currency: string; priceId: string }> = {
'fr': { currency: 'eur', priceId: 'price_eur_monthly' },
'en': { currency: 'usd', priceId: 'price_usd_monthly' },
'de': { currency: 'eur', priceId: 'price_eur_monthly' },
'ja': { currency: 'jpy', priceId: 'price_jpy_monthly' }
};
function getPriceForLocale(locale: string) {
return LOCALE_CURRENCY_MAP[locale] ?? LOCALE_CURRENCY_MAP['en'];
}
TVA et Taxes par Pays
La gestion de la TVA en multi-pays est un piège majeur. Stripe Tax automatise le calcul, mais vous devez tout de même :
- Identifier le pays du client (via l'adresse de facturation, pas la locale du navigateur)
- Appliquer le bon taux de TVA (20 % en France, 19 % en Allemagne, 0 % hors UE pour le B2B)
- Générer des factures conformes à chaque juridiction
Pour la conformité RGPD et les obligations légales par pays, un accompagnement juridique est fortement recommandé dès que vous dépassez 2-3 marchés.
Gestion des Traductions à l'Échelle
Workflow de Traduction : Du Développeur au Traducteur
Un workflow i18n mature sépare clairement les responsabilités :
Développeur → Plateforme i18n → Traducteur
(Crowdin, Lokalise)
Ajoute clé "new_feature" Synchronise les Traduit dans
dans fr.json fichiers JSON chaque langue
↕ ↕
Pull Request auto Validation contextuelle
avec traductions (screenshots, contexte)
Outils de Gestion des Traductions (TMS)
| Outil | Forces | Prix | Idéal pour |
|---|---|---|---|
| Crowdin | Intégration GitHub/GitLab, over-the-air updates | Gratuit (open source), 40 $/mois (pro) | SaaS en croissance, communauté |
| Lokalise | UI intuitive, QA automatisé, branching | 120 $/mois | Équipes produit avec traducteurs dédiés |
| Phrase | API puissante, gestion de contexte avancée | Sur devis | Entreprises multi-produits |
| i18next + fichiers JSON | Gratuit, pas de dépendance externe | 0 € | MVP, < 3 langues |
Intégration CI/CD
Automatisez la synchronisation des traductions dans votre pipeline CI/CD :
# .github/workflows/i18n-sync.yml
name: Sync translations
on:
push:
paths:
- 'locales/fr.json'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Upload source to Crowdin
uses: crowdin/github-action@v2
with:
upload_sources: true
download_translations: false
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
# Workflow inverse : télécharger les traductions
name: Download translations
on:
schedule:
- cron: '0 6 * * 1' # Tous les lundis à 6h
jobs:
download:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download from Crowdin
uses: crowdin/github-action@v2
with:
upload_sources: false
download_translations: true
create_pull_request: true
pull_request_title: 'chore(i18n): update translations'
Bonnes Pratiques pour les Fichiers de Traduction
- Organisez par feature, pas par page :
dashboard.welcomeplutôt quepage_dashboard.text_1 - Clés en anglais snake_case : lisibles par les développeurs, indépendantes de la langue source
- Pas de HTML dans les traductions : utilisez les composants
<i18n-t>de vue-i18n pour le markup - Contexte pour les traducteurs : ajoutez des commentaires dans votre TMS pour les chaînes ambiguës
- Tests de longueur : l'allemand est ~30 % plus long que l'anglais, le japonais ~50 % plus court. Votre UI doit s'adapter.
Tester l'Internationalisation
L'i18n est une source de régressions silencieuses. Intégrez ces vérifications dans vos tests automatisés :
// Vérifier qu'aucune clé de traduction ne manque
import fr from '../locales/fr.json';
import en from '../locales/en.json';
function getKeys(obj: Record<string, any>, prefix = ''): string[] {
return Object.entries(obj).flatMap(([key, value]) =>
typeof value === 'object'
? getKeys(value, `${prefix}${key}.`)
: [`${prefix}${key}`]
);
}
describe('i18n completeness', () => {
it('en.json has all keys from fr.json', () => {
const frKeys = getKeys(fr);
const enKeys = getKeys(en);
const missing = frKeys.filter(k => !enKeys.includes(k));
expect(missing).toEqual([]);
});
});
Testez aussi visuellement avec des pseudo-traductions (remplacer chaque caractère par un accent : Ãççöûñt Sëttîñgs) pour repérer les textes hardcodés oubliés et les problèmes de débordement.
FAQ : Internationalisation d'un SaaS
FAQ - Questions fréquentes
Conclusion : L'i18n, un Investissement Stratégique
L'internationalisation n'est pas un chantier technique isolé, c'est un levier de croissance stratégique. Un SaaS bien internationalisé peut multiplier son marché adressable par 5 ou 10 en ciblant l'anglais, l'allemand et l'espagnol en plus du français. Les outils modernes (nuxt-i18n, Crowdin, Stripe Tax) réduisent considérablement la friction technique, mais la réussite repose sur une décision architecturale prise tôt : externaliser les textes, structurer le routage multilingue, et prévoir la gestion des devises dès la conception.
Si vous envisagez d'internationaliser votre SaaS ou de créer un SaaS de zéro avec l'international en tête, contactez notre équipe. Nous concevons des applications web sur mesure et des SaaS prêts pour l'international dès le premier jour.





