Von rechts nach links. So drehen Sie die Site-Schnittstelle unter RTL

Bild


Wir haben kürzlich die Online-Version von 2GIS ins Arabische übersetzt, und in einem früheren Artikel habe ich über die dafür notwendige Theorie gesprochen - was ist dir="rtl" , nach welchen Regeln wird ein Text mit gemischtem Fokus angezeigt und wie können Sie sich selbst kontrollieren.


Es ist Zeit, mit dem Üben zu beginnen - die gesamte Benutzeroberfläche mit minimalem Aufwand von rechts nach links zu drehen, damit selbst ein echter Araber den Haken nicht spürt.


In diesem Artikel werde ich Ihnen erklären, wie Sie schnell Prototypen erstellen können, was mit CSS-Assembly zu tun ist und welche Krücken in JS zerlegt werden sollen, ein wenig über Übersetzungs- und Lokalisierungsfunktionen, eine Erinnerung an die logischen Eigenschaften von CSS und das Thema RTL in CSS-in-JS.


Stile umdrehen


Wenn ich das Attribut dir = "rtl" auf das Tag angewendet habe, hat sich nur die implizite Reihenfolge der Elemente geändert - zum Beispiel die Reihenfolge der Tabellenzellen oder Flex-Elemente. Mit den in den Stilen explizit angegebenen Werten ist nichts passiert.

Nehmen Sie die Stile einer Benachrichtigung unten rechts:


 .tooltip { position: 'absolute'; bottom: 10px; right: 10px; } 

dir="rtl" in keiner Weise auf diese Stile aus. In der RTL-Version befindet sich der Tooltip ebenfalls auf der rechten Seite, obwohl dies auf der linken Seite erwartet wird.


Was zu tun ist? Sie müssen right: 10px durch left: 10px . Und so auch bei allen anderen Stilen. Absolute Positionierung, Rand / Abstand, Textausrichtung - für die arabische Version muss alles in die entgegengesetzte Richtung gedreht werden.


Schneller Prototyp


Zunächst können Sie ohne zu zögern alle Vorkommen von links nach rechts ändern und mit Kurzformwerten ein wenig zaubern:


  • links: 0 → rechts: 0
  • links auffüllen: 4px → rechts auffüllen: 4px
  • Rand: 0 16px 0 0 → Rand: 0 0 0 16px

Das postcss-rtl Plugin ist dafür geeignet. Praktisch - Sie müssen es nur in die Liste aller Postcss-Projekt-Plugins einfügen. Es ersetzt alle gerichteten Regeln durch gespiegelte und schließt sie in [dir="rtl"] . Zum Beispiel:


 /* input */ .foo { color: red; margin-left: 16px; } /* output */ [dir] .foo { color: red; } [dir="ltr"] .foo { margin-left: 16px; } [dir="rtl"] .foo { margin-right: 16px; } 

Danach müssen Sie nur noch dir="rtl" und nur die erforderlichen Regeln werden automatisch angewendet. Alles funktioniert und es scheint, dass fast alles für die Produktion bereit ist, aber diese Lösung ist nur für einen schnellen Prototyp geeignet:


  • Die Spezifität jeder Regel nimmt zu . Dies wird nicht unbedingt ein Problem sein, aber ich möchte es vermeiden;
  • Solche Manipulationen führen zu Fehlern . Beispielsweise kann die Reihenfolge der Eigenschaften unterbrochen werden .
  • Die Größe der CSS-Datei nimmt spürbar zu . [dir] zu jedem Selektor hinzugefügt, jede gerichtete Eigenschaft wird dupliziert. In unserem Fall hat sich die Größe bei einem Projekt um 21% erhöht, bei einem anderen um 35%:

Originalgröße (gzip)bidirektionale Größe (gzip)angeschwollen
2gis.ru272,3 kB329,7 kB21%
m.2gis.ru24,5 kB33,2 kB35%
habr.com33,1 kB41,4 kB25%

