
Recientemente tradujimos la versión en línea de 2GIS al árabe, y en un artículo anterior hablé sobre la teoría necesaria para esto: qué es dir="rtl"
, por qué reglas se muestra un texto de enfoque mixto y cómo controlarse.
Es hora de comenzar a practicar: girar toda la interfaz de derecha a izquierda con un mínimo esfuerzo para que incluso un árabe real no sienta la trampa.
En este artículo, le diré cómo prototipar rápidamente qué hacer con el ensamblaje CSS y qué muletas descomponer en JS, observar un poco sobre las características de traducción y localización, recordar las propiedades lógicas de CSS y tocar el tema RTL en CSS-in-JS.
Estilos de volteo
Cuando apliqué el atributo dir = "rtl" a la etiqueta, solo cambió el orden implícito de los elementos, por ejemplo, el orden de las celdas de la tabla o los elementos flexibles. No pasó nada con los valores especificados explícitamente en los estilos.
Tome los estilos de alguna notificación ubicada en la parte inferior derecha:
.tooltip { position: 'absolute'; bottom: 10px; right: 10px; }
dir="rtl"
no afectará estos estilos de ninguna manera: en la versión RTL, la información sobre herramientas también estará a la derecha, aunque se espera a la izquierda.
Que hacer right: 10px
reemplazar right: 10px
por left: 10px
. Y así con todos los otros estilos. Posicionamiento absoluto, margen / relleno, alineación de texto: todo debe girarse en la dirección opuesta para la versión árabe.
Prototipo rápido
Para empezar, puede, sin dudarlo, cambiar todas las apariciones de izquierda a derecha y hacer un poco de magia con valores abreviados:
- izquierda: 0 → derecha: 0
- relleno-izquierda: 4px → relleno-derecha: 4px
- margen: 0 16px 0 0 → margen: 0 0 0 16px
El complemento postcss-rtl es adecuado para esto. Conveniente: solo necesita colocarlo en la lista de todos los complementos de postcss-project. Reemplaza todas las reglas dirigidas por las duplicadas y lo envuelve en [dir="rtl"]
. Por ejemplo:
.foo { color: red; margin-left: 16px; } [dir] .foo { color: red; } [dir="ltr"] .foo { margin-left: 16px; } [dir="rtl"] .foo { margin-right: 16px; }
Después de eso, solo necesita establecer dir="rtl"
y solo se aplican automáticamente las reglas necesarias. Todo funciona y parece que casi todo está listo para la producción, pero esta solución solo es adecuada para un prototipo rápido:
- La especificidad de cada regla aumenta . Esto no será necesariamente un problema, pero me gustaría evitarlo;
- Tales manipulaciones dan lugar a errores . Por ejemplo, el orden de las propiedades puede romperse ;
- El tamaño del archivo CSS está creciendo notablemente .
[dir]
agrega a cada selector, cada propiedad dirigida se duplica. En nuestro caso, el tamaño aumentó en un proyecto en un 21%, en otro, en un 35%:
| tamaño original (gzip) | tamaño bidireccional (gzip) | hinchado en |
---|
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% |
¿Hay una mejor opción?
Es necesario recopilar estilos para LTR y RTL por separado. Entonces no será necesario tocar los selectores y el tamaño de css difícilmente cambiará.
Para esto, elegí:
- RTLCSS : esta biblioteca está bajo el capó de postcss-rtl.
- webpack-rtl-plugin es una solución llave en mano para estilos creados a través de ExtractTextPlugin. El mismo RTLCSS debajo del capó.
Y comenzó a recopilar RTL y LTR en diferentes archivos: styles.css
y styles.rtl.css
. El único inconveniente de ensamblar en diferentes archivos es que no puede reemplazar el directorio sobre la marcha sin descargar primero el archivo deseado.
RTLCSS le permite usar directivas para controlar el procesamiento de reglas específicas, por ejemplo:
.foo { right: 0; } .bar { font-size:16px; }
¿Qué otras soluciones hay?
Todas las soluciones existentes casi no son diferentes de RTLCSS.
- css-flip de Twitter;
- cssjanus de Wikimedia;
- Sí, y postcss-rtl admite el parámetro
onlyDirection
, con el que puede recopilar estilos para una sola dirección, pero el tamaño aún crece; por ejemplo, para dispositivos móviles 2GIS es 18% en lugar de 35% (24.5 kB → 29 kB).
¿Cuándo se necesitan las directivas?
Cuando los estilos no deberían depender de la orientación
Por ejemplo, el ángulo de rotación de la flecha que indica la dirección del viento:

