Les niveaux de traduction de sites web
Traduire un site web n'est pas une chose unique. C'est un spectre allant du "hack rapide" au "système d'internationalisation de qualité production".
Voici à quoi cela ressemble :
| Niveau | Effort | Qualité | Cas d'utilisation |
|---|---|---|---|
| 1. Widget Google Translate | 5 minutes | Faible | Temporaire, test de la demande |
| 2. Pages statiques manuelles | 1-2 jours | Bonne | Petits sites (5-10 pages) |
| 3. Fichiers JSON + Bibliothèque | 3-5 jours | Super | La plupart des sites web |
| 4. Intégration CMS | 1-2 semaines | Super | Sites marketing |
| 5. Plateforme i18n complète | 2-4 semaines | Excellente | SaaS, sites à fort trafic |
Ce guide couvre les cinq approches avec des exemples de code.
Niveau 1 : Widget Google Translate (Le hack de 5 minutes)
Quand l'utiliser : Vous voulez tester si un marché est viable avant d'investir dans une vraie traduction.
Avantages :
- Zéro code
- Supporte 100+ langues
- Gratuit
Inconvénients :
- La qualité de traduction est médiocre
- Pas de valeur SEO (les robots voient la langue d'origine)
- Semble non professionnel
- Les traductions ne sont pas mises en cache (lent)
Implémentation
Ajoutez ceci à votre <body> :
HTML1<div id="google_translate_element"></div> 2 3<script type="text/javascript"> 4 function googleTranslateElementInit() { 5 new google.translate.TranslateElement( 6 { 7 pageLanguage: 'en', 8 includedLanguages: 'es,fr,de,zh-CN,ja', 9 layout: google.translate.TranslateElement.InlineLayout.SIMPLE 10 }, 11 'google_translate_element' 12 ); 13 } 14</script> 15 16<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
C'est tout. Les utilisateurs voient une liste déroulante pour choisir les langues.
Styliser le widget :
CSS1/* Cacher le branding Google */ 2.goog-te-banner-frame { 3 display: none !important; 4} 5 6/* Styliser la liste déroulante */ 7.goog-te-combo { 8 padding: 8px 12px; 9 border: 1px solid #e2e8f0; 10 border-radius: 6px; 11 font-size: 14px; 12}
Pourquoi ça craint pour la production
-
Désastre SEO : Google indexe votre contenu original. Les utilisateurs hispanophones cherchant en espagnol ne trouveront pas votre site.
-
Pas de contrôle : Vous ne pouvez pas corriger les mauvaises traductions. "Bank" pourrait se traduire par "banc" (pour s'asseoir) au lieu de banque.
-
Performance : Chaque chargement de page tape sur les serveurs de Google. Ajoute de la latence.
-
Problèmes UX : La mise en page se casse quand le texte s'étend (l'allemand est 30 % plus long que l'anglais).
Verdict : Bien pour tester. Ne livrez pas ça à de vrais utilisateurs.
Niveau 2 : Pages statiques manuelles (La méthode Copier-Coller)
Quand l'utiliser : Petit site (moins de 10 pages), rarement mis à jour, soucieux du budget.
Effort : 1-2 jours par langue.
Comment ça marche
Créez des fichiers HTML séparés pour chaque langue :
/index.html (Anglais)
/es/index.html (Espagnol)
/fr/index.html (Français)
/de/index.html (Allemand)
Exemple de structure
Version anglaise (/index.html) :
HTML1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <link rel="alternate" hreflang="es" href="/es/" /> 5 <link rel="alternate" hreflang="fr" href="/fr/" /> 6 <title>Welcome to Our Product</title> 7</head> 8<body> 9 <nav> 10 <a href="/">English</a> 11 <a href="/es/">Español</a> 12 <a href="/fr/">Français</a> 13 </nav> 14 15 <h1>Welcome to Our Product</h1> 16 <p>Build amazing apps with our platform.</p> 17 18 <button>Get Started</button> 19</body> 20</html>
Version espagnole (/es/index.html) :
HTML1<!DOCTYPE html> 2<html lang="es"> 3<head> 4 <link rel="alternate" hreflang="en" href="/" /> 5 <link rel="alternate" hreflang="fr" href="/fr/" /> 6 <title>Bienvenido a Nuestro Producto</title> 7</head> 8<body> 9 <nav> 10 <a href="/">English</a> 11 <a href="/es/">Español</a> 12 <a href="/fr/">Français</a> 13 </nav> 14 15 <h1>Bienvenido a Nuestro Producto</h1> 16 <p>Construye aplicaciones increíbles con nuestra plataforma.</p> 17 18 <button>Comenzar</button> 19</body> 20</html>
Configuration SEO
Ajoutez des balises hreflang pour que Google sache quelle version montrer :
HTML1<!-- Dans chaque page --> 2<link rel="alternate" hreflang="en" href="https://example.com/" /> 3<link rel="alternate" hreflang="es" href="https://example.com/es/" /> 4<link rel="alternate" hreflang="fr" href="https://example.com/fr/" /> 5<link rel="alternate" hreflang="x-default" href="https://example.com/" />
Détection de langue
Redirigez en fonction de la langue du navigateur :
JavaScript1// Détecter la langue de l'utilisateur et rediriger 2const userLang = navigator.language || navigator.userLanguage; 3const supportedLangs = ['en', 'es', 'fr']; 4const lang = userLang.slice(0, 2); 5 6// Rediriger uniquement sur la page d'accueil 7if (window.location.pathname === '/' && supportedLangs.includes(lang) && lang !== 'en') { 8 // Vérifier si l'utilisateur a manuellement sélectionné une langue auparavant 9 if (!localStorage.getItem('language_selected')) { 10 window.location.href = `/${lang}/`; 11 } 12} 13 14// Se souvenir du choix de l'utilisateur 15document.querySelectorAll('nav a').forEach(link => { 16 link.addEventListener('click', () => { 17 localStorage.setItem('language_selected', 'true'); 18 }); 19});
Avantages
- Contrôle SEO complet
- Traductions parfaites (vous engagez des professionnels)
- Rapide (pas d'appels API externes)
- Hébergement simple (juste des fichiers HTML)
Inconvénients
- Cauchemar de maintenance (mettre à jour 5 pages pour chaque changement)
- Ne passe pas à l'échelle (100 pages × 5 langues = 500 fichiers)
- Pas de contenu dynamique
Verdict : Bien pour les pages de destination, terrible pour les applications.
Niveau 3 : Fichiers JSON + Bibliothèque (L'approche standard)
Quand l'utiliser : La plupart des sites web. C'est la norme de l'industrie.
Frameworks : React, Vue, Next.js, Svelte - tous supportent cela.
Comment ça marche
- Extraire les chaînes vers des fichiers JSON
- Utiliser une bibliothèque i18n pour charger les traductions
- Remplacer le texte codé en dur par des clés de traduction
Exemple React (react-i18next)
Installer :
Terminalnpm install react-i18next i18next
Configuration (i18n.js) :
JavaScript1import i18n from 'i18next'; 2import { initReactI18next } from 'react-i18next'; 3 4import enTranslations from './locales/en.json'; 5import esTranslations from './locales/es.json'; 6import frTranslations from './locales/fr.json'; 7 8i18n 9 .use(initReactI18next) 10 .init({ 11 resources: { 12 en: { translation: enTranslations }, 13 es: { translation: esTranslations }, 14 fr: { translation: frTranslations } 15 }, 16 lng: localStorage.getItem('language') || 'en', 17 fallbackLng: 'en', 18 interpolation: { 19 escapeValue: false // React échappe déjà 20 } 21 }); 22 23export default i18n;
Fichiers de traduction :
locales/en.json :
JSON1{ 2 "welcome": { 3 "title": "Welcome to Our Product", 4 "subtitle": "Build amazing apps with our platform", 5 "cta": "Get Started" 6 }, 7 "nav": { 8 "home": "Home", 9 "pricing": "Pricing", 10 "docs": "Documentation" 11 } 12}
locales/fr.json :
JSON1{ 2 "welcome": { 3 "title": "Bienvenue dans Notre Produit", 4 "subtitle": "Construisez des applications incroyables avec notre plateforme", 5 "cta": "Commencer" 6 }, 7 "nav": { 8 "home": "Accueil", 9 "pricing": "Tarifs", 10 "docs": "Documentation" 11 } 12}
Composant :
JSX1import { useTranslation } from 'react-i18next'; 2 3function HomePage() { 4 const { t, i18n } = useTranslation(); 5 6 const changeLanguage = (lng) => { 7 i18n.changeLanguage(lng); 8 localStorage.setItem('language', lng); 9 }; 10 11 return ( 12 <div> 13 <nav> 14 <button onClick={() => changeLanguage('en')}>Anglais</button> 15 <button onClick={() => changeLanguage('es')}>Espagnol</button> 16 <button onClick={() => changeLanguage('fr')}>Français</button> 17 </nav> 18 19 <h1>{t('welcome.title')}</h1> 20 <p>{t('welcome.subtitle')}</p> 21 <button>{t('welcome.cta')}</button> 22 </div> 23 ); 24}
Avec des variables :
JSX1// Fichier de traduction 2{ 3 "greeting": "Bonjour, {{name}} ! Vous avez {{count}} messages." 4} 5 6// Composant 7<p>{t('greeting', { name: 'Sarah', count: 5 })}</p> 8// Sortie : "Bonjour, Sarah ! Vous avez 5 messages."
Avec des pluriels :
JSON1{ 2 "messages": "{{count}} message", 3 "messages_plural": "{{count}} messages" 4}
JSX<p>{t('messages', { count: 1 })}</p> // "1 message" <p>{t('messages', { count: 5 })}</p> // "5 messages"
Exemple Next.js (next-intl)
Installer :
Terminalnpm install next-intl
Structure de fichiers :
/app/[locale]/
├── layout.tsx
├── page.tsx
/messages/
├── en.json
├── es.json
├── fr.json
i18n.ts :
TypeScript1import { getRequestConfig } from 'next-intl/server'; 2 3export default getRequestConfig(async ({ locale }) => ({ 4 messages: (await import(`./messages/${locale}.json`)).default 5}));
middleware.ts (détection de locale) :
TypeScript1import createMiddleware from 'next-intl/middleware'; 2 3export default createMiddleware({ 4 locales: ['en', 'es', 'fr'], 5 defaultLocale: 'en' 6}); 7 8export const config = { 9 matcher: ['/((?!api|_next|.*\..*).*)'] 10};
Composant Page :
TSX1import { useTranslations } from 'next-intl'; 2 3export default function HomePage() { 4 const t = useTranslations('welcome'); 5 6 return ( 7 <div> 8 <h1>{t('title')}</h1> 9 <p>{t('subtitle')}</p> 10 <button>{t('cta')}</button> 11 </div> 12 ); 13}
URLs :
/en→ Anglais/es→ Espagnol/fr→ Français
Next.js gère le routage, le SEO et la détection de locale automatiquement.
Exemple Vue (vue-i18n)
Installer :
Terminalnpm install vue-i18n
Configuration (main.js) :
JavaScript1import { createApp } from 'vue'; 2import { createI18n } from 'vue-i18n'; 3import App from './App.vue'; 4 5import en from './locales/en.json'; 6import es from './locales/es.json'; 7 8const i18n = createI18n({ 9 locale: localStorage.getItem('language') || 'en', 10 fallbackLocale: 'en', 11 messages: { en, es } 12}); 13 14createApp(App).use(i18n).mount('#app');
Composant :
VUE1<template> 2 <div> 3 <select v-model="$i18n.locale" @change="saveLanguage"> 4 <option value="en">Anglais</option> 5 <option value="es">Espagnol</option> 6 </select> 7 8 <h1>{{ $t('welcome.title') }}</h1> 9 <p>{{ $t('welcome.subtitle') }}</p> 10 </div> 11</template> 12 13<script> 14export default { 15 methods: { 16 saveLanguage() { 17 localStorage.setItem('language', this.$i18n.locale); 18 } 19 } 20}; 21</script>
Gestion des traductions
Problème : Vous avez maintenant des fichiers JSON. Comment faites-vous pour :
- Les envoyer aux traducteurs ?
- Suivre ce qui est traduit ?
- Détecter les clés manquantes ?
Option 1 : Manuelle (pénible)
- Exporter JSON vers Excel
- Envoyer par e-mail aux traducteurs
- Copier-coller au retour
Option 2 : Utiliser un TMS (intelligent)
Terminal1# Exemple IntlPull 2npx @intlpullhq/cli init 3 4# Télécharger les chaînes source 5npx @intlpullhq/cli upload --source locales/en.json 6 7# Les traducteurs traduisent dans l'interface web 8 9# Télécharger les traductions 10npx @intlpullhq/cli download 11# Crée locales/es.json, locales/fr.json, etc.
Les traducteurs travaillent dans une interface web, vous tirez simplement les fichiers mis à jour.
Niveau 4 : Intégration CMS (Sites Marketing)
Quand l'utiliser : Sites riches en contenu (blogs, pages marketing).
Options CMS : Contentful, Sanity, Strapi.
Comment ça marche
Au lieu de fichiers JSON, le contenu vit dans un CMS. Les éditeurs traduisent là-bas.
Exemple : Next.js + Contentful
Schéma :
JavaScript1// Type de contenu Contentful 2{ 3 "name": "Blog Post", 4 "fields": [ 5 { "id": "title", "type": "Text", "localized": true }, 6 { "id": "body", "type": "RichText", "localized": true }, 7 { "id": "slug", "type": "Text", "localized": true } 8 ] 9}
Récupération :
TypeScript1import { createClient } from 'contentful'; 2 3const client = createClient({ 4 space: process.env.CONTENTFUL_SPACE_ID, 5 accessToken: process.env.CONTENTFUL_ACCESS_TOKEN 6}); 7 8export async function getBlogPost(slug: string, locale: string) { 9 const entries = await client.getEntries({ 10 content_type: 'blogPost', 11 'fields.slug': slug, 12 locale: locale // 'en-US', 'es', 'fr' 13 }); 14 15 return entries.items[0]; 16}
Composant :
TSX1export default async function BlogPost({ params }) { 2 const { slug, locale } = params; 3 const post = await getBlogPost(slug, locale); 4 5 return ( 6 <article> 7 <h1>{post.fields.title}</h1> 8 <div>{documentToReactComponents(post.fields.body)}</div> 9 </article> 10 ); 11}
Routes :
/en/blog/hello-world/es/blog/hola-mundo/fr/blog/bonjour-monde
Avantages
- Les éditeurs non techniques peuvent traduire
- Texte enrichi, images, champs SEO tous traduits
- Aperçu avant publication
Inconvénients
- Le CMS coûte de l'argent
- Verrouillage vendeur
- Configuration plus complexe
Niveau 5 : Plateforme i18n complète (Échelle Production)
Quand l'utiliser : Produits SaaS, sites à fort trafic, mises à jour fréquentes.
Plateformes : IntlPull, Lokalise, Phrase, Crowdin.
Ce que vous obtenez
- UI Web pour les traducteurs (avec contexte, captures d'écran)
- CLI pour les développeurs (
upload/downloaddes traductions) - Intégration Git (synchronisation bidirectionnelle)
- Traduction automatique (DeepL, ChatGPT)
- Mises à jour Over-the-air (changer les traductions sans déployer)
- Mémoire de traduction (réutiliser les traductions passées)
- Flux de travail de révision (approuver/rejeter)
Exemple : Flux de travail IntlPull
1. Configuration initiale :
Terminalnpm install @intlpullhq/react npx @intlpullhq/cli init
2. Le développeur ajoute des chaînes dans le code :
TSX1import { useIntlPull } from '@intlpullhq/react'; 2 3function App() { 4 const { t } = useIntlPull(); 5 6 return ( 7 <div> 8 <h1>{t('welcome.title')}</h1> 9 <button>{t('welcome.cta')}</button> 10 </div> 11 ); 12}
3. Extraire et télécharger :
Terminalnpx @intlpullhq/cli upload # Scanne le code pour les appels t() # Télécharge les nouvelles chaînes sur la plateforme
4. Les traducteurs traduisent (dans l'interface web, avec captures d'écran et contexte)
5. Télécharger les traductions :
Terminalnpx @intlpullhq/cli download # Télécharge toutes les traductions # Génère les fichiers de locale
6. Déployer :
Terminalnpm run build
7. Mises à jour OTA (optionnel) :
Plus tard, vous corrigez une coquille en espagnol. Au lieu de redéployer :
Terminalnpx @intlpullhq/cli publish --ota
Les utilisateurs reçoivent les traductions mises à jour instantanément.
Configuration Production
Next.js + IntlPull :
TypeScript1// intlpull.config.ts 2export default { 3 projectId: 'your-project-id', 4 sourceLanguage: 'en', 5 targetLanguages: ['es', 'fr', 'de', 'ja', 'zh'], 6 ota: { 7 enabled: true, 8 pollingInterval: 60000 // Vérifier les mises à jour chaque minute 9 }, 10 translation: { 11 defaultEngine: 'deepl', 12 fallback: 'google' 13 } 14};
Composant :
TSX1'use client'; 2 3import { IntlPullProvider, useIntlPull } from '@intlpullhq/react'; 4 5export default function RootLayout({ children }) { 6 return ( 7 <IntlPullProvider config={{ projectId: 'your-project-id' }}> 8 {children} 9 </IntlPullProvider> 10 ); 11}
Avancé : Mémoire de traduction
Réutilisez les traductions passées automatiquement :
Terminal1# Chaîne dans l'app 1 : "Save changes" 2# Traduit en espagnol : "Guardar cambios" 3 4# Plus tard, dans l'app 2, vous ajoutez "Save changes" 5# IntlPull suggère "Guardar cambios" (correspondance à 100 %)
Gagne du temps, assure la cohérence.
SEO pour les sites traduits
1. Balises hreflang
Dites à Google dans quelle langue est chaque page :
HTML1<link rel="alternate" hreflang="en" href="https://example.com/en/page" /> 2<link rel="alternate" hreflang="es" href="https://example.com/es/page" /> 3<link rel="alternate" hreflang="fr" href="https://example.com/fr/page" /> 4<link rel="alternate" hreflang="x-default" href="https://example.com/en/page" />
2. URLs localisées
Option A : Sous-répertoires (recommandé)
example.com/en/example.com/es/example.com/fr/
Option B : Sous-domaines
en.example.comes.example.comfr.example.com
Option C : Domaines différents
example.com(Anglais)example.es(Espagnol)example.fr(Français)
Les sous-répertoires sont les plus faciles pour le SEO (toute l'autorité dans un seul domaine).
3. Métadonnées localisées
TSX1// Exemple Next.js 2export async function generateMetadata({ params }) { 3 const { locale } = params; 4 const titles = { 5 en: 'Best Translation Platform for Developers', 6 es: 'Mejor Plataforma de Traducción para Desarrolladores', 7 fr: 'Meilleure Plateforme de Traduction pour Développeurs' 8 }; 9 10 return { 11 title: titles[locale], 12 description: t('meta.description'), // Depuis le fichier de traduction 13 openGraph: { 14 title: titles[locale], 15 locale: locale 16 } 17 }; 18}
Optimisation des performances
1. Code Splitting
Chargez uniquement la langue actuelle :
JavaScript1// ❌ Mauvais : Charger toutes les langues 2import en from './locales/en.json'; 3import es from './locales/es.json'; 4import fr from './locales/fr.json'; 5 6// ✅ Bon : Chargement paresseux 7const loadTranslations = async (locale) => { 8 return await import(`./locales/${locale}.json`); 9};
2. Mise en cache
TypeScript1// Mettre en cache les traductions dans localStorage 2const cacheTranslations = (locale, data) => { 3 const cache = { 4 locale, 5 data, 6 timestamp: Date.now() 7 }; 8 localStorage.setItem('translations', JSON.stringify(cache)); 9};