Gibt es eine bessere Option?


Es ist notwendig, Stile für LTR und RTL getrennt zu sammeln. Dann müssen die Selektoren nicht berührt werden, und die Größe des CSS ändert sich kaum.


Dafür habe ich gewählt:


  1. RTLCSS - Diese Bibliothek befindet sich unter der Haube von postcss-rtl.
  2. Das webpack-rtl-plugin ist eine schlüsselfertige Lösung für Stile, die mit ExtractTextPlugin erstellt wurden. Das gleiche RTLCSS unter der Haube.

Und er fing an, RTL und LTR in verschiedenen Dateien zu sammeln - styles.css und styles.rtl.css . Das einzige Minus beim Zusammenstellen zu verschiedenen Dateien ist, dass Sie dir nicht im laufenden Betrieb ersetzen können, ohne zuvor die gewünschte Datei heruntergeladen zu haben.


Mit RTLCSS können Sie mithilfe von Anweisungen die Verarbeitung bestimmter Regeln steuern, z. B.:


 .foo { /*rtl:ignore*/ right: 0; } .bar { font-size:16px/*rtl:14px*/; } 

Welche anderen Lösungen gibt es?


Alle vorhandenen Lösungen unterscheiden sich kaum von RTLCSS.


  • CSS-Flip von Twitter;
  • cssjanus aus Wikimedia;
  • Ja, und postcss-rtl unterstützt den Parameter onlyDirection , mit dem Sie Stile für nur eine Richtung erfassen können, aber die Größe wächst weiter - für mobiles 2GIS beträgt sie beispielsweise 18% anstelle von 35% (24,5 kB → 29 kB).

Wann werden Richtlinien benötigt?


Wann Stile nicht von der Ausrichtung abhängen sollten


Zum Beispiel der Drehwinkel des Pfeils, der die Windrichtung angibt:


Bild
 .arrow._nw { /*rtl:ignore*/ transform: rotate(135deg); } 



Oder eine Überblendung für eine Telefonnummer - Nummern werden immer von links nach rechts geschrieben, was bedeutet, dass der Farbverlauf immer rechts sein sollte:


Bild


Bild


Wann soll das Symbol zentriert werden?


Dies ist ein Sonderfall des vorherigen Absatzes. Wenn wir das asymmetrische Symbol durch die Einrückung / Positionierung zentrieren, verschieben wir seinen Block zur Seite, und wenn er den Versatz widerspiegelt, „bewegt“ sich das Symbol auf die andere Seite:


Bild


In solchen Situationen ist es besser, das Symbol in svg selbst zu zentrieren:



Wenn Sie ein gesamtes Widget isolieren müssen, das nicht auf RTL reagieren soll


In unserem Fall ist dies eine Karte. Wir setzen alle Stile beim Zusammenstellen in Blockanweisungen um: /*rtl:begin:ignore*/ ... /*rtl:end:ignore*/ .


Gibt es eine noch bessere Option?


Die Lösung mit der Umkehrung der Regeln funktioniert gut, aber die Frage stellt sich - ist es eine Krücke? Die Abhängigkeit von Stilen von der Richtung ist eine natürliche Aufgabe für das moderne Web, und ihre Relevanz wächst von Jahr zu Jahr. Dies hätte sich in modernen Standards und Ansätzen widerspiegeln müssen. Und gefunden!


Logische Eigenschaften


Um das Layout für unterschiedliche Ausrichtungen anzupassen, gibt es in CSS einen Standard für logische Eigenschaften . Es geht nicht nur um die Richtungen von links nach rechts und von rechts nach links, sondern auch um die Richtung von oben nach unten, aber wir werden es nicht berücksichtigen.


Wir verwenden bereits etwas Ähnliches in Flexes und Grids - zum Beispiel werden flex-start , flex-end , grid-row-start und grid-column-end von links / rechts gelöst.