.arrow._nw { transform: rotate(135deg); }
O un desvanecimiento para un número de teléfono: los números siempre se escriben de izquierda a derecha, lo que significa que el gradiente siempre debe estar a la derecha:


Cuándo centrar el ícono
Este es un caso especial del párrafo anterior. Si centramos el icono asimétrico a través de la sangría / posicionamiento, desplazamos su bloque hacia un lado, y si refleja el desplazamiento, el icono se "moverá" hacia el otro lado:

Es mejor centrar el icono en svg en estas situaciones:
Cuando necesita aislar un widget completo que no debe responder a RTL
En nuestro caso, este es un mapa. Envolvemos todos sus estilos al ensamblar en directivas de bloque: /*rtl:begin:ignore*/ ... /*rtl:end:ignore*/
.
¿Hay una opción aún mejor?
La solución con la reversión de las reglas funciona bien, pero surge la pregunta: ¿es una muleta? La dependencia de los estilos en la dirección es una tarea natural para la web moderna, y su relevancia crece cada año. Esto debería haberse reflejado en los estándares y enfoques modernos. Y encontrado!
Propiedades lógicas
Para adaptar el diseño a diferentes orientaciones, hay un estándar para las propiedades lógicas en css . No solo concierne a las direcciones de izquierda a derecha y de derecha a izquierda, sino también a las direcciones de arriba a abajo, pero no lo consideraremos.
Ya usamos algo similar en flexiones y cuadrículas; por ejemplo, flex-start
, flex-end
, grid-row-start
, grid-column-end
desatados de izquierda / derecha.
En lugar de los conceptos left
, right
, top
e bottom
propone utilizar inline-start
, inline-end
, block-start
y block-end
. En lugar de width
y height
, inline-size
block-size
. Y en lugar de shortcands abcd
- logical adcb
(los shortdes lógicos van en sentido antihorario). Además, para emparejar shortends existentes, aparecen nuevas versiones emparejadas: 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
Y aparece la taquigrafía tan esperada para el posicionamiento:
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
Esto ya está disponible en Firefox sin banderas y en los navegadores webkit basados en banderas.
Pros : la solución es nativa, funcionará sin ensamblaje / complementos en absoluto, si los navegadores necesarios son compatibles. No hay necesidad de directivas: simplemente escriba left
lugar de inline-start
, cuando se refiere a la "izquierda" física.
Las desventajas provienen de los profesionales: sin complementos, el código en la mayoría de los navegadores no es válido, debe hacer mucho trabajo para traducir un gran proyecto existente.
¿Cómo conectarse?
La forma más fácil es postcss-logical . Sin el parámetro dir
, recopila estilos para ambas direcciones de la misma manera que postcss-rtl, con el parámetro dir
dado, solo para la dirección especificada:
.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; }
¿Cómo convencer a un equipo para que comience a escribir offset-inline-start en lugar de izquierda?
De ninguna manera Pero decidimos simplificarlo en nuestro proyecto: escriba start: 0
lugar de offset-inline-start: 0
, tan pronto como todos se acostumbren, comenzaré a imponer una entrada válida :)
RTL + CSS-in-JS = ️️ <3
CSS-in-JS no necesita ser ensamblado de antemano. Esto significa que en tiempo de ejecución es posible determinar la dirección de los componentes y elegir cuáles dar vuelta y cuáles no. Útil si necesita insertar algún widget que no sea compatible con RTL.
En general, la tarea es convertir objetos del tipo { paddingInlineStart: '4px' }
(o { paddingLeft: '4px' }
, si no fue posible cambiar a propiedades lógicas) en objetos del tipo { paddingRight: '4px' }
:
- Armado con bidi-css-js o rtl-css-js . Proporcionan una función que toma un objeto de estilo y vuelve transformado a la orientación deseada.
- ???
- BENEFICIOS!
Ejemplo de reacción
Envuelva cada componente con estilo en un HOC que acepte estilos:
export default withStyles(styles)(Button);
Toma la dirección del componente del contexto y selecciona los estilos finales:
function withStyles(styles) { const { ltrStyles, rtlStyles } = bidi(styles); return function WithStyles(WrappedComponent) { ... render() { return <WrappedComponent {...this.props} styles={this.context.dir === 'rtl' ? rtlStyles : ltrStyles} />; }; }; ... }; }
Y el proveedor enfoca el contexto:
<DirectionProvider dir="rtl"> ... <Button /> ...
Airbnb utiliza un enfoque similar: https://github.com/airbnb/react-with-styles-interface-aphrodite#built-in-rtl-support , si ya se usa afrodita en el proyecto, puede usar esta solución preparada.
Para JSS, aún es más simple: solo necesita habilitar jss-rtl :
jss.use(rtl());
componentes con estilo
const Button = styled.button` background: #222; margin-left: 12px; `;
¿Qué pasa si trabajamos con cadenas de plantillas y no con objetos? Todo es complicado, pero hay una solución: calcule el nombre de la propiedad desde la dirección especificada en los props
:
const marginStart = props => props.theme.dir === "rtl" ? "margin-left" : "margin-right"; const Button = styled.button` background: #222; ${marginStart}: 12px; `;
Pero parece más fácil cambiar de líneas a objetos, los componentes con estilo han podido hacer esto desde la versión 3.3.0 .
Características de traducción y localización.
Descubrimos la parte técnica. Aislaron el contenido de una orientación indeterminada, reflejaron estilos, colocaron excepciones en los lugares correctos y tradujeron los textos al árabe. Todo parece estar listo: al cambiar el idioma, la interfaz completa aparece en el otro lado de la pantalla, no hay un diseño en su lugar y todo se ve mejor que en cualquier sitio árabe.
Mostramos al verdadero árabe, y ...
Resulta que no todos los hablantes de árabe saben lo que es Twitter. Esto se aplica a casi todas las palabras en inglés. Para tal caso, hay una transcripción árabe: "تويتر".
Resulta que en árabe hay comas, y el hecho de que en todas partes del código estuvimos concatenados a través de ",", en árabe, necesitamos concatenar a través de "،".
Resulta que en algunos países musulmanes el calendario oficial es islámico. Es lunar y la fórmula de traducción habitual es indispensable.
Resulta que en Dubai no hay temperatura negativa y el signo más en el pronóstico de +40 no tiene ningún sentido.
No solo estilos recogidos y reflejados
Si hacemos dir="auto"
en el elemento de bloque y su contenido resulta ser LTR, el texto golpeará al lado izquierdo del contenedor, incluso si está alrededor de RTL. Esto simplemente se puede curar estableciendo explícitamente text-align: right
. Incluso puede aplicar esto a toda la página en la versión árabe: el valor de esta propiedad se hereda.
Los iconos tampoco se reflejan automáticamente. Y sin esto, los íconos direccionales, como las flechas en la galería, pueden mirar en la dirección incorrecta. ¡Imagine que este es el único caso en el que las flechas hechas a través del borde se justifican a sí mismas!
Una transformación simple ayudará a reflejar los íconos:
[dir="rtl"] .my-icon { transform: scaleX(-1); }
Es cierto que no ayudará si el icono contiene letras o números. Luego debe hacer dos iconos diferentes y pegarlos condicionalmente:

Y, sin embargo, resulta que no todos los elementos de la interfaz deben reflejarse. Por ejemplo, en nuestro caso, decidimos dejar las casillas de verificación normales:


No sé cómo seleccionar dichos elementos de toda la interfaz. Solo un hablante nativo puede ayudar aquí, quien dirá lo que le es familiar y lo que no.
Entrada del usuario
Incluso si controlamos completamente todos los datos de nuestra aplicación, puede ser una entrada del usuario. Por ejemplo, el nombre del archivo. Incluso puede idear y entregar el archivo .js como .png; tal vulnerabilidad estaba en Telegram :
cool_picture * U + 202E * gnp.js → cool_picture sj.png
En tales casos, vale la pena filtrar caracteres utf inapropiados de la cadena.
Voltear guiones
La sintaxis cambia un poco en el JavaScript RTL. El bucle que se veía así:
for (let i = 0; i < arr.length; i++) {
Ahora necesitas escribir así:
for (++i; length.arr > i; let 0 = i) {
Un chiste
Todo lo que necesita hacer es evitar los conceptos de "izquierda" y "derecha" en su código. Por ejemplo, encontramos problemas al calcular las coordenadas del centro de la pantalla, antes de que todas las tarjetas colgaran a la izquierda y ahora a la derecha, pero el código de la aplicación no lo sabía. Todos los cálculos y estilos en línea deben llevarse a cabo, teniendo en cuenta el enfoque básico.
Ingenio rápido
En algunas situaciones, es difícil implementar el soporte RTL en algún componente del sistema. Luego debe intentar adaptar este componente para RTL desde el exterior, pero deje LTR adentro.
Por ejemplo, tenemos un control deslizante. Solo admite valores ordenados positivos. Puede ser lineal y logarítmico (al principio, la densidad de valores es menor que al final). Debe reflejarlo mientras mantiene el comportamiento de la escala.

Puede voltear el control deslizante usando transform: scaleX(-1)
. Luego, debe invertir el trabajo con el mouse (clics y arrastres) en relación con el centro del control deslizante. Mala opción
Hay otra opción: rotar el eje en la otra dirección, cambiando solo la transferencia y la recepción de valores desde el control deslizante. Si se trata de una escala lineal, en lugar del conjunto de valores [10, 100, 1000] transferiremos el conjunto [N-1000, N-100, N-10], y en el controlador lo convertiremos de nuevo. Para la escala logarítmica, en lugar del conjunto [10, 100, 1000], pasamos [1/1000, 1/100, 1/10]:
function flipSliderValues(values, scale, isRtl) { if (!isRtl) { return values; } if (scale === 'log') {
Así es como el control deslizante comenzó a admitir RTL, aunque él mismo no lo sabe.
Libro de cuentos
A diferencia de la composición tipográfica en algunos IE9, no necesita ejecutar un navegador separado para verificar la composición tipográfica en RTL. Incluso puede escribir y ver el diseño de LTR y RTL simultáneamente en una ventana. Para hacer esto, puede, por ejemplo, hacer un decorador en el libro de cuentos , que representa dos versiones de la historia a la vez:

La captura de pantalla muestra que sin aislamiento text-overflow: ellipsis
no se comportan como nos gustaría; es mejor solucionarlo de inmediato.
Es mucho más fácil soportar RTL inmediatamente durante el diseño que probar absolutamente todo el proyecto más adelante.
Problemas sin resolver
El conocimiento de la teoría no ayuda a resolver absolutamente todos los problemas. Daré un ejemplo.
Una sugerencia que aparece en la dirección del texto a medida que escribe. Cuando hablamos de entradas multilingües, no podemos saber de antemano en qué dirección mostrar este mensaje:


Debe intentar evitar tales problemas en la etapa de diseño y, a veces, abandonar soluciones que son obvias para LTR y que no son aplicables en RTL. En este caso, al navegar por las indicaciones, puede sustituir todo el texto (como, por ejemplo, Yandex o Google).
Conclusiones
RTL no es solo "cambiar todo"
Es necesario tener en cuenta las peculiaridades del lenguaje, no necesita voltear algo, necesita adaptar otra cosa. En algún lugar de la lógica es absolutamente necesario abandonar la "derecha" / "izquierda".
Es muy difícil hacer algo sin saber el idioma.
Pensarás que todo está listo hasta que muestres tu proyecto a un hablante nativo real. Desarrollar desde el punto de vista de una persona que no conoce ningún idioma. Después de todo, incluso palabras tan obvias para usted, como, por ejemplo, "Twitter", pueden tener que traducirse. Y resulta que los signos de puntuación no son los mismos en todo el planeta.
Receta final
Es difícil describir en una lista todo lo que se discutió en dos artículos. No habrá tutorial, pero aquí está lo más importante:
- asegúrese de encontrar un hablante nativo y mostrarle los prototipos lo antes posible;
- Recopile estilos para LTR y RTL en diferentes archivos. Para esto, rtlcss y webpack-rtl-plugin son adecuados;
- agregue excepciones para todo lo que no necesita ser entregado y refleje claramente lo que no se entregó;
- aislar todo el contenido arbitrario usando
<bdi>
y dir="auto"
; - establecer explícitamente
text-align
a toda la página; - Evite
left
/ right
en el código js cuando se refiere a inicio y fin.
Prepárese para pasar la mayor parte del tiempo en los detalles más pequeños.
No es difícil estar preparado de antemano
Y algunos consejos para aquellos que aún no van a adaptar el sitio a RTL, pero que quieren poner una pajita:
- no use la propiedad de
direction
para otros fines; - por si acaso, sin embargo, aísle todo el contenido arbitrario (y, de hecho, incluso en la interfaz en inglés, los usuarios pueden escribir algo en árabe y romper todo);
- si es posible, use propiedades css lógicas ;
- compruebe el diseño no solo en diferentes navegadores, sino también a veces en RTL, al menos por curiosidad. Controle mejor el diseño discretamente bajo RTL utilizando herramientas como un libro de cuentos ;
- no permita construcciones de lenguaje de código duro (por ejemplo, concatenación de cadenas separadas por comas), si es posible, configure todo, incluidos los signos de puntuación. Esto es útil no solo para RTL, por ejemplo, en griego el signo de interrogación es ";".
Estas reglas no deberían ser una molestia. Pero si de repente surge el deseo de lanzar la versión RTL, será mucho más barato de lo que podría.