Lorsque l’on démarre le sujet de l’internationalisation et de la localisation, on est vite perdu dans cet océan de locales, de tags, de languages, etc. Tout le monde y va de son grain de sel sur la bonne manière de faire. Pourtant c’est un passage obligé lorsqu’on développe une application web destiné à un public multilingue.
Afin d’y voir, j’espère, un peu plus clair, voici le fruit de mes recherches, y compris dans les RFC quelque peu ardues à lire, mais c’est là que sont définis les standards du Web. Nous verrons ensuite certaines implémentations d’outils qui amènent quelques contraintes (notamment les navigateurs). Et enfin, vous retrouverez des recommandations pour éviter certaines erreurs.
À la fin de cet article, vous aurez, je l’espère, tout ce qu’il faut pour bien gérer le sujet de l’internationalisation (et la localisation) sur vos applications web.
Le W3C définit les termes i18n et l10n comme suit (Language Tags and Locale Identifiers for the World Wide Web) :
Définitions
Internationalisation (i18n) : The design and development of a product that is enabled for target audiences that vary in culture, region, or language.
Localization (l10n) : The tailoring of a system to the individual cultural expectations of a specific target market or group of individuals. Localization includes, but is not limited to, the translation of user-facing text and messages. […] When a particular set of content and preferences corresponding to a specific set of international preferences is operationally available, then the system is said to be localized.
Locale : An identifier (such as a language tag) for a set of international preferences. Usually this identifier indicates the preferred language of the user and possibly includes other information, such as a geographic region (such as a country). A locale is passed in APIs or set in the operating environment to obtain culturally-affected behavior within a system or process.
International preferences : A user’s particular set of language and formatting preferences and associated cultural conventions. Software can use these preferences to correctly process or present information exchanged with that user.
Pour comprendre ce qu’est un “language tag”, il faut se plonger dans les standards du Web (RFC 5646, BCP 47, Unicode Locale et RFC 4647).
Standards du Web
Tags pour la localisation : RFC 5646 & BCP 47
La RFC 5646 définit une standardisation du format des identifiants de locales (ou paramètres régionaux en français).
Syntaxe
Pour être bien formé, un “language tag” comprend des subtags séparés par un tiret.
Un identifiant de locale ou tag commence par :
- Un « Primary Language Subtag » (obligatoire sauf exception, détail : RFC 5646: Tags for Identifying Languages ) permettant d’identifier la langue. Il est en général un code alpha-2 voire alpha-3 (règles et détails dans la même section)
- Suivi d’un « Extended Language Subtags » (facultatif) permettant de spécifier un peu plus une langue. Il est composé de 3 lettres. Détail : RFC 5646: Tags for Identifying Languages
Exemple : les extensions ‘gan‘, ‘yue‘ et ‘cmn‘ pour la langue Chinoise ‘zh‘, donnant ‘zh-gan‘ pour le Gan, ‘zh-yue‘ pour le Cantonais, et ‘zh-cmn‘ pour le Mandarin.
- Suivi d’un « Script Subtag » (facultatif) de 4 lettres utilisé pour indiquer des variations d’écriture. Détail : RFC 5646: Tags for Identifying Languages
Exemple : ‘sr-Latn‘ représente le Serbe en écriture latine
- Suivi d’une « Région subtag » (facultatif) utilisé pour indiquer une variation linguistique à un pays, territoire ou région. Ça peut être 2 lettres (code alpha-2) ou 3 chiffres (UN M49). Détail : RFC 5646: Tags for Identifying Languages
Exemple : ‘de-AT‘ pour l’allemand utilisé en Autriche, ‘es-419‘ pour l’espagnol en Amérique latine et régions caribéennes.
- Suivi de « Variant Subtags » (facultatif) permettant d’ajouter des variantes connues. Il peut y en avoir plusieurs d’affilée. Détail :RFC 5646: Tags for Identifying Languages
Exemple : ‘de-CH-1996‘ représente l’allemand utilisé en Suisse suite à la réforme de 1996.
- Suivi d’ « Extension Subtags » (facultatif) permettant d’apporter une information supplémentaire ne faisant pas partie de la distinction des précédents subtags. Il commence par un singleton (1 lettre), il peut y en avoir plusieurs d’affilée. Détail : RFC 5646: Tags for Identifying Languages
Exemple : ‘...-a-foo-b-bar‘
- Suivi de « Private Use Subtags » (facultatif) permettant d’indiquer une distinction spécifique importante dans un contexte privé. Im commence par la lettre ‘x’. Il peut y en avoir plusieurs d’affilée. Détail : RFC 5646: Tags for Identifying Languages
Exemple : ‘...-x-foo-x-bar‘
Les subtags ne sont pas sensibles à la casse, cependant, il y a certaines recommandations d’écriture comme celle d’écrire la région en majuscules.
Préfixes
Il est recommandé d’utiliser certains subtags avec un prefix :
- Les « Extended Language Subtags » en ont toujours exactement un
- Les « Variant Subtags » en ont parfois un ou plus
Exemple : le subtag ‘cmn‘ est recommandé d’être utilisé après un préfixe ‘zh‘, car l’« Extended Language Subtag » Mandarin n’a de sens seulement après le « Primary Language Subtag » Chinois.
Type: extlangSubtag: cmnDescription: Mandarin ChineseAdded: 2009-07-29Preferred-Value: cmnPrefix: zhMacrolanguage: zh
Détail : RFC 5646: Tags for Identifying Languages
Les autres types de subtags n’ont pas de préfixes.
Suppression du script
Certains subtags (« Primary Language Subtag » et « Extended Language Subtags ») sont recommandés de ne pas être utilisés avec un « Script Subtag » (via suppress-script), car la grande majorité de documents fournis dans la langue mentionnée est écrite avec l’alphabet en question, c’est donc une utilisation redondante.
Exemple : le subtag de langue ‘fr‘ est en grande majorité en écriture latine, donc cela permet de supprimer l’utilisation superflue du « Script Subtag » ‘Latn‘.
Type: languageSubtag: frDescription: FrenchAdded: 2005-10-16Suppress-Script: Latn
Détail : RFC 5646: Tags for Identifying Languages
Exceptions
Il y a des exceptions à cette norme (mentionnées dans la RFC), pour des usages privés ou des tags désuets.
Validation
Pour être valide, un tag :
- doit être bien formé (cf paragraphe Syntaxe plus haut) et tous les subtags sont référencés dans le « IANA Language Subtag Registry » ou faire partie des exceptions,
- N’a pas de « Variant Subtag » dupliqué
- N’a pas d’« Extension Subtag » dupliqué
Détail pour la conformité des tags : RFC 5646: Tags for Identifying Languages
À noter que cette RFC parle de « Tags for Identifying Languages » et de « Language Tags » pour la totalité du tag, cependant cette dénomination est assez mal nommée, puisque sont également identifiés tous les paramètres régionaux, et pas seulement la langue.
Toute cette nomenclature est également appelée pour plus de simplicité, la BCP (Best Current Practice) 47.
Le registre des subtags valides est tenu par l’IANA (Internet Assigned Numbers Authority) et est situé ici : https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
Forme canonique
La RFC définit également une forme canonique des “languages tags” : elle permet une simplification et une uniformisation des tags, puisque ceux-ci vont être utilisés par de nombreux utilisateurs et processus.
Elle consiste à :
- ranger les extensions par ordre alphabétique
- remplacer les tags ou subtags dépréciés ou désuets par leurs valeurs préférentielles
- remplacer les « Extended Language Subtags » par leurs valeurs préférentielles
Le grain de sel du Consortium Unicode
En plus de ça, le Unicode Consortium a étendu le BCP 47 en ajoutant certaines « Extension Subtags » :
- « BCP 47 Extension U » comportant un ‘
-u-‘ pour les Unicode locale. Elle possède à chaque fois une clé et une valeur.
Exemple : ‘...-u-ca-buddhist-hc-h12‘ pour le calendrier bouddhiste et un système d’heures sur 12 heures (Unicode Locale Data Markup Language (LDML) )
- « BCP 47 Extension T » comportant un ‘
-t-‘ pour les contenus transformés. Elle possède à chaque fois une clé et une valeur.
Exemple : ‘ru-t-en-h0-hybrid‘ pour du russe avec un mélange d’anglais américain (Unicode Locale Data Markup Language (LDML) )
La totalité est appelée « Unicode locales », et est la base du Web en termes d’internationalisation comme le W3C le défini : Language Tags and Locale Identifiers for the World Wide Web. Le W3C recommande l’utilisation des « Canonical Unicode locale identifier » pour choisir ses locales.
Les « Canonical Unicode locale identifier », définis par le Consortium Unicode, sont en soit la même chose que les codes BCP 47 en forme canonique mais avec les extensions Unicode mentionnées.
Correspondance/matching des tags : RFC 4647
La RFC 4647 définit une standardisation des préférences utilisateurs en matière de localisation : le “language range”.
Syntaxe
Ce format ressemble au format BCP 47 : c’est un enchaînement de subtags séparés par un tiret. Les subtags sont des caractères alphanumériques ASCII ou *, c’est-à-dire une “wildcard” qui signifie “quelque soit la séquence de subtags”. Les “language range” comme le format BCP 47 n’est pas sensible à la casse, même s’il existe une convention d’écriture pour capitaliser certains des subtags (la région notamment, ou les scripts en Titlecase).
Dans cette RFC est également définie une liste de priorités vis-à-vis de ces “language range”, ce qui permet d’avoir un préférence, mais un fallback si la préférence n’est pas possible. Exemple : un breton préférera avoir la langue bretonne, mais si cela n’est pas disponible, acceptera le français.
De manière écrite, cette liste se présente comme une séquence de “language range” séparées par une virgule.
Exemple : ‘en, fr, zh-Hant‘, ce qui se lit “anglais puis français puis chinois écrit en écriture traditionnelle”.
L’ordre de priorité est descendant par défaut, mais il peut aussi y avoir un poids associé à chacun, “q” : cf partie “Accept-Language” plus bas).
Il existe les “language range” basiques et les “language range” étendus. Les “language range” étendus permettent simplement de gérer la présence de subtags spécifiques, peu importe les subtags intermédiaires.
Exemple : ‘*-CH‘ est un « language range » étendu (signifie toutes les langues parlées en Suisse) ainsi que ‘fr-*-FR‘ tandis que ‘fr‘ ou ‘fr-CH‘ est un « language range » basique. (NB : ‘*‘ est un « language range » basique également.)
Lorsqu’une application ou un protocole ne supporte pas les “language range” étendus, mais peuvent toutefois en recevoir et devoir les traiter. Dans ce cas, 3 possibilités :
- transformer le “language range” étendu en un “language range” basique : si l’étendu commence par ‘
*‘ alors remplacer la totalité par ‘*‘, sinon on enlève simplement la wildcard
Exemple : ‘*-CH‘ donne ‘*‘, ‘en-*-US‘ donne ‘en-US‘.
- rejeter le “language range” étendu
- traiter les “language range” étendus comme des basiques, ce qui revient à les ignorer puisqu’ils ne vont correspondre à aucun code BCP 47
3 mécanismes de correspondance existent, et chaque application ou protocole peut appliquer l’un ou l’autre selon ses besoins.
1 – Filtrage basique (basic filtering en anglais)
Le filtrage basique cherche à faire correspondre une liste de “language ranges” basiques priorisée à un ensemble de codes au format BCP 47.
Pour un “language range” donné, le filtrage basique va comparer (de manière non sensible à la casse) s’il est strictement égal au code BCP 47 ou s’il est strictement égal à un (ou plusieurs) préfixe(s).
Exemple : le “language range” ‘de-de‘ (l’allemand utilisé en Allemagne) matche le tag ‘de-DE-1996‘ (l’allemand utilisé en Allemagne, orthographe de 1996) mais ne matche pas ‘de-Deva‘ (l’allemand écrit en écriture Devanagari) ni ‘de-Latn-DE‘ (l’allemand écrit en écriture latine, utilisé en Allemagne).
La wildcard ‘*’ matche n’importe quel tag.
2 – Filtrage étendu (extended filtering en anglais)
Le filtrage étendu cherche à faire correspondre une liste de “language ranges” étendus priorisée à un ensemble de codes au format BCP 47.
Exemple : le “language range” étendu ‘de-*-DE‘ matche les tags suivants :
– ‘de-DE‘,
– ‘de-de‘,
– ‘de-Latn-DE‘,
– ‘de-Latf-DE‘,
– ‘de-DE-x-goethe‘ (subtag privé),
– ‘de-Latn-DE-1996‘,
– ‘de-Deva-DE‘
mais ne matche pas :
– ‘de‘ (‘DE‘ manque),
– ‘de-x-DE‘ (‘x‘ est avant ‘DE‘),
– ‘de-Deva‘ (‘DE‘ manque à la fin)
3 – Recherche (lookup en anglais)
La recherche fait correspondre une liste de “language ranges” basiques à un ensemble de codes au format BCP 47 afin de trouver le tag exact qui match le mieux avec le « language range ».
Afin de trouver la meilleure concordance, le “language range” est progressivement tronqué à partir de la fin, jusqu’à ce qu’une correspondance soit trouvée. S’il n’y a aucun match, alors la valeur par défaut est retournée.
Exemple : le “language range” ‘zh-Hant-CN-x-private1-private2‘ va avoir le pattern de fallback suivant :
1- ‘zh-Hant-CN-x-private1-private2‘
2- ‘zh-Hant-CN-x-private1‘
3- ‘zh-Hant-CN‘
4- ‘zh-Hant‘
5- ‘zh‘
6- (default)
La wildcard ‘*’ et ses dérivés (‘*-CH‘ par exemple) “matchent” n’importe quel tag (‘-CH‘ est ignoré).
HTTP
La RFC 9110 décrit HTTP (Hypertext Transfer Protocol), son architecture, sa terminologie et ses différents mécanismes.
header Content-Language
L’en-tête Content-Language permet de spécifier la locale, au format BCP 47, dans laquelle un document a été écrit. Il peut y en avoir plusieurs, séparées par une virgule.
Exemple : Pour un dictionnaire français/anglaisContent-Language: en, fr
Si aucun Content-language n’est spécifié, alors le contenu est acceptable pour tous les publics. Détail : RFC 9110: HTTP Semantics
header Accept-Language
L’en-tête Accept-Language permet de spécifier la locale préférentielle dans laquelle un utilisateur ou un processus souhaite obtenir la réponse. Cette locale est en fait un ou des “language-range” (cf plus haut) : une ou plusieurs locales, au format BCP 47, séparées par une virgule. A chaque locale peut être associée (via un “; ») un poids (nommé q) qui permet de donner plus de valeur à une locale plutôt qu’à une autre. Ce poids doit être en 0 et 1. L’ordre de préférence suit le sens de lecture (de gauche à droite).
Exemple : Accept-Language: da, en-gb;q=0.8, en;q=0.7
Ce qui signifie que l’utilisateur ou le processus a une préférence pour le danois, mais acceptera l’anglais britannique et en dernier recours un anglais “générique”.
Le “language-range” ‘*‘ présent dans le champ Accept-Language matche toutes les autres locales pas déjà correspondante à un autre “language-range” dans le champ.
S’il n’y a pas de Accept-Language, alors toutes les locales sont acceptables.
Détail : RFC 9110: HTTP Semantics
Le fait de spécifier de manière précise dans chaque requête les préférences de locales d’un utilisateur peut permettre de déduire sa nationalité, et donc d’identifier une personne pour de la surveillance. De ce fait, cela peut porter atteinte au droit à la vie privée de l’utilisateur. Cela s’appelle du “Browser Fingerprinting” (Voir plus : RFC 9110: HTTP Semantics ).
Javascript
Javascript notamment, utilise les locales Unicode dans ses spécifications ECMAScript et notamment le Intl.Locale (Intl.Locale – JavaScript | MDN ) mais pas que (Intl – JavaScript | MDN ).
HTML
Les spécifications HTML5 définissent les attributs et éléments suivants pour la gestion de l’internationalisation :
Attributs
attribut lang
L’attribut (universel) lang permet d’adapter le rendu d’un texte. Ça peut être pour de la ponctuation, des ligatures, des espacements, la synthèse vocale.
Bonne pratique : définir la locale par défaut avec l’attribut HTML lang sur la balise <html>. Cette locale est héritée par toutes les autres balises.
Détail : HTML Standard
attribut translate
L’attribut (universel) translate permet de spécifier s’il est nécessaire de traduire lorsque la page est localisée ou s’il faut laisser le texte inchangé. Les valeurs possibles sont : ‘yes‘ (ou chaîne vide) ou ‘no‘.
Exemple :<html lang=fr>
<!-- par défaut translate=yes puisque la page est localisée -->
...
<p>Cette tarte est savoureuse !</p>
<p translate=no>Yum Yum !</p>
</html>
Détail : HTML Standard
attribut dir
L’attribut (universel) dir permet d’indiquer le sens de lecture du texte. Les valeurs possibles sont : ‘ltr‘ (left-to-right) ‘rtl‘ (right-to-left) et ‘auto‘. Cela influe par exemple sur l’ordre des items d’un flex-direction.
Détail : HTML Standard
attribut hreflang
L’attribut hreflang est utilisé pour indiquer la locale dans laquelle est écrite le contenu vers lequel on renvoie via un <a> ou <link>. Les valeurs valides sont les mêmes que l’attribut lang : les codes BCP 47. Cet attribut doit être uniquement présent si l’attribut href est présent.
Exemple :<a href="https://www.w3schools.com" hreflang="en">W3Schools</a>
Détail : HTML Standard
Eléments
element <bdo>
L’élément HTML <bdo> (de l’anglais “bidirectional override”) permet d’overrider la direction du texte localement. Cet élément doit avoir l’attribut ‘dir‘.
Exemple :<p>Texte de gauche à droite </p>
<p><bdo dir="rtl">Texte de droite à gauche</bdo>.</p>
Détail : HTML Standard
element <bdi>
L’élément HTML <bdi> (de l’anglais “bidirectional isolation”) permet d’utiliser dans une même phrase différents sens de lecture.
Exemple :<p dir="ltr"><bdi>أرمينيا جميلة</bdi>Cette phrase en arabe est automatiquement affichée de droite à gauche.</p>
Détail : HTML Standard
éléments <b> et <i>
Les éléments html <b> et <i> permettent respectivement de mettre en gras et en italique du texte. Cependant, selon les langues, le style gras ou italique n’est pas le style préférentiel pour mettre en avant telle ou telle information.
Par conséquent, il vaut mieux éviter d’utiliser ces éléments et leur préférer des éléments sémantiquement plus intéressants : <em>, <cite> par exemple.
Détail : Using b and i tags
CSS
Sélecteurs
selecteur [lang="..."]
Ce sélecteur est utilisé pour styliser un élément dont la valeur de l’attribut html lang est identique à la valeur du sélecteur.
Exemple : le CSS suivant va styliser le html qui suit*[lang="zh"]{
font-family: Kaiti,Kai, serif;
}
<p>"This is <em>English</em>" translates as <span lang="zh">这是<em>英文</em></span>.</p>
selecteur [lang|="..."]
Ce sélecteur est utilisé pour styliser un élément dont la valeur de l’attribut html lang commence par la valeur du sélecteur.
Exemple : le CSS suivant va styliser le html qui suit*[lang|="zh"] {
font-family: Kaiti,Kai, serif;
}
<p>"This is <em>English</em>" translates as <span lang="zh-Hans">这是<em>英文</em></span>.</p>
selecteur :lang(...)
Cette pseudo-classe est utilisée afin de styliser tous les éléments et leurs enfants dont la valeur de l’attribut html lang correspond à la valeur du sélecteur.
Exemple : le CSS suivant va styliser tout le document, alors que le sélecteur [lang="..."] n’aurait stylisé que le <p>.:lang(fr) {
font-family: "Times New Roman",serif;
}
<html lang=fr>...<p>Je suis français.</p></html>
Détail : Styling using language attributes
Propriétés
propriété direction
La propriété direction permet de spécifier la direction du texte, colonnes de tableaux, et overflow horizontal. Les valeurs possibles sont : ltr (left-to-right) qui est la valeur par défaut ou rtl (right-to-left).
A noter que la direction du texte est préférentiellement définie avec l’attribut html dir (cf plus haut).
Détail : CSS Writing Modes Module Level 4
propriété unicode-bidi
La propriété unicode-bidi permet de spécifier comment sont gérés les textes écrits dans 2 directions différentes.
Exemple : Dans le texte “בָּרוּךְ שֵׁם כְּבוֹד מַלְכוּתוֹ לְעוֹלָם וָעֶד.” il y a un “.” (un point) ne venant pas du même alphabet et donc le sens de lecture est différent du reste du texte.
Les valeurs possibles sont : normal (valeur initiale), embed, bidi-override, isolate, isolate-override, plaintext.
Détail : CSS Writing Modes Module Level 4
propriété writing-mode
La propriété writing-mode permet de spécifier si le texte est vertical ou horizontal, et dans quel sens le texte évolue : de haut en bas ou de gauche à droite. Pour un document entier, cette spécification se fait sur le tag <html>. Les valeurs possibles sont :
horizontal-tb: texte horizontal (peu importe si la direction estrtloultr), la 2e ligne horizontale sera en-dessous de la première (top-to-bottom). C’est la valeur initiale.
Exemple : français, anglais (de gauche à droite, de haut en bas) mais aussi arabe, hébreux (de droite à gauche, de haut en bas)
vertical-rl: pour une directionltr, texte vertical de haut en bas, la 2e ligne verticale sera à gauche de la précédente. Pour une directionrtl, texte vertical de bas en haut, la 2e ligne verticale sera à droite de la précédente.
Exemple : chinois (de haut en bas, de droite à gauche)
vertical-lr: pour une directionltr, texte vertical de haut en bas, la 2e ligne verticale sera à droite de la précédente. Pour une directionrtl, texte vertical de bas en haut, la 2e ligne verticale sera à gauche de la précédente.
Exemple : mongole (de haut en bas, de gauche à droite)
Détail : CSS Writing Modes Module Level 4
propriété text-orientation
La propriété text-orientation permet de définir l’orientation des caractères. Cela n’affecte que le texte en mode d’écriture verticale (writing-mode vertical-rl ou vertical-lr). Les valeurs possibles sont : mixed (valeur par défaut), upright, sideways.
Exemples :
mixeduprightsidewaysDétail : CSS Writing Modes Module Level 4
propriété text-combine-upright
La propriété text-combined-upright permet de combiner certains caractères dans l’espace d’un seul caractère. Cette propriété a seulement un effet dans le mode d’écriture verticale. Les valeurs possibles sont : none (valeur initiale), all et digits (ce dernier n’est pas encore implémenté par les navigateurs).
Exemple : le code html suivant, associé au CSS qui suit va donner le résultat à droite<p lang="zh-Hant">民國<span class="num">105</span>年<span class="num">4</span>月<span class="num">29</span>日</p>
html {
writing-mode : vertical-rl;
font: 24 px serif;
}
.num {
text-combine-upright: all;
}
Détail : CSS Writing Modes Module Level 4
Propriétés logiques VS physiques
Traditionnellement, les éléments sur une page html sont dimensionnées avec des dimensions physiques : les box ont une width et une height, les items sont positionnées à partir du top et du left, les borders, margin et padding sont définis pour le top, right, bottom et left.
Dans un contexte de gestion de différentes locales, la gauche n’est plus forcément la gauche, le haut n’est plus forcément le haut.
C’est pourquoi, il est nécessaire de revoir toutes ces propriétés et d’utiliser les propriétés logiques. Selon le writing-mode, chaque propriété logique n’aura pas la même signification physique.
Les propriétés logiques vont donc utiliser des termes abstraits pour décrire le flux d’écriture comme :
- block dimension
- inline dimension
Et également :
- start
- end
Exemple :blockquote {
text-align: start; /* left in latin, right in arabic */
margin-inline-start: 0px; /* margin-left in latin, margin-right in arabic */
border-inline-start: 5px solid gray; /* border-left in latin, border-right in arabic */
padding-inline-start: 5px; /* padding-left in latin, padding-right in arabic */
}
Détail : CSS Logical Properties and Values Module Level 1
Implémentations de l’outillage
Les navigateurs
Certains navigateurs ne supportent pas toutes les langues de tous les pays. Or, c’est avec ces configurations de locale que la détection automatique est réalisée.
Exemple : La Belgique
Côté Safari (paramètre de Mac), les langues française et néerlandaise sont supportées.
Côté Firefox, les langues française et néerlandaise sont également supportées.
Côté Edge : les langues française et néerlandaise sont également supportées.
Côté Chrome en revanche, aucune des 2 langues pour ce pays n’est référencée. Une issue existe à ce propos depuis 2014, sans évolution.
Les devices mobiles
C’est en se basant sur la locale des devices (iOS et Android) que l’on peut en déduire la locale pour notre application.
Les iOS supportent les mêmes locales que les Macs (cf plus haut) et les Android (Google derrière) supportent les mêmes locales que Google Chrome.
i18next
i18next est la librairie la plus utilisée côté front pour implémenter l’internationalisation. Dans la documentation de la librairie, il est mentionné :
Theoretically, you’re not bound to any specific language code format, but if you want to make use of all the in built language features, like proper pluralization and correct fallback resolution, we strongly suggest to use the following iso norm (BCP 47 language tag):
lng-(script)-REGION-(extensions)
source : FAQ | i18next documentation
Un mécanisme de fallback existe. Dans l’ordre, cet algorithme va chercher s’il ne trouve pas la clé originelle :
- une locale sans le script ou la région dans laquelle il y a la clé
i18next.init({ lng: "en-GB", resources: { "en-GB": { "translation": { "i18n": "Internationalisation" } }, "en": { "translation": { "i18n": "Internationalization", "i18n_short": "i18n" } } }}, () => { i18next.t('i18n'); // -> finds "Internationalisation" i18next.t('i18n_short'); // -> falls back to "en": "i18n"});
- une locale de fallback dans laquelle il y a la clé
i18next.init({ fallbackLng: { 'de-CH': ['fr', 'it'], //French and Italian are also spoken in Switzerland 'zh-Hant': ['zh-Hans', 'en'], 'es': ['fr'], 'default': ['en'] }});
- un namespace de fallback dans lequel il y a la clé
Par défaut, i18next récupère les traductions d’un fichier translation. On peut cependant le configurer pour que le chargement se fasse depuis plusieurs fichiers appelés namespaces. On peut également définir un namespace de fallback.
i18next.init({ ns: ['app', 'common'], // files to load defaultNS: 'app', // default namespace (needs no prefix on calling t) fallbackNS: 'common' // fallback, can be a string or an array of namespaces}, () => { i18next.t('title') // -> "Un titre" (from app) i18next.t('button.save') // -> "Sauvegarder" (fallback from common)});
- une clé de fallback
Dans l’exemple ici, le code 502 n’est pas défini.
// const error = '404';i18next.t([`error.${error}`, 'error.unspecific'])// -> "The page was not found"// const error = '502';i18next.t([`error.${error}`, 'error.unspecific']) // -> "Something went wrong"
S’il n’y a toujours aucun matching, alors la clé elle-même est retournée.
Détail du mécanisme de fallback : Fallback | i18next documentation
Recommandations
1 – Utilisez l’encodage UTF-8 (Unicode) pour le contenu, la base de données, etc. Déclarez cet encodage. Évitez également l’échappement des caractères, et préférez les vrais caractères sauf exception.
Source : Internationalization Quick Tips for the Web (encodage), Internationalization Quick Tips for the Web (formulaires) et Internationalization Quick Tips for the Web (caractères d’échappement)
2 – Préférez l’utilisation des tags précis mais pas plus spécifiques que cela n’est nécessaire, si aucun apport distinctif n’est apporté.
Exemple : préférer ‘ja‘ à la place de ‘ja-JP‘ : le japonais est parlé par défaut au Japon, donc préciser le japonais du Japon est redondant et inutile (sauf si de manière intentionnelle on cherche le contraste avec un japonais parlé dans une autre partie du monde)
Source : RFC 5646: Tags for Identifying Languages, 1er point
3 – Évitez les précisions de script, sauf si leur ajout permet une distinction importante. C’est d’ailleurs mentionné avec le “Suppress-Script” dans le registre IANA.
Exemple : préférer ‘en-GB‘ à ‘en-Latn-GB‘
Type: languageSubtag: enDescription: EnglishAdded: 2005-10-16Suppress-Script: Latn
Note : cela peut même être dérangeant avec une implémentation de filtrage basique (cf plus haut), car un utilisateur demandant le “language-range” ‘en-GB‘ ne va pas matcher le contenu ‘en-Latn-GB‘, mais seulement ‘en’, et donc recevra du contenu moins adapté pour lui.
Source : RFC 5646: Tags for Identifying Languages , 2e point
4 – Évitez les subtags dépréciés et préférez leurs remplaçants. C’est d’ailleurs mentionné avec le “Preferred-Value” dans le registre IANA.
Exemple : préférez ‘he-FR‘ à la place de ‘iw-FX‘ pour l’hébreux de France métropolitaine : les 2 subtags étant dépréciés (cf le référentiel IANA), ils sont remplacés par leur valeurs préférentielles respectives : ‘he-FR‘.
Type: languageSubtag: iwDescription: HebrewAdded: 2005-10-16Deprecated: 1989-01-01Preferred-Value: he
Type: regionSubtag: FXDescription: Metropolitan FranceAdded: 2005-07-14Deprecated: 1997-07-14Preferred-Value: FR
Source : RFC 5646: Tags for Identifying Languages , 3e point
5 – N’utilisez pas la détection automatique de locale (que ce soit avec l’adresse IP ou avec le Accept-Language) :
- très peu d’utilisateurs changent les paramètres par défaut des navigateurs, la plupart n’en connaissent même pas l’existence
- de manière assez récurrente, il n’y a pas la région mais seulement la langue dans les paramètres par défaut
Exemple : en Belgique, de très nombreux utilisateurs ont leurs navigateurs avec ‘fr‘ et non ‘fr-BE‘
- certains utilisateurs empruntent leur matériel, vont dans des cybercafés et donc par extension, la locale peut ne pas être appropriée
À la place, laissez les utilisateurs finaux définir leurs préférences de langue, par exemple via un menu dédié de sélection de locale.
Source : Accept-Language used for locale setting
Attention, proposer un changement de locale uniquement grâce au navigateur n’est pas recommandé : cela peut permettre d’identifier un utilisateur (“Browser Fingerprinting”).
Enfin, si l’utilisateur n’a pas la possibilité de faire le choix de sa préférence de locale, alors l’en-tête Accept-Language ne doit figurer dans aucune requête car cela n’engendrera qu’une expérience utilisateur de piètre qualité.
Source : RFC 2616: Hypertext Transfer Protocol — HTTP/1.1
6 – N’associez pas les drapeaux aux langues : les drapeaux représentent des pays, pas des langues. Certains pays utilisent la même langue que d’autres, et certains autres pays ont plusieurs langues. Certaines connotations nationalistes peuvent être mal perçues par d’autres pays même s’ils parlent la même langue.
Exemple : Un américain devrait-il choisir l’anglais en cliquant sur le drapeau britannique ? Comment choisir la langue arabe, le drapeau arabe n’existant pas ? Plusieurs langues sont parlées en Inde, et aucun drapeau n’existe pour ces langues, comment faire ?
Source : Indicating the language of a link destination
7 – Évitez les espaces de textes définis et anticipez le fait que le texte peut prendre plus de place ou moins de place que prévu selon la langue. Pensez également à ça pour la limite des champs en base de données.
Source : Text size in translation
8 – Utilisez les balises html uniquement pour la sémantique, et privilégiez les feuilles de style (c’est-à-dire, le css) pour la présentation (cf le paragraphe sur <b> et <i> notamment). Ne découpez pas une même phrase en plusieurs paragraphes <p>.
9 – Les images, icônes, animations, couleurs, exemples sont fortement biaisés culturellement. Il est nécessaire de faire attention à leur utilisation.
Exemples :
– le blanc signifie la pureté dans la culture occidentale, tandis qu’il est le symbole de la mort et du deuil dans les pays d’Asie.
– le symbole ✔️ signifie “correct” dans beaucoup de pays, mais au Japon il se rapproche plus du “incorrect” ❌ puisque le symbole pour “correct” est un rond ⭕
– les gestuelles et langage du corps ( 👍, 👌, 👉, ✋, ✌️) ne veulent absolument pas dire la même chose selon les pays, et peuvent même être insultants
Source : Internationalization Quick Tips for the Web
Conclusion
Vous l’avez vu dans cet article, il y a beaucoup d’informations, mais la quasi-totalité est standardisée par des RFC. Certaines spécificités sont à utiliser uniquement dans des cas d’usages très avancés : la plupart du temps, vous n’en aurez pas besoin. Certaines implémentations (notamment celle des navigateurs) ne sont pas complètes et nécessitent donc quelques ajustements dans votre propre implémentation. Pour aller plus loin, vous pouvez creuser la gestion des nombres, des devises, des dates, du pluriel, etc. Les recommandations que vous avez pu lire vous seront d’ors et déjà utiles et vous éviteront quelques écueils dans le développement de vos applications web. À vous de jouer !

