
Nous avons récemment traduit la version en ligne de 2GIS en arabe, et dans un article précédent j'ai parlé de la théorie nécessaire pour cela - qu'est-ce que dir="rtl"
, par quelles règles un texte à focus mixte est affiché, et comment vous contrôler.
Il est temps de commencer la pratique - de tourner toute l'interface de droite à gauche avec un minimum d'effort pour que même un vrai Arabe ne ressente pas le piège.
Dans cet article, je vais vous dire comment prototyper rapidement quoi faire avec l'assemblage CSS et quelles béquilles décomposer dans JS, noter un peu les fonctionnalités de traduction et de localisation, rappeler les propriétés logiques de CSS et toucher le sujet RTL dans CSS-in-JS.
Styles inversés
Lorsque j'ai appliqué l'attribut dir = "rtl" à la balise, seul l'ordre implicite des éléments a changé - par exemple, l'ordre des cellules du tableau ou des éléments flexibles. Rien ne s'est produit avec les valeurs explicitement spécifiées dans les styles.
Prenez les styles de certaines notifications situées en bas à droite:
.tooltip { position: 'absolute'; bottom: 10px; right: 10px; }
dir="rtl"
n'affectera en aucun cas ces styles - dans la version RTL, l'infobulle sera également à droite, bien qu'elle soit attendue à gauche.
Que faire Vous devez remplacer à right: 10px
par left: 10px
. Et donc avec tous les autres styles. Positionnement absolu, marge / remplissage, alignement du texte - tout doit être tourné dans le sens opposé pour la version arabe.
Prototype rapide
Pour commencer, vous pouvez, sans hésitation, changer toutes les occurrences de gauche à droite et faire un peu de magie avec des valeurs abrégées:
- gauche: 0 → droite: 0
- padding-left: 4px → padding-right: 4px
- marge: 0 16px 0 0 → marge: 0 0 0 16px
Le plugin postcss-rtl est approprié pour cela. Pratique - il vous suffit de le déposer dans la liste de tous les plugins du projet postcss. Il remplace toutes les règles dirigées par des règles en miroir et les enveloppe dans [dir="rtl"]
. Par exemple:
.foo { color: red; margin-left: 16px; } [dir] .foo { color: red; } [dir="ltr"] .foo { margin-left: 16px; } [dir="rtl"] .foo { margin-right: 16px; }
Après cela, il vous suffit de définir dir="rtl"
et seules les règles nécessaires sont automatiquement appliquées. Tout fonctionne et il semble que presque tout soit prêt pour la production, mais cette solution ne convient que pour un prototype rapide:
- la spécificité de chaque règle augmente . Ce ne sera pas nécessairement un problème, mais je voudrais l'éviter;
- ces manipulations donnent lieu à des bugs . Par exemple, l' ordre des propriétés peut casser ;
- la taille du fichier css augmente sensiblement .
[dir]
ajouté à chaque sélecteur, chaque propriété dirigée est dupliquée. Dans notre cas, la taille a augmenté sur un projet de 21%, sur un autre - de 35%:
| taille originale (gzip) | taille bidirectionnelle (gzip) | enflé |
---|
2gis.ru | 272,3 kB | 329,7 kB | 21% |
m.2gis.ru | 24,5 kB | 33,2 kB | 35% |
habr.com | 33,1 kB | 41,4 kB | 25% |
Y a-t-il une meilleure option?
Il est nécessaire de collecter séparément les styles pour LTR et RTL. Ensuite, il ne sera pas nécessaire de toucher les sélecteurs et la taille du CSS ne changera guère.
Pour cela, j'ai choisi:
- RTLCSS - Cette bibliothèque est sous le capot de postcss-rtl.
- webpack-rtl-plugin est une solution clé en main pour les styles construits via ExtractTextPlugin. Le même RTLCSS sous le capot.
Et il a commencé à collecter RTL et LTR dans différents fichiers - styles.css
et styles.rtl.css
. Le seul inconvénient de l'assemblage dans différents fichiers est que vous ne pouvez pas remplacer dir à la volée sans télécharger d'abord le fichier souhaité.
RTLCSS vous permet d'utiliser des directives pour contrôler le traitement de règles spécifiques, par exemple:
.foo { right: 0; } .bar { font-size:16px; }
Quelles autres solutions existe-t-il?
Toutes les solutions existantes ne diffèrent presque pas de RTLCSS.
- css-flip de Twitter;
- cssjanus de Wikimedia;
- Oui, et postcss-rtl prend en charge le paramètre
onlyDirection
, avec lequel vous pouvez collecter des styles pour une seule direction, mais la taille augmente toujours - par exemple, pour mobile 2GIS, elle est de 18% au lieu de 35% (24,5 kB → 29 kB).
Quand faut-il des directives?
Quand les styles ne doivent pas dépendre de l'orientation
Par exemple, l'angle de rotation de la flèche indiquant la direction du vent:

.arrow._nw { transform: rotate(135deg); }
Ou un fondu pour un numéro de téléphone - les numéros sont toujours écrits de gauche à droite, ce qui signifie que le dégradé doit toujours être à droite:


Quand centrer l'icône
Il s'agit d'un cas particulier du paragraphe précédent. Si nous centrons une icône asymétrique par indentation / positionnement, nous déplaçons son bloc sur le côté, et si ce décalage se reflète, l'icône se «déplacera» de l'autre côté:

Il est préférable de centrer l'icône dans svg lui-même dans de telles situations:
Lorsque vous devez isoler un widget entier qui ne devrait pas répondre à RTL
Dans notre cas, c'est une carte. Nous encapsulons tous ses styles lors de l'assemblage dans des directives de bloc: /*rtl:begin:ignore*/ ... /*rtl:end:ignore*/
.
Existe-t-il une option encore meilleure?
La solution avec l'inversion des règles fonctionne bien, mais la question se pose - est-ce une béquille? La dépendance des styles à l'égard de la direction est une tâche naturelle pour le web moderne, et sa pertinence augmente chaque année. Cela aurait dû se refléter dans les normes et approches modernes. Et retrouvé!
Propriétés logiques
Pour adapter la disposition pour différentes orientations, il existe une norme pour les propriétés logiques dans css . Elle concerne non seulement les directions de gauche à droite et de droite à gauche, mais également la direction de haut en bas, mais nous ne la considérerons pas.
Nous utilisons déjà quelque chose de similaire dans les flexions et les grilles - par exemple, flex-start
, flex-end
, grid-row-start
, grid-column-end
déliés de gauche / droite.
Au lieu des concepts left
, right
, top
et bottom
proposé d'utiliser inline-start
, inline-end
, block-start
et block-end
. Au lieu de width
et height
- height
inline-size
block-size
. Et au lieu de raccourcis abcd
- logical adcb
logique (les raccourcis logiques vont dans le sens antihoraire). De plus, pour jumeler les raccourcis existants, de nouvelles versions jumelées apparaissent - padding-block
, margin-inline
, border-color-inline
, etc.
left: 0 → inset-inline-start: 0
padding-left: 4px → padding-inline-start: 4px
margin: 0 16px 0 0 → margin: logical 0 0 0 16px
padding-top: 8px; padding-bottom: 16px → padding-block: 8px 16px
margin-left: 4px; margin-right: 8px → margin-inline: 4px 8px
text-align: right → text-align: end
Et le raccourci tant attendu pour le positionnement apparaît:
left: 4px; right: 8px → inset-inline: 4px 8px
top: 8px; bottom: 16px → inset-block: 8px 16px
top: 0; right: 2px; bottom: 2px; left: 0 → inset: logical 0 0 2px 2px
Ceci est déjà disponible dans Firefox sans drapeaux et dans les navigateurs Webkit basés sur des drapeaux.
Avantages - la solution est native, elle fonctionnera sans assemblage / plugins du tout, si les navigateurs requis sont pris en charge. Il n'y a pas besoin de directives - il suffit d'écrire à left
au lieu de inline-start
, quand vous parlez de «gauche» physique.
Les inconvénients viennent des pros - sans plugins, le code dans la plupart des navigateurs n'est pas valide, vous devez faire beaucoup de travail pour traduire un grand projet existant.
Comment se connecter?
Le moyen le plus simple est la logique postcss . Sans le paramètre dir
, il collecte les styles pour les deux directions de la même manière que postcss-rtl, avec le paramètre dir
donné, uniquement pour la direction spécifiée:
.banner { color: #222222; inset: logical 0 5px 10px; padding-inline: 20px 40px; resize: block; transition: color 200ms; } .banner { color: #222222; top: 0; left: 5px; bottom: 10px; right: 5px; &:dir(ltr) { padding-left: 20px; padding-right: 40px; } &:dir(rtl) { padding-right: 20px; padding-left: 40px; } resize: vertical; transition: color 200ms; } .banner { color: #222222; top: 0; left: 5px; bottom: 10px; right: 5px; padding-left: 20px; padding-right: 40px; resize: vertical; transition: color 200ms; }
Comment convaincre une équipe de commencer à écrire offset-inline-start au lieu de gauche?
Pas question. Mais nous avons décidé de simplifier notre projet - écrivez start: 0
au lieu de offset-inline-start: 0
, dès que tout le monde s'y habituera, je commencerai à imposer une entrée valide :)
RTL + CSS-in-JS = ️️ <3
CSS-in-JS n'a pas besoin d'être assemblé à l'avance. Cela signifie qu'en cours d'exécution, il est possible de déterminer la direction des composants et de choisir ceux à retourner et ceux qui ne le sont pas. Utile si vous devez insérer un widget qui ne prend pas du tout en charge RTL.
En général, la tâche consiste à transformer des objets de type { paddingInlineStart: '4px' }
(ou { paddingLeft: '4px' }
, s'il n'était pas possible de passer aux propriétés logiques) en objets de type { paddingRight: '4px' }
:
- Armement avec bidi-css-js ou rtl-css-js . Ils fournissent une fonction qui prend un objet de style et retourne transformé à l'orientation souhaitée.
- ???
- PROFIT!
Exemple de réaction
Enveloppez chaque composant stylisé dans un HOC qui accepte les styles:
export default withStyles(styles)(Button);
Il prend la direction du composant à partir du contexte et sélectionne les styles finaux:
function withStyles(styles) { const { ltrStyles, rtlStyles } = bidi(styles); return function WithStyles(WrappedComponent) { ... render() { return <WrappedComponent {...this.props} styles={this.context.dir === 'rtl' ? rtlStyles : ltrStyles} />; }; }; ... }; }
Et le fournisseur met l'accent sur le contexte:
<DirectionProvider dir="rtl"> ... <Button /> ...
Airbnb utilise une approche similaire: https://github.com/airbnb/react-with-styles-interface-aphrodite#built-in-rtl-support , si aphrodite est déjà utilisé sur le projet, vous pouvez utiliser cette solution prête à l'emploi.
Pour JSS, c'est encore plus simple - il vous suffit d' activer jss-rtl :
jss.use(rtl());
composants stylisés
const Button = styled.button` background: #222; margin-left: 12px; `;
Et si nous travaillons avec des chaînes de modèle et non avec des objets? Tout est compliqué, mais il existe une solution - calculez le nom de la propriété à partir de la direction spécifiée dans les props
:
const marginStart = props => props.theme.dir === "rtl" ? "margin-left" : "margin-right"; const Button = styled.button` background: #222; ${marginStart}: 12px; `;
Mais il semble plus facile de passer des lignes aux objets, les composants stylés ont pu le faire depuis la version 3.3.0 .
Caractéristiques de la traduction et de la localisation
Nous avons compris la partie technique. Ils ont isolé le contenu d'une orientation indéterminée, mis en miroir les styles, placé les exceptions aux bons endroits, traduit les textes en arabe. Tout semble prêt - lors du changement de langue, toute l'interface apparaît de l'autre côté de l'écran, aucune mise en page n'est en place et tout semble mieux que sur n'importe quel site arabe.
Nous montrons le vrai arabe, et ...
Il s'avère que tous les locuteurs arabes ne savent pas ce qu'est Twitter. Cela s'applique à presque tous les mots en anglais. Pour un tel cas, il existe une translittération arabe: "تويتر".
Il s'avère qu'en arabe, il y a des virgules, et le fait que nous partout dans le code avons été concaténés par «,», en arabe, nous devons concaténer par «،».
Il s'avère que dans certains pays musulmans le calendrier officiel est islamique. C'est lunaire et la formule de traduction habituelle est indispensable.
Il s'avère qu'à Dubaï, il n'y a pas de température négative et le signe plus dans la prévision +40 n'a aucun sens.
Pas seulement des styles repris et reflétés
Si nous faisons dir="auto"
sur l'élément de bloc et que son contenu s'avère être LTR, le texte battra sur le côté gauche du conteneur, même s'il est autour de RTL. Cela peut simplement être résolu en définissant explicitement text-align: right
. Vous pouvez même l'appliquer à la page entière dans la version arabe - la valeur de cette propriété est héritée.
Les icônes ne sont pas non plus automatiquement reflétées. Et sans cela, les icônes directionnelles, telles que les flèches dans la galerie, peuvent regarder dans la mauvaise direction. Imaginez que c'est le seul cas dans lequel les flèches traversant la frontière se justifient!
Une simple transformation aidera à refléter les icônes:
[dir="rtl"] .my-icon { transform: scaleX(-1); }
Certes, cela n'aidera pas si l'icône contient des lettres ou des chiffres. Ensuite, vous devez créer deux icônes différentes et les coller de manière conditionnelle:

Et pourtant, il s'avère que tous les éléments d'interface ne doivent pas être mis en miroir. Par exemple, dans notre cas, nous avons décidé de laisser les cases à cocher normales:


Je ne sais pas comment sélectionner de tels éléments dans toute l'interface. Seul un locuteur natif peut aider ici, qui dira ce qui lui est familier et ce qui ne l’est pas.
Entrée utilisateur
Même si nous contrôlons complètement toutes les données de notre application, elles peuvent être saisies par l'utilisateur. Par exemple, le nom du fichier. Vous pouvez même créer et distribuer le fichier .js en tant que .png - une telle vulnérabilité était dans Telegram :
cool_picture * U + 202E * gnp.js → cool_picture sj.png
Dans de tels cas, il vaut la peine de filtrer les caractères utf inappropriés de la chaîne.
Flipping de scripts
La syntaxe change un peu dans le javascript RTL. La boucle qui ressemblait à ceci:
for (let i = 0; i < arr.length; i++) {
Maintenant, vous devez écrire comme ceci:
for (++i; length.arr > i; let 0 = i) {
Une plaisanterie.
Tout ce que vous devez faire est d'éviter les concepts de «gauche» et de «droite» dans votre code. Par exemple, nous avons rencontré des problèmes dans le calcul des coordonnées du centre de l'écran - avant que toutes les cartes ne soient accrochées à gauche, et maintenant à droite, mais le code d'application ne le savait pas. Tous les calculs et styles en ligne doivent être effectués en tenant compte de la focalisation de base.
Esprit rapide
Dans certaines situations, il est difficile d'implémenter la prise en charge RTL dans certains composants du système. Ensuite, vous devez essayer d'adapter ce composant pour RTL de l'extérieur, mais laissez LTR à l'intérieur.
Par exemple, nous avons un curseur. Il prend uniquement en charge les valeurs ordonnées positives. Elle peut être linéaire et logarithmique (au début, la densité des valeurs est moindre qu'à la fin). Vous devez le refléter tout en conservant le comportement de l'échelle.

Vous pouvez inverser le curseur en utilisant transform: scaleX(-1)
. Ensuite, vous devez inverser le travail avec la souris (clics et glissements) par rapport au centre du curseur. Mauvaise option.
Il existe une autre option - pour faire pivoter l'axe dans l'autre sens, en modifiant uniquement le transfert et la réception des valeurs du curseur. S'il s'agit d'une échelle linéaire, au lieu de l'ensemble de valeurs [10, 100, 1000], nous transférerons l'ensemble [N-1000, N-100, N-10] et dans le gestionnaire, nous le reconvertirons. Pour l'échelle logarithmique, au lieu de l'ensemble [10, 100, 1000], on passe [1/1000, 1/100, 1/10]:
function flipSliderValues(values, scale, isRtl) { if (!isRtl) { return values; } if (scale === 'log') {
C'est ainsi que le curseur a commencé à prendre en charge RTL, bien qu'il ne le sache pas lui-même.
Livre de contes
Contrairement à la composition sous certains IE9, vous n'avez pas besoin d'exécuter un navigateur distinct pour vérifier la composition sous RTL. Vous pouvez même composer et voir la disposition de LTR et RTL simultanément dans une seule fenêtre. Pour ce faire, vous pouvez, par exemple, créer un décorateur dans le livre d'histoires , qui rend deux versions de l'histoire à la fois:

La capture d'écran montre que sans text-overflow: ellipsis
isolement text-overflow: ellipsis
ne se comportent pas comme nous le souhaiterions - il est préférable de le corriger immédiatement.
Il est beaucoup plus facile de prendre en charge RTL immédiatement pendant la mise en page que de tester absolument l'ensemble du projet plus tard.
Problèmes non résolus
La connaissance de la théorie n'aide pas à résoudre absolument tous les problèmes. Je vais donner un exemple.
Un indice qui apparaît dans la direction du texte lors de la frappe. Lorsque nous parlons de saisie multilingue, nous ne pouvons pas savoir à l'avance dans quelle direction afficher cette invite:


Vous devez essayer d'éviter de tels problèmes au stade de la conception et parfois abandonner les solutions évidentes pour LTR et non applicables dans RTL. Dans ce cas, lors de la navigation dans les invites, vous pouvez remplacer le texte entier (comme, par exemple, Yandex ou Google).
Conclusions
RTL, ce n'est pas seulement «tout changer»
Il est nécessaire de prendre en compte les particularités de la langue, vous n'avez pas besoin de retourner quelque chose, vous devez adapter autre chose. Quelque part dans la logique, il est absolument nécessaire d'abandonner le «droit» / «gauche».
Il est très difficile de faire quelque chose sans connaître la langue
Vous penserez que tout est prêt jusqu'à ce que vous montriez votre projet à un vrai locuteur natif. Développer du point de vue d'une personne qui ne connaît aucune langue. Après tout, même des mots aussi évidents pour vous, comme, par exemple, «Twitter», peuvent devoir être traduits. Et il se trouve que les signes de ponctuation ne sont pas les mêmes sur toute la planète.
Recette finale
Il est difficile de décrire dans une seule liste tout ce qui a été discuté dans deux articles. Il n'y aura pas de procédure pas à pas, mais voici la principale chose à faire:
- assurez-vous de trouver un locuteur natif et montrez-lui les prototypes le plus tôt possible;
- Collectez des styles pour LTR et RTL dans différents fichiers. Pour cela, rtlcss et webpack-rtl-plugin conviennent;
- ajouter des exceptions pour tout ce qui n'a pas besoin d'être retourné et refléter clairement ce qui ne s'est pas retourné;
- isoler tout le contenu arbitraire en utilisant
<bdi>
et dir="auto"
; - définir explicitement l'
text-align
sur la page entière; - Évitez
left
/ right
dans le code js lorsque vous voulez dire début et fin.
Préparez-vous à passer le plus de temps sur les moindres détails.
Ce n'est pas difficile d'être préparé à l'avance
Et quelques astuces pour ceux qui ne vont pas encore adapter le site à RTL, mais qui veulent mettre la paille:
- n'utilisez pas la propriété
direction
à d'autres fins; - juste au cas où, isolez toujours tout le contenu arbitraire (et en effet, même dans l'interface anglaise, les utilisateurs peuvent écrire quelque chose en arabe et tout casser);
- si possible, utilisez les propriétés css logiques ;
- vérifier la mise en page non seulement dans différents navigateurs, mais aussi parfois en RTL, du moins par curiosité. Mieux contrôler discrètement la mise en page sous RTL en utilisant des outils comme un livre d'histoires ;
- n'autorisez pas les constructions de langage codé en dur (par exemple, la concaténation de chaînes séparées par des virgules), si possible, configurez tout, y compris les signes de ponctuation. Ceci est utile non seulement pour RTL - par exemple, en grec, le point d'interrogation est «;».
Ces règles ne devraient pas être un problème. Mais si soudain l'envie de lancer la version RTL vient, elle deviendra beaucoup moins chère qu'elle ne le pourrait.