Anstelle der Konzepte left , right , top und bottom vorgeschlagen, inline-start , inline-end , block-start und block-end . Anstelle von width und height - inline-size und block-size . Und anstelle von Shortcands abcd - logical adcb (logische Shortdes gehen gegen den Uhrzeigersinn). Außerdem werden zum Pairing vorhandener Shortends neue gepaarte Versionen angezeigt - padding-block , margin-inline , border-color-inline usw.


  • 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

Und die lang erwartete Abkürzung für die Positionierung erscheint:


  • 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

Dies ist bereits in Firefox ohne Flags und in Flag-basierten Webkit-Browsern verfügbar.


Vorteile - Die Lösung ist nativ und funktioniert ohne Assembly / Plugins, wenn die erforderlichen Browser unterstützt werden. Es sind keine Anweisungen erforderlich - schreiben Sie einfach left statt inline-start , wenn Sie die physische "links" meinen.


Nachteile kommen von den Profis - ohne Plugins ist der Code in den meisten Browsern ungültig, Sie müssen viel Arbeit leisten, um ein großes vorhandenes Projekt zu übersetzen.


Wie verbinde ich mich?


Der einfachste Weg ist postcss-logisch . Ohne den Parameter dir werden Stile für beide Richtungen auf dieselbe Weise wie postcss-rtl mit dem angegebenen Parameter dir nur für die angegebene Richtung erfasst:


 .banner { color: #222222; inset: logical 0 5px 10px; padding-inline: 20px 40px; resize: block; transition: color 200ms; } /* becomes */ .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; } /* or, when used with { dir: 'ltr' } */ .banner { color: #222222; top: 0; left: 5px; bottom: 10px; right: 5px; padding-left: 20px; padding-right: 40px; resize: vertical; transition: color 200ms; } 

Wie kann man ein Team davon überzeugen, Offset-Inline-Start statt Links zu schreiben?


Auf keinen Fall. Aber wir haben uns für unser Projekt entschieden, um es zu vereinfachen - schreibe start: 0 statt offset-inline-start: 0 , sobald sich alle daran gewöhnt haben, werde ich einen gültigen Eintrag auferlegen :)


RTL + CSS-in-JS = ️️ <3


CSS-in-JS muss nicht im Voraus zusammengestellt werden. Dies bedeutet, dass es zur Laufzeit möglich ist, die Richtung der Komponenten zu bestimmen und auszuwählen, welche umgedreht werden sollen und welche nicht. Nützlich, wenn Sie ein Widget einfügen müssen, das RTL überhaupt nicht unterstützt.


Im Allgemeinen besteht die Aufgabe darin, Objekte vom Typ { paddingInlineStart: '4px' } (oder { paddingLeft: '4px' } , wenn es nicht möglich war, zu logischen Eigenschaften zu wechseln) in Objekte vom Typ { paddingRight: '4px' } :


  1. Bewaffnung mit bidi-css-js oder rtl-css-js . Sie bieten eine Funktion, die ein Stilobjekt übernimmt und in die gewünschte Ausrichtung transformiert zurückgibt.
  2. ???
  3. GEWINN!

Reaktionsbeispiel


Wickeln Sie jede gestaltete Komponente in ein HOC ein , das Stile akzeptiert:


 export default withStyles(styles)(Button); 

Er nimmt die Richtung der Komponente aus dem Kontext und wählt die endgültigen Stile aus:


 function withStyles(styles) { const { ltrStyles, rtlStyles } = bidi(styles); return function WithStyles(WrappedComponent) { ... render() { return <WrappedComponent {...this.props} styles={this.context.dir === 'rtl' ? rtlStyles : ltrStyles} />; }; }; ... }; } 

Und der Anbieter konzentriert sich auf den Kontext:


 <DirectionProvider dir="rtl"> ... <Button /> ... 

Airbnb verwendet einen ähnlichen Ansatz: https://github.com/airbnb/react-with-styles-interface-aphrodite#built-in-rtl-support Wenn Aphrodite bereits für das Projekt verwendet wird, können Sie diese vorgefertigte Lösung verwenden.


