1C de droite à gauche: comment nous avons pris en charge RTL dans la plateforme 1C: Enterprise

Platform 1C: Enterprise est localisé en 22 langues , dont l'anglais, l'allemand, le français, le chinois et le vietnamien. Récemment, dans la version 8.3.17, nous avons pris en charge la langue arabe.



L'une des particularités de la langue arabe est que le texte qu'elle contient est écrit et lu de droite à gauche. L'interface utilisateur de la langue arabe doit être mise en miroir horizontalement (mais pas tout et pas toujours - il y a des subtilités), ouvrez le menu contextuel à gauche du curseur, etc.



Sous la coupe - sur la façon dont nous avons soutenu RTL (de droite à gauche) dans le client Web de la plate-forme 1C: Enterprise, et aussi l'une des hypothèses expliquant pourquoi le monde arabe écrit de droite à gauche.



image



Un peu d'histoire



Nous sommes habitués à écrire de gauche à droite. Cette direction d'écriture est en grande partie générée par le fait que lors de l'écriture de texte sur papier, les droitiers (et selon les statistiques, environ 85% d'entre eux) voient ce qui a déjà été écrit - l'écriture (à droite) ne recouvre pas le texte écrit. Les gauchers doivent souffrir.



Une des hypothèses "pourquoi la langue arabe s'écrit de droite à gauche" ressemble à ceci. Les langues d'origine de l'arabe sont originaires de l'époque où il n'y avait pas de papier et ses analogues (papyrus, parchemin, etc.). Il n'y avait qu'une seule façon d'enregistrer des informations: graver des lettres dans la pierre. Et en quoi sera-t-il plus pratique pour les droitiers de manier un marteau et un ciseau? Bien sûr, tenir un ciseau dans sa main gauche et frapper dessus avec un marteau serré dans sa main droite. Et dans ce cas, il est plus pratique d'écrire de droite à gauche.



Eh bien, maintenant - sur la façon dont nous avons géré cet héritage de siècles.



Comment avons-nous commencé la tâche?



Aucun des développeurs de plate-forme ne parlait arabe et n'avait aucune expérience dans le développement d'interfaces RTL. Nous avons pelleté de nombreux articles sur le thème de RTL (je tiens tout particulièrement à remercier la société 2GIS pour le travail accompli et les articles soigneusement élaborés: article 1 , article 2 ). En étudiant le matériel, j'ai réalisé que nous ne pouvions pas nous passer d'un locuteur natif. Par conséquent, en même temps que la recherche de traducteurs vers l'arabe, nous avons commencé à rechercher un employé - un locuteur natif de la langue arabe, qui aurait l'expérience dont nous avions besoin, pourrait nous conseiller sur les spécificités arabes des interfaces. Après avoir examiné plusieurs candidats, nous avons trouvé une telle personne et nous nous sommes mis au travail.



Jouons avec les polices



Par défaut, nous utilisons la police de la plate-forme Arial, 10pt. Le développeur d'une configuration spécifique peut changer la police de la plupart des éléments de l'interface, mais, comme le montre la pratique, cela est rarement fait. Ceux. dans la plupart des cas, les utilisateurs des programmes 1C voient des inscriptions écrites par Arial sur leurs écrans.



Arial affiche bien 21 langues (y compris le chinois et le vietnamien). Mais, comme il s'est avéré grâce à notre collègue arabe, le texte arabe rendu dans cette police est très petit et difficile à lire:



100%:



image



les utilisateurs arabes ont tendance à travailler avec un DPI accru - 125%, 150%. La situation s'améliore dans ce DPI, mais Arial reste difficile à lire en raison de la nature de la police.



125%:



image



150%:



image



Nous avons examiné plusieurs options pour résoudre ce problème:



  1. Arial , , ( ).
  2. Arial 11pt RTL-.
  3. Arial , LTR- Arial.


Lors du choix d'une solution, nous devions prendre en compte le fait que la police Arial 10pt était utilisée dans la plate-forme 1C: Enterprise depuis très longtemps, sur la plate-forme que nous et nos partenaires avons créé plus de 1300 solutions d'édition, et dans toutes, la police Arial 10pt s'est bien montrée sur tous les systèmes d'exploitation pris en charge (Windows, Linux et macOS de différentes versions), ainsi que dans les navigateurs. Changer la police et / ou sa taille signifierait des tests massifs de l'interface utilisateur, et la plupart de ces tests ne peuvent pas être automatisés. Changer la police signifierait également changer l'interface familière des programmes pour les utilisateurs actuels.



De plus, nous n'avons pas pu trouver une police universelle qui représenterait bien toutes les langues, y compris l'arabe. Par exemple, la police Segoe UI rend bien l'arabe même à 10 pt, mais elle ne prend pas en charge le chinois et n'est pas non plus prise en charge dans certains systèmes d'exploitation. Tahoma est bon pour rendre le texte arabe à 10 pt, mais a des problèmes avec le support Linux et "trop ​​gras" latin / cyrillique pour gras (le gras arabe semble bon). Etc.



Augmenter la taille de police par défaut à 11pt dans l'interface RTL signifierait une quantité sérieuse de tests d'interface utilisateur - nous devons nous assurer que tout est rendu correctement, toutes les étiquettes sont placées dans l'espace prévu pour elles, etc. Et même à 11 pt, Arial n'affiche pas parfaitement les caractères arabes.



En conséquence, la troisième voie s'est avérée optimale en termes de coûts de main-d'œuvre et d'effet obtenu: nous continuons à utiliser Arial pour tous les caractères sauf l'arabe. Et pour les caractères arabes, nous utilisons une police bien adaptée pour cela - Almarai . Pour ce faire, ajoutez au CSS:



@font-face {
  font-family: 'Almarai';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('Almarai'), 
       local('Almarai-Regular'),
       url(https://fonts.gstatic.com/s/almarai/v2/tsstApxBaigK_hnnQ1iFo0C3.woff2) 
            format('woff2');
  unicode-range: 
       U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC;
}


puis partout où vous avez besoin d'utiliser la police par défaut, définissez la police de cette manière:



font-family: 'Almarai', Arial, sans-serif;


La beauté de cette approche est que s'il n'y a pas un seul caractère dans l'interface qui tombe dans la plage unicode, alors cette police ne se chargera même pas. Mais dès qu'un tel symbole apparaît, le navigateur télécharge la police elle-même (ou utilise sa version locale) et affiche le symbole dans la police souhaitée.



Interface "Flip"



Comme vous vous en doutez, la mise en page HTML du client Web n'était pas prête pour un retournement. Après avoir fait la première étape, en définissant l'attribut dir = "rtl" sur l'élément racine et en ajoutant le style html [dir = rtl] {text-align: right;} , nous avons commencé le travail minutieux. Au cours de ce travail, nous avons développé un certain nombre de pratiques que nous souhaitons partager ici.



Symétrie



Regardons l'exemple des boutons. Les boutons de la plate-forme peuvent contenir une image, du texte et un marqueur de liste déroulante. Et tout cela dans n'importe quelle composition à la discrétion des développeurs de solutions applicatives basées sur la plateforme.



La colonne «avant RTL» représente graphiquement le remplissage initial des éléments de bouton. La dépendance du nombre de retraits sur la présence d'éléments dans le bouton, ainsi que sur la séquence de leur disposition, est évidente. S'il y a une image, le texte n'a pas besoin d'un retrait à gauche, si l'image est à droite, alors l'image a un décalage négatif, s'il y a un marqueur dans la liste déroulante, le conteneur avec le texte a plus de retrait à droite, si le marqueur est immédiatement après l'image, il a toujours un retrait à droite. Trop de ifs, sauf pour un bouton contenant uniquement du texte avec un remplissage symétrique. Symétrique! Si vous distribuez les retraits symétriquement, il n'y a rien à retourner. C'est devenu l'idée principale.



La colonne "après RTL" affiche les nouveaux retraits symétriques sur les mêmes boutons. Il reste à résoudre la nuance avec l'indentation entre l'image et le marqueur de liste. Je voulais une solution universelle pour toute orientation. Le triangle lui-même est dessiné avec la bordure supérieure sur le pseudo-élément, et il n'a besoin d'un retrait que s'il se trouve après l'image. Dans cette condition, un autre pseudo-élément est ajouté avec la largeur de l'indentation requise. Le triangle et le remplissage se permutent eux-mêmes lorsque l'orientation est modifiée.



image



Remarque. Tous les exemples ci-dessous sont par défaut pour l'interface LTR. Pour voir à quoi ressemble l'exemple dans l'interface RTL, remplacez dir = "ltr" par dir = "rtl".



<!DOCTYPE html>
<html dir="ltr">
<head>
<style>
.button {
    display: inline-flex;
    align-items: center;
    border: 1px solid #A0A0A0;
    border-radius: 3px;
    height: 26px;
    padding: 0 8px;
}
.buttonImg {
    background: #A0A0A0;
    width: 16px;
    height: 16px;
}
.buttonBox {
    margin: 0 8px;
}
.buttonDrop {
    display: flex;
}
.buttonDrop:after {
    content: '';
    display: block;
    border-width: 3px 3px 0;
    border-style: solid;
    border-left-color: transparent;
    border-right-color: transparent;
}
.buttonImg + .buttonDrop::before {
    content: '';
    display: block;
    width: 8px;
    overflow: hidden;
}
</style>
</head>
<body>
<a class="button">
    <span class="buttonImg"></span>
    <span class="buttonBox"></span>
    <span class="buttonDrop"></span>
</a>
<a class="button">
    <span class="buttonImg"></span>
    <span class="buttonDrop"></span>
</a>
</body>
</html>


Nous essayons d'éviter les éléments inutiles, les pseudo-éléments et les wrappers. Mais, en faisant un choix dans ce cas entre augmenter les conditions en CSS et ajouter un pseudo-élément, la solution avec un pseudo-élément a gagné en raison de sa polyvalence. Il n'y a pas beaucoup de ces boutons sur le formulaire, donc les performances lors de l'ajout d'éléments ne souffriront pas même dans Internet Explorer.



Le principe de symétrie s'est également avéré utile pour faire défiler nos panneaux. Pour déplacer le contenu horizontalement, nous avons précédemment appliqué une seule propriété margin-left: -Npx; ...



image



La valeur est maintenant définie sur la marge symétrique : 0 -Npx; , c'est à dire. pour gauche et droite à la fois, et où se déplacer - le navigateur lui-même sait, en fonction de la direction spécifiée.



Classes atomiques



L'une des capacités de notre plateforme est la possibilité de modifier dynamiquement le contenu et son emplacement sur le formulaire «à la volée» selon le goût de chaque utilisateur. Un cas courant de modifications est l'alignement horizontal du texte: à gauche, à droite ou au centre. Ceci est réalisé en alignant simplement le texte sur la valeur appropriée. Une inversion pour RTL signifierait étendre les conditions dans les scripts et les styles pour chaque champ et pour chaque cas de son positionnement. La solution minimum coûte 4 lignes:



.taStart {
    text-align: left;
} 
html[dir=rtl] .taStart {
    text-align: right;
}
.taEnd {
    text-align: right;
}
html[dir=rtl] .taEnd {
    text-align: left;
}


Ainsi, aux endroits nécessaires, la classe est installée avec l'alignement nécessaire et son remplacement facile si nécessaire. Il ne reste plus qu'à remplacer le paramètre d'alignement par style = "text-align: ..." par la classe appropriée.



Le même principe est utilisé pour définir un autre type d'alignement - float .



.floatStart {
    float: left;
} 
html[dir=rtl] .floatStart {
    float: right;
}
.floatEnd {
    float: right;
}
html[dir=rtl] .floatEnd {
    float: left;
}


Et, comme sans elle, une classe pour la mise en miroir, par exemple, des icônes, qui est également installée dans tous les conteneurs nécessitant une mise en miroir dans l'interface RTL.



html[dir=rtl] .rtlScale {
    transform: scaleX(-1);
}


Antiscale



Après avoir traité les éléments linéaires «simples», il est temps de passer aux éléments «complexes». Il y en a aussi sur notre plate-forme, par exemple des interrupteurs à bascule. Ils peuvent être de différentes formes géométriques. Le navigateur a fait face à la disposition des éléments, les retraits de nos interrupteurs à bascule sont initialement symétriques. Donc quel est le problème? Le problème réside dans l'arrondissement des cadres.

L'arrondi des cadres est calculé pour chaque élément de l'interrupteur à bascule, en fonction de sa position. "En haut à gauche", "en haut à droite", "en haut à droite et en bas à droite" - les variations sont différentes.



Vous pouvez retourner le conteneur entier avec l'interrupteur à bascule, mais qu'en est-il du texte qui retourne également? Nous avons appelé cette technique "anti-calcaire" . Ajoutez la classe atomique rtlScale au conteneur qui doit être mis en miroiret ajoutez la propriété transform à son élément enfant : inherit; ... Dans l'interface LTR, cette méthode sera ignorée, mais pour l'interface RTL, le texte, retourné deux fois, sera affiché selon les besoins.



image



<!DOCTYPE html>
<html dir="ltr">
<head>
<style>
html[dir=rtl] .rtlScale {
    transform: scaleX(-1);
}
.tumbler {
    display: inline-flex;
    border-radius: 4px 0 0 4px;
    border: 1px solid #A0A0A0;
    padding: 4px 8px;
}
.tumblerBox {
    transform: inherit;
}
</style>
</head>
<body>
<div class="tumbler rtlScale">
    <div class="tumblerBox"> </div>
</div>
</body>
</html>


Flexbox



Bien sûr, malheureusement, nous n'avons pas mis au point cette technologie incroyable, mais avec grand plaisir nous avons utilisé ses capacités à nos fins. Par exemple, dans le panneau de coupe. Les boutons de défilement de ce panneau ne prennent pas de place; ils apparaissent en haut du panneau lorsqu'il est possible de faire défiler dans un sens ou dans l'autre. Une implémentation assez logique de la position: absolue; droite / gauche: 0; s'est avérée non universelle, nous l'avons donc abandonnée. En conséquence, la solution universelle a commencé à ressembler à ceci: définissez le conteneur parent du bouton de défilement sur une largeur zéro afin qu'il ne prenne pas d'espace, et l'orientation du bouton de défilement situé à la fin a été modifiée via flex-direction: row-reverse; ...



image



Ainsi, le bouton à la fin de la ligne est pressé contre l'extrémité de la ligne du conteneur de largeur nulle et est affiché «à l'envers» sur le panneau.



<!DOCTYPE html>
<html dir="ltr">
<head>
<style>
.panel {
    display: inline-flex;
    background: #fbed9e;
    height: 64px;
    width: 250px;
}
.content {
    width: 100%;
}
.scroll {
    display: flex;
    position: relative; 
    width: 0; 
}
.scrollBack {
    order: -1; 
}
.scrollNext {
    flex-direction: row-reverse; 
}
.scroll div {
    display: flex; 
    flex: 0 0 auto; 
    justify-content: center; 
    align-items: center; 
    background: rgba(255,255,255,0.5); 
    width: 75px; 
}
</style>
</head>
<body>
<div class="panel">
    <div class="content"> </div>
    <div class="scroll scrollBack">
        <div></div>
    </div>
    <div class="scroll scrollNext">
        <div></div>
    </div>
</div>
</body>
</html>


À propos, l'idée de largeur nulle s'est avérée également utile pour résoudre d'autres problèmes. Les éléments déroulants (menus contextuels, listes déroulantes, etc.) sont largement utilisés dans la plateforme. Le calcul de positionnement est complexe et subtil, donc la mise en miroir nécessite encore plus de complexité et de subtilité.



image



La solution consiste à placer la liste déroulante dans un conteneur de taille zéro (appelé ancre). L'ancre est positionnée absolument au point requis de l'interface, et son contenu avec son bord de départ est pressé contre le bord de départ de l'ancre, positionnant le contenu dans la direction souhaitée.



<!DOCTYPE html>
<html dir="ltr">
<head>
<style>
.anchor {
    border: 1px solid red; 
    position: absolute; 
    width: 100px; 
    height: 50px; 
    max-width: 0; 
    max-height: 0; 
    top: 25%;
    left: 50%;
}
.anchorContent {
    background: #FFF; 
    border: 1px solid #A0A0A0; 
    width: inherit; 
    height: inherit; 
    padding: 4px 8px; 
}
</style>
</head>
<body>
<div class="anchor">
    <div class="anchorContent"> </div>
</div>
</body>
</html>


Éléments absolument positionnés



Là où le positionnement absolu des éléments ne peut être évité ( style = ”position: absolue;” ou style = ”position: fixe;” ), dir = ”rtl” est impuissant. Une approche vient à la rescousse lorsque la coordonnée horizontale est appliquée non pas au style gauche , mais au style droit .



image



De plus, si dans JS, lors du calcul des coordonnées, on fait appel aux propriétés scrollLeft et offsetLeft des éléments, alors dans l'interface RTL, l'utilisation directe de ces propriétés peut entraîner des conséquences inattendues. Vous devez calculer la valeur de ces propriétés d'une manière différente. L'implémentation de cette fonctionnalité dans la Google Closure Library, que nous utilisons dans le client Web, a fait ses preuves: voir.https://github.com/google/closure-library/blob/master/closure/goog/style/bidi.js .



Finalement



Nous l'avons fait! Nous avons retourné et enregistré notre code source en une seule version pour les interfaces LTR et RTL. Le besoin n'est pas encore apparu, mais si vous le souhaitez, nous pouvons afficher simultanément deux formes de directions différentes sur une page. Et en passant, en utilisant nos astuces, nous nous sommes retrouvés avec un fichier CSS 25% plus léger.



Nous avons également pris en charge RTL dans un client léger (natif) 1C qui fonctionne sur Windows, Linux et macOS, mais ceci est un sujet pour un article séparé.



All Articles