jueves, 26 de febrero de 2015

Right-to-Left

En un proyecto reciente hemos tenido el requerimiento de tener que soportar la orientación del texto de derecha a izquierda, y como intuyo que no será la última aplicación en la que tengamos que dar soporte a rtl, dejaré aquí algunas recetas y reflexiones que he ido acumulando durante el desarrollo de tan “exótica” feature.

Lo primero, como en todo, es entender que tenemos que hacer. Aunque suene de perogrullo, cambiar algo tan arraigado en nuestra cabeza como es el sentido de la escritura, puede suponer un esfuerzo de concentración extra para no perder ningún detalle. De hecho, no son pocas las veces que he llamado al cliente para que él mismo corroborara si algo tan simple como un texto o un imagen era acorde a rtl.

Al comienzo, gran parte del trabajo sucio lo hará el atributo dir de HTML. Este atributo nos permite especificar la orientación del texto de un elemento (ltr – por defecto – o rtl). Lo más sencillo es establecerlo en el body (sólo especificándolo de nuevo en los elementos en los que queramos sobrescribir el valor heredado, si es que tenemos esa necesidad). Además de dir como atributo, tenemos direction como propiedad CSS que cumple la misma función.

Por ejemplo el siguiente documento HTML se vería así si el valor de dir fuera rtl.

<body dir="rtl">

    <p>Hola mundo</p>

    <table>

        <tr>

            <td>Primera celda</td>

            <td>Segunda celda</td>

        </tr>

    </table>

</body>

image

Otro punto a tener en cuenta es la propiedad CSS float. Donde dije digo, digo Diego. Es decir, en rtl tendremos que flotar en el sentido opuesto al valor original. En el siguiente ejemplo (y aunque dir valga rtl) los divs seguirán flotante a la izquierda.

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>Document</title>

    <style>

        div {

            margin-right: 10px;

            float: left;

        }

    </style>

</head>

<body dir="rtl">

    <div>Div 1</div>

    <div>Div 2</div>

</body>

</html>

Para solucionarlo (así al menos lo he hecho yo), lo que hago es volver a definir la regla CSS (mismo selector pero definido después del original, así prevalecerá) y sobreescribir las propiedades que son necesarias. Digo “necesarias” en plural porque no sólo tenemos que preocuparnos por float, sino también de todas aquellas propiedades que hayamos utilizado en alguno de ambos lados del elemento (léase margin-left, margin-right, padding-left, etc.). Dicho esto, style quedaría así:

<style>

    div {

        margin-right: 10px;

        float: left;

    }

 

    div {

        margin-right: 0;

        margin-left: 10px;

        float: right;

    }

</style>

Si en algún momento la cascada de CSS nos jugara una mala pasada, siempre podemos hacer una regla con una especificad mayor que la original (aunque lo más sencillo es incluir todas estas reglas en un fichero rtl.css y asegurarse de que lo cargamos después de los estilos originales). Por ejemplo:

body[dir="rtl"] div {

    margin-right: 0;

    margin-left: 10px;

    float: right;

}

Además de float, el posicionamiento absoluto, relativo o fijo también tocará revisarlo. Si la idea era anclar algo en la esquina superior derecha (por ejemplo una x para cerrar), ahora deberemos hacerlo en la esquina superior izquierda, luego pasaremos de utilizar la propiedad left a utilizar la propiedad right.

    <style>

        .close-button {

            right: 0;

            top: 0;

        }

 

        .close-button {

            left: 0;

            right: auto;

        }

    </style>

Ya en Javascript, aunque está bien eso de separar contenido, estilo y comportamiento, me ha pasado que en algunos plugins de jQuery estaba dando estilo directamente a través de código. En este caso y para aplicar un estilo u otro en función de dir podríamos utilizar esta función:

    <script>

        function isRtl() {

            return document.body.dir === "rtl";

        }

    </script>

Algo que también tenemos que vigilar es la orientación de las imágenes. Por ejemplo, si tenemos una imagen con una flecha apuntando a la izquierda para indicar que se abrirá un panel en esa dirección, si ahora el panel se abre en la dirección opuesta lógicamente la flecha tendrá que estar apuntado en la dirección adecuada.

En este y otros muchos casos y para intentar no volverme muy loco, he creado un par de métodos de extensión para facilitar la tarea:

using System.Threading;

using System.Web;

using System.Web.Mvc;

using Antlr.Runtime.Misc;

 

namespace WebApplication1

{

    public enum Direction

    {

        Left,

        Right

    }

 

    public static class HtmlLanguageExtensions

    {

        public static HtmlString Dir(this HtmlHelper that)

        {

            return new HtmlString(!IsRtl() ? "ltr" : "rtl");

        }

 

        public static HtmlString Direction(

            this HtmlHelper that,

            Func<string> left,

            Func<string> right)

        {

            return new HtmlString(!IsRtl() ? left() : right());

        }

 

        public static HtmlString Direction(

            this HtmlHelper that,

            Direction direction)

        {

            string retval;

            if (!IsRtl())

            {

                retval =

                    direction == WebApplication1.Direction.Left ?

                    "left" :

                    "right";

            }

            else

            {

                retval =

                    direction == WebApplication1.Direction.Left ?

                    "right" :

                    "left";

            }

            return new HtmlString(retval);

        }

 

        public static HtmlString Lang(this HtmlHelper that)

        {

            return new HtmlString(Culture.GetCurrentTwoLetterISOLanguageName());

        }

 

        private static bool IsRtl()

        {

            return Thread.CurrentThread.CurrentUICulture.TextInfo.IsRightToLeft;

        }

    }

}

Ahora en vez de escribir esto:

<img src="~/Content/images/flecha-izquierda.svg" />

Escribiría esto otro:

<img src="~/Content/images/flecha-@(Html.Direction(() => "izquierda", () => "derecha")).svg" />

Y en el caso de que los literales a devolver fueran directamente “left” o “right”, escribiría esto:

<img src="~/Content/images/arrow-@(Html.Direction(Direction.Left).svg" />

Por último, estamos utilizando Bootstrap como framework CSS y de serie no soporta rtl. Si utilizamos por ejemplo la regla .list-inline veríamos esto:

image

Algo raro está pasando… esa scrollbar…, el estilo de .list-inline es:

.list-inline {

  padding-left: 0;

  margin-left: -5px;

  list-style: none;

}

.list-inline > li {

  display: inline-block;

  padding-right: 5px;

  padding-left: 5px;

}

Gracias a que siempre hay algún buen samaritano con un repo en github (desde aquí le quiero dar gracias al autor) la solución pasa por incluir una segunda hola de estilos que de soporte a rtl en Bootstrap. El repo es https://github.com/morteza/bootstrap-rtl. Para utilzar esta nueva hoja de estilos tenemos que agregar el enlace después de haber cargado Boostrap.

<link rel="stylesheet" href="//cdn.rawgit.com/morteza/bootstrap-rtl/master/dist/cdnjs/3.3.1/css/bootstrap-rtl.min.css">

Ahora el estilo de .list-inline pasará a ser:

.list-inline {

    padding-right: 0;

    padding-left: initial;

    margin-right: -5px;

    margin-left: 0;

}

Y ya entonces si tenemos una lista de Bootstrap funcionando correctamente con rtl

image

La verdad es que no he comprobado si todos los componentes y estilos de Boostrap son soportados en rtl con esta nuevo hoja de estilos, pero aquí voy a optar por ser reactivo y según vaya encontrándome problemas iré buscando soluciones.

Hasta aquí mi aventura con rtl, espero te sirva de ayuda!

Un saludo!