Für JSS ist es noch einfacher - Sie müssen nur jss-rtl aktivieren :


 jss.use(rtl()); 

gestylte Komponenten


 const Button = styled.button` background: #222; margin-left: 12px; `; 

Was ist, wenn wir mit Vorlagenzeichenfolgen und nicht mit Objekten arbeiten? Alles ist kompliziert, aber es gibt eine Lösung - berechnen Sie den Namen der Eigenschaft aus der in den props angegebenen Richtung:


 const marginStart = props => props.theme.dir === "rtl" ? "margin-left" : "margin-right"; const Button = styled.button` background: #222; ${marginStart}: 12px; `; 

Es scheint jedoch einfacher zu sein, von Linien zu Objekten zu wechseln. Gestaltete Komponenten können dies seit Version 3.3.0 .


Merkmale der Übersetzung und Lokalisierung


Wir haben den technischen Teil herausgefunden. Sie isolierten Inhalte einer unbestimmten Ausrichtung, spiegelten Stile, platzierten Ausnahmen an den richtigen Stellen und übersetzten die Texte ins Arabische. Alles scheint fertig zu sein - beim Umschalten der Sprache erscheint die gesamte Benutzeroberfläche auf der anderen Seite des Bildschirms, es ist kein Layout vorhanden und alles sieht besser aus als auf jeder arabischen Website.


Wir zeigen den echten Araber und ...


Es stellt sich heraus, dass nicht jeder arabische Sprecher weiß, was Twitter ist. Dies gilt für fast alle englischen Wörter. Für einen solchen Fall gibt es eine arabische Transliteration: "تويتر".


Es stellt sich heraus, dass es auf Arabisch Kommas gibt und dass wir überall im Code durch "," verkettet wurden, auf Arabisch müssen wir durch "،" verkettet werden.


Es stellt sich heraus, dass in einigen muslimischen Ländern der offizielle Kalender islamisch ist. Es ist Mond und die übliche Übersetzungsformel ist unverzichtbar.


Es stellt sich heraus, dass es in Dubai keine negative Temperatur gibt und das Pluszeichen in der + 40-Prognose keinen Sinn ergibt.


Nicht nur aufgegriffene und gespiegelte Stile


Wenn wir dir="auto" für das Blockelement ausführen und sein Inhalt sich als LTR herausstellt, wird der Text auf der linken Seite des Containers angezeigt, selbst wenn er sich in der Nähe von RTL befindet. Dies kann einfach behoben werden, indem Textausrichtung explizit festgelegt wird text-align: right . Sie können dies sogar auf die gesamte Seite in der arabischen Version anwenden - der Wert dieser Eigenschaft wird vererbt.


Symbole werden auch nicht automatisch gespiegelt. Und ohne dies können Richtungssymbole wie Pfeile in der Galerie in die falsche Richtung schauen. Stellen Sie sich vor, dies ist der einzige Fall, in dem sich durch die Grenze gemachte Pfeile rechtfertigen!


Eine einfache Transformation hilft dabei, die Symbole wiederzugeben:


 [dir="rtl"] .my-icon { transform: scaleX(-1); } 

Richtig, es hilft nicht, wenn das Symbol Buchstaben oder Zahlen enthält. Dann müssen Sie zwei verschiedene Symbole erstellen und bedingt einfügen:


Bild


Es stellt sich jedoch heraus, dass nicht alle Schnittstellenelemente gespiegelt werden müssen. In unserem Fall haben wir beispielsweise beschlossen, die Kontrollkästchen normal zu lassen:


Bild


Bild


Ich weiß nicht, wie ich solche Elemente aus der gesamten Benutzeroberfläche auswählen soll. Hier kann nur ein Muttersprachler helfen, der sagt, was ihm vertraut ist und was nicht.


Benutzereingabe


