Le Lancement Qui a Tout Cassé
Le lancement allemand était prévu pour 9h. À 8h55, quelqu'un a ouvert la version allemande.
Boutons coupés au milieu du mot. Navigation cassée. Formulaires illisibles. L'interface soigneusement conçue ressemblait à la peinture au doigt d'un tout-petit.
Que s'est-il passé ? L'anglais "Save" (4 carac.) est devenu l'allemand "Speichern" (10 carac.). L'UI n'était pas prête pour ça.
Ce guide vous apprend comment construire des UI qui fonctionnent dans n'importe quelle langue, du japonais compact à l'allemand verbeux en passant par l'arabe de droite à gauche.
Les Trois Problèmes Difficiles
1. Expansion du Texte (Le Problème Allemand)
Différentes langues prennent différentes quantités d'espace pour la même signification :
| Anglais | Allemand | Expansion |
|---|---|---|
| Save | Speichern | +150% |
| Delete | Löschen | +75% |
| Settings | Einstellungen | +120% |
| OK | OK | 0% |
Expansion moyenne par langue :
| Langue | Expansion vs Anglais |
|---|---|
| Allemand | +30% |
| Français | +15-20% |
| Espagnol | +20-30% |
| Italien | +15-20% |
| Portugais | +15-25% |
| Russe | +10-15% |
| Japonais | -10% à -20% (plus compact) |
| Chinois | -30% (très compact) |
| Arabe | +20-25% |
2. Mises en Page Droite-à-Gauche (RTL)
L'arabe, l'hébreu, le persan, l'ourdou se lisent de droite à gauche. Votre mise en page entière doit se retourner.
Ce qui change :
- Navigation : Droite → Gauche
- Alignement du texte : Par défaut à droite
- Icônes : Miroir horizontalement
- Fil d'Ariane : Ordre inverse
- Formulaires : Étiquettes à droite
- Barres de défilement : À gauche
Ce qui reste pareil :
- Vidéos/images (ne pas refléter)
- Numéros de téléphone, adresses
- Logos (généralement)
- Blocs de code
3. Limites de Caractères
Twitter autorise 280 caractères, pas octets.
Votre base de données varchar(50) pourrait contenir 50 octets, ce qui n'est que 16 caractères chinois.
Gérer l'Expansion du Texte
Stratégie 1 : Mises en Page Flexibles
❌ Mauvais : Largeurs fixes
CSS.button { width: 80px; /* Casse en Allemand */ }
✅ Bon : Largeur auto avec remplissage
CSS1.button { 2 padding: 0.5rem 1rem; 3 min-width: 80px; /* Empêche les boutons minuscules */ 4 max-width: 200px; /* Empêche absurdement long */ 5 white-space: nowrap; /* Ou autoriser le retour à la ligne */ 6}
Stratégie 2 : Troncature avec Infobulles
TSX1// Composant React 2function TruncatedText({ text, maxLength = 30 }) { 3 const isTruncated = text.length > maxLength; 4 const displayText = isTruncated 5 ? text.slice(0, maxLength) + '...' 6 : text; 7 8 return isTruncated ? ( 9 <span title={text}>{displayText}</span> 10 ) : ( 11 <span>{displayText}</span> 12 ); 13}
Stratégie 3 : Typographie Responsive
CSS1/* Réduire la taille de police pour le texte plus long */ 2.button { 3 font-size: clamp(0.75rem, 2vw, 1rem); 4}
Stratégie 4 : Tester avec Pseudo-Localisation
Simuler l'expansion du texte avant de traduire :
JavaScript1function pseudoLocalize(text) { 2 // Ajouter des crochets et des caractères supplémentaires pour simuler l'expansion 3 const expanded = text 4 .split('') 5 .map(char => { 6 // Remplacer par des versions accentuées 7 const map = { 8 'a': 'á', 'e': 'é', 'i': 'í', 'o': 'ó', 'u': 'ú', 9 'A': 'Á', 'E': 'É', 'I': 'Í', 'O': 'Ó', 'U': 'Ú' 10 }; 11 return map[char] || char; 12 }) 13 .join(''); 14 15 // Ajouter 30% de longueur supplémentaire 16 const padding = 'x'.repeat(Math.ceil(text.length * 0.3)); 17 18 return `[[${expanded}${padding}]]`; 19}
Si votre UI casse avec la pseudo-localisation, elle cassera avec de vraies langues.
Implémenter le Support RTL
Étape 1 : Détecter la Direction
JavaScript1function getDirection(locale) { 2 const rtlLanguages = ['ar', 'he', 'fa', 'ur']; 3 const language = locale.split('-')[0]; 4 return rtlLanguages.includes(language) ? 'rtl' : 'ltr'; 5} 6 7// Définir sur l'élément HTML racine 8document.documentElement.dir = getDirection(currentLocale); 9document.documentElement.lang = currentLocale;
Étape 2 : Utiliser les Propriétés CSS Logiques
❌ Ancienne façon (directionnelle) :
CSS1.sidebar { 2 float: left; 3 margin-right: 20px; 4 padding-left: 10px; 5 border-left: 1px solid #ccc; 6}
✅ Nouvelle façon (logique) :
CSS1.sidebar { 2 float: inline-start; /* gauche en LTR, droite en RTL */ 3 margin-inline-end: 20px; 4 padding-inline-start: 10px; 5 border-inline-start: 1px solid #ccc; 6}
Étape 3 : Miroir de Mise en Page
Flexbox :
CSS1.nav { 2 display: flex; 3 /* Inverse automatiquement en RTL */ 4}
Étape 4 : Gérer Images/Icônes
Ne pas tout refléter :
CSS1/* Refléter les icônes qui indiquent la direction */ 2.icon-arrow { 3 transform: scaleX(var(--rtl-mirror, 1)); 4} 5 6html[dir="rtl"] { 7 --rtl-mirror: -1; /* Retourner horizontalement */ 8} 9 10/* Ne pas refléter les logos, photos */ 11.logo, 12.photo { 13 transform: scaleX(1) !important; 14}
Étape 5 : Tester la Mise en Page RTL
Astuce DevTools :
JavaScript// Commande console pour tester RTL document.documentElement.dir = 'rtl';
Gérer les Limites de Caractères
Problème : Contraintes de Base de Données
Solution 1 : Augmenter les limites
SQL1-- ✅ Tenir compte des caractères multi-octets 2CREATE TABLE products ( 3 name VARCHAR(200) -- Marge de sécurité 4x 4);
Solution 2 : Utiliser les types TEXT
SQL1-- ✅ Pas de limite de longueur 2CREATE TABLE products ( 3 name TEXT 4);
Problème : Limites d'Affichage UI
Valider le nombre de caractères, pas le nombre d'octets :
TSX1function validateInput(text, maxChars) { 2 // ✅ Compter les clusters de graphèmes (caractères perçus par l'utilisateur) 3 const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); 4 const segments = Array.from(segmenter.segment(text)); 5 6 return segments.length <= maxChars; 7}
Problème : Troncature
✅ Troncature intelligente :
JavaScript1function smartTruncate(text, maxLength, locale = 'en') { 2 const segmenter = new Intl.Segmenter(locale, { granularity: 'word' }); 3 const segments = Array.from(segmenter.segment(text)); 4 5 if (segments.length <= maxLength) return text; 6 7 // Tronquer à la limite du mot, pas au milieu du mot 8 let truncated = ''; 9 // ... logique 10 return truncated.trim() + '...'; 11}
Design Multilingue Responsif
Défi : Même espace, différentes langues
Une barre de navigation qui convient à l'anglais pourrait déborder en Allemand.
Solution : Points de rupture responsifs par langue
CSS1/* Allemand a besoin de plus d'espace */ 2html[lang="de"] .nav { 3 gap: 0.5rem; /* Espacement plus serré */ 4 font-size: 0.9rem; /* Légèrement plus petit */ 5} 6 7/* Japonais peut être plus compact */ 8html[lang="ja"] .nav { 9 gap: 1.5rem; /* Plus d'espace de respiration */ 10}
Considérations Mobiles
Problème : Le mobile a encore moins d'espace.
TSX1function MobileNav() { 2 const { t } = useTranslation('common'); 3 const isMobile = useMediaQuery('(max-width: 768px)'); 4 5 return ( 6 <nav> 7 {isMobile ? ( 8 // Utiliser des étiquettes plus courtes sur mobile 9 <> 10 <a href="/settings">{t('nav.settings_short')}</a> 11 </> 12 ) : ( 13 <a href="/settings">{t('nav.settings')}</a> 14 )} 15 </nav> 16 ); 17}
Stratégies de Test
1. Test de Régression Visuelle
JavaScript1// Exemple Playwright 2locales.forEach(locale => { 3 test(`Homepage looks correct in ${locale}`, async ({ page }) => { 4 await page.goto(`/${locale}`); 5 await expect(page).toHaveScreenshot(`homepage-${locale}.png`); 6 }); 7});
2. Détection de Débordement de Texte
JavaScript1// Test automatisé pour débordement de texte 2function detectOverflow() { 3 const elements = document.querySelectorAll('button, .nav-item, .card-title'); 4 // ... vérifier scrollWidth > clientWidth 5}
3. Vérification de Mise en Page RTL
JavaScript1test('RTL layout is correct for Arabic', async ({ page }) => { 2 await page.goto('/ar'); 3 const dir = await page.$eval('html', el => el.dir); 4 expect(dir).toBe('rtl'); 5 // ... vérifier alignement texte 6});
4. Liste de Contrôle de Test Manuel
- Tester toutes les langues sur mobile (espace restreint)
- Tester les langues RTL (Arabe, Hébreu)
- Tester les langues CJK (Chinois, Japonais, Coréen)
- Tester la langue la plus longue (généralement Allemand)
Solutions Spécifiques au Framework
React
TSX1// Fournisseur de direction 2function DirectionProvider({ locale, children }) { 3 const direction = getDirection(locale); 4 // ... 5 return ( 6 <DirectionContext.Provider value={direction}> 7 {children} 8 </DirectionContext.Provider> 9 ); 10}
Tailwind CSS
JavaScript1// tailwind.config.js 2module.exports = { 3 plugins: [ 4 require('@tailwindcss/rtl'), 5 ], 6};
Le Résumé
La localisation UI n'est pas juste de la traduction - c'est :
- Mises en page flexibles qui gèrent 30% d'expansion de texte
- Support RTL avec propriétés CSS logiques
- Limites de caractères comptées correctement
- Design responsif pour mobile + multilingue
- Tests avec pseudo-localisation
Gains rapides :
- Remplacer tous les
left/rightparinline-start/inline-end - Utiliser
min-widthau lieu dewidthpour les boutons - Tester avec Arabe (RTL) et Allemand (expansion de texte)
Besoin d'aide avec la localisation UI ?
Essayez IntlPull - Fournit un contexte visuel aux traducteurs.