Selbst wenn wir alle Daten unserer Anwendung vollständig kontrollieren, kann dies eine Benutzereingabe sein. Zum Beispiel der Name der Datei. Sie können die .js-Datei sogar als .png erfinden und ausgeben - eine solche Sicherheitsanfälligkeit gab es in Telegram :


cool_picture * U + 202E * gnp.js → cool_picture sj.png

In solchen Fällen lohnt es sich, unangemessene utf-Zeichen aus der Zeichenfolge herauszufiltern.


Skripte umdrehen


Die Syntax ändert sich im RTL-Javascript etwas. Die Schleife, die so aussah:


 for (let i = 0; i < arr.length; i++) { 

Jetzt müssen Sie so schreiben:


 for (++i; length.arr > i; let 0 = i) { 

Ein Witz.


Alles, was Sie tun müssen, ist, die Konzepte von "links" und "rechts" in Ihrem Code zu vermeiden. Zum Beispiel hatten wir Probleme bei der Berechnung der Koordinaten in der Mitte des Bildschirms - bevor alle Karten links und jetzt rechts hingen, aber der Anwendungscode nichts davon wusste. Alle Berechnungen und Inline-Stile müssen unter Berücksichtigung des Grundfokus durchgeführt werden.


Schneller Witz


In einigen Situationen ist es schwierig, die RTL-Unterstützung in einer Komponente des Systems zu implementieren. Dann müssen Sie versuchen, diese Komponente von außen für RTL anzupassen, aber LTR innen lassen.


Zum Beispiel haben wir einen Schieberegler. Es werden nur positiv geordnete Werte unterstützt. Sie kann linear und logarithmisch sein (am Anfang ist die Wertedichte geringer als am Ende). Sie müssen es reflektieren, während Sie das Verhalten der Waage beibehalten.


Bild


Sie können den Schieberegler mit transform: scaleX(-1) . Dann müssen Sie die Arbeit mit der Maus (Klicken und Ziehen) relativ zur Mitte des Schiebereglers umkehren. Schlechte Option.


Es gibt noch eine andere Option: Sie können die Achse in die andere Richtung drehen und nur die Übertragung und den Empfang von Werten vom Schieberegler ändern. Wenn dies eine lineare Skala ist, übertragen wir anstelle des Wertesatzes [10, 100, 1000] den Satz [N-1000, N-100, N-10] und konvertieren ihn im Handler zurück. Für die logarithmische Skala übergeben wir anstelle der Menge [10, 100, 1000] [1/1000, 1/100, 1/10]:


 function flipSliderValues(values, scale, isRtl) { if (!isRtl) { return values; } if (scale === 'log') { // [A, B] --> [1/B, 1/A] return values.map(x => 1 / x).reverse(); } // [A, B] --> [MAX-B, MAX-A] return values.map(x => Number.MAX_SAFE_INTEGER - x).reverse(); }; 

Auf diese Weise begann der Schieberegler, RTL zu unterstützen, obwohl er selbst nichts davon weiß.


Märchenbuch


Im Gegensatz zum Satz unter einigen IE9 müssen Sie keinen separaten Browser ausführen, um den Satz unter RTL zu überprüfen. Sie können sogar das Layout von LTR und RTL gleichzeitig in einem Fenster setzen und anzeigen. Dazu können Sie beispielsweise einen Dekorateur im Storybook erstellen , der zwei Versionen der Story gleichzeitig wiedergibt:


Bild


Der Screenshot zeigt, dass ohne Isolation text-overflow: ellipsis sich nicht so verhalten, wie wir es möchten - es ist besser, sie sofort zu beheben.


Es ist viel einfacher, RTL sofort während des Layouts zu unterstützen, als das gesamte Projekt später absolut zu testen.


Ungelöste Probleme


Die Kenntnis der Theorie hilft nicht, absolut alle Probleme zu lösen. Ich werde ein Beispiel geben.


Ein Hinweis, der während der Eingabe in Richtung des Textes angezeigt wird. Wenn wir über mehrsprachige Eingaben sprechen, können wir nicht im Voraus wissen, in welche Richtung diese Eingabeaufforderung angezeigt werden soll:




Sie müssen versuchen, solche Probleme in der Entwurfsphase zu vermeiden, und manchmal Lösungen aufgeben, die für LTR offensichtlich sind und in RTL nicht anwendbar sind. In diesem Fall können Sie beim Navigieren in den Eingabeaufforderungen den gesamten Text ersetzen (wie dies beispielsweise bei Yandex oder Google der Fall ist).


Schlussfolgerungen


RTL ist nicht nur "alles umdrehen"


Es ist notwendig, die Besonderheiten der Sprache zu berücksichtigen, Sie müssen nichts umdrehen, Sie müssen etwas anderes anpassen. Irgendwo in der Logik ist es absolut notwendig, das „Recht“ / „Links“ aufzugeben.


Es ist sehr schwierig, etwas zu tun, ohne die Sprache zu kennen


Sie werden denken, dass alles fertig ist, bis Sie Ihr Projekt einem echten Muttersprachler zeigen. Entwickeln Sie sich aus der Sicht einer Person, die keine Sprache kennt. Schließlich müssen möglicherweise auch solche für Sie offensichtlichen Wörter wie beispielsweise „Twitter“ übersetzt werden. Und die Satzzeichen sind, wie sich herausstellt, nicht auf dem ganzen Planeten gleich.


Endrezept


Es ist schwierig, in einer Liste alles zu beschreiben, was in zwei Artikeln besprochen wurde. Es wird keine Komplettlösung geben, aber hier ist die Hauptsache:


  • Achten Sie darauf, einen Muttersprachler zu finden und ihm die Prototypen so früh wie möglich zu zeigen.
  • Sammeln Sie Stile für LTR und RTL in verschiedenen Dateien. Hierfür sind rtlcss und webpack-rtl-plugin geeignet;
  • Fügen Sie Ausnahmen für alles hinzu, was nicht umgedreht werden muss, und spiegeln Sie deutlich das wider, was sich nicht umgedreht hat.
  • isolieren Sie alle beliebigen Inhalte mit <bdi> und dir="auto" ;
  • Stellen Sie die text-align explizit auf die gesamte Seite ein.
  • Vermeiden Sie left / right in js Code, wenn Sie Start und Ende meinen.

Machen Sie sich bereit, die meiste Zeit mit den kleinsten Details zu verbringen.


Es ist nicht schwer, im Voraus vorbereitet zu sein


Und einige Tipps für diejenigen, die die Site noch nicht an RTL anpassen wollen, aber einen Strohhalm legen möchten:


  • Verwenden Sie die Eigenschaft direction für andere Zwecke.
  • Nur für den Fall, isolieren Sie immer noch alle beliebigen Inhalte (und sogar in der englischen Benutzeroberfläche können Benutzer etwas auf Arabisch schreiben und alles kaputt machen).
  • Verwenden Sie nach Möglichkeit logische CSS-Eigenschaften .
  • Überprüfen Sie das Layout nicht nur in verschiedenen Browsern, sondern manchmal auch in RTL, zumindest aus Neugier. Besser unauffällig das Layout unter RTL mit Tools wie einem Storybook steuern;
  • Lassen Sie keine Hardcode-Sprachkonstrukte zu (z. B. Verkettung von durch Kommas getrennten Zeichenfolgen). Konfigurieren Sie nach Möglichkeit alles, einschließlich Satzzeichen. Dies ist nicht nur für RTL nützlich - im Griechischen lautet das Fragezeichen beispielsweise „;“.

Diese Regeln sollten kein Ärger sein. Aber wenn plötzlich der Wunsch kommt, die RTL-Version zu starten, wird sie viel billiger als sie könnte.

Source: https://habr.com/ru/post/de416635/


All Articles