Cómo brindar compatibilidad con diferentes idiomas y culturas

Las apps incluyen recursos que pueden ser específicos de una cultura en particular. Por ejemplo, pueden incluir strings específicas de una cultura que se traducen al idioma de la configuración regional actual. Se recomienda mantener los recursos específicos de una cultura separados del resto de tu app. Android resuelve recursos específicos de un idioma y una cultura según la configuración regional del sistema. Puedes brindar compatibilidad con diferentes configuraciones regionales utilizando el directorio de recursos de tu proyecto de Android.

Puedes especificar recursos adaptados a la cultura de los usuarios de tu app. Puedes proporcionar cualquier tipo de recurso que sea apropiado para el idioma y la cultura de tus usuarios. Por ejemplo, en la siguiente captura de pantalla, se puede ver una app que muestra una string y recursos de elementos de diseño en la configuración regional predeterminada del dispositivo (en_US) y en la española (es_ES).

La app muestra diferentes íconos y textos según la configuración regional

Figura 1. La app usa diferentes recursos según la configuración regional

Si creaste tu proyecto con Android SDK Tools (lee Cómo crear un proyecto en Android), las herramientas crean un directorio res/ en el nivel superior del proyecto. El directorio res/ contiene subdirectorios para varios tipos de recursos. También hay algunos archivos predeterminados, como res/values/strings.xml, que contienen los valores de tu string.

Brindar compatibilidad con diferentes idiomas va más allá de usar recursos específicos de una configuración regional. Algunos usuarios eligen un idioma que utiliza secuencias de comandos de derecha a izquierda (RTL), como el árabe o el hebreo, para la configuración regional de la IU. Otros ven o generan contenido en un idioma que utiliza secuencias de comandos RTL, aunque hayan establecido un idioma que utiliza secuencias LTR (por ejemplo, inglés) como configuración regional. Para admitir ambos tipos de usuarios, tu app debe hacer lo siguiente:

  • Emplear un diseño de IU RTL para configuraciones regionales RTL
  • Detectar y declarar la dirección de los datos de texto que se muestran dentro de los mensajes con formato Por lo general, puedes invocar un método que determine la dirección de los datos de texto por ti.

Cómo crear directorios de configuración regional y archivos de recursos

Para agregar compatibilidad con más configuraciones regionales, crea directorios adicionales dentro de res/. El nombre de cada directorio debe seguir el siguiente formato:

<resource type>-b+<language code>[+<country code>]

Por ejemplo, values-b+es/ contiene recursos de string para configuraciones regionales con el código de idioma es. Del mismo modo, mipmap-b+es+ES/ contendrá íconos para configuraciones regionales que tengan el código de idioma es y el código de país ES. Android carga los recursos correspondientes según las configuraciones regionales del dispositivo durante el tiempo de ejecución. Para obtener más información, consulta Cómo proporcionar recursos alternativos.

Una vez que hayas decidido cuáles son las configuraciones regionales que quieras admitir, crea los subdirectorios y archivos de recursos. Por ejemplo:

MyProject/
    res/
       values/
           strings.xml
       values-b+es/
           strings.xml
       mipmap/
           country_flag.png
       mipmap-b+es+ES/
           country_flag.png

Los siguientes son algunos archivos diferentes de recursos para distintos idiomas:

Strings en inglés (configuración local predeterminada), /values/strings.xml:

<resources>
    <string name="hello_world">Hello World!</string>
</resources>

Strings en español (configuración regional de es), /values-es/strings.xml:

<resources>
    <string name="hello_world">¡Hola Mundo!</string>
</resources>

Ícono de la bandera de Estados Unidos (configuración regional predeterminada), /mipmap/country_flag.png:

Ícono de la bandera de Estados Unidos

Figura 2. Ícono usado para la configuración regional predeterminada (en_US)

Ícono de la bandera de España (configuración regional es_ES), /mipmap-b+es+ES/country_flag.png:

Ícono de la bandera de España

Figura 3. Ícono usado para la configuración regional de es_ES

Nota: Puedes usar un calificador de configuración regional (o cualquier calificador de configuración) en cualquier tipo de recurso, como lo harías si deseas proporcionar versiones localizadas del elemento de diseño de tu mapa de bits. Para obtener más información, consulta Localización.

Cómo usar los recursos de tu app

Puedes hacer referencia a los recursos de tu código fuente y otros archivos XML utilizando el atributo name de cada recurso.

En tu código fuente, puedes hacer referencia a un recurso usando la sintaxis R.<resource type>.<resource name>. Hay una variedad de métodos disponibles que aceptan un recurso de esta manera.

Por ejemplo:

Kotlin

// Get a string resource from your app's Resources
val hello = resources.getString(R.string.hello_world)

// Or supply a string resource to a method that requires a string
TextView(this).apply {
    setText(R.string.hello_world)
}

Java

// Get a string resource from your app's Resources
String hello = getResources().getString(R.string.hello_world);

// Or supply a string resource to a method that requires a string
TextView textView = new TextView(this);
textView.setText(R.string.hello_world);

En otros archivos XML, puedes hacer referencia a un recurso con la sintaxis @<resource type>/<resource name>, siempre que el atributo XML acepte un valor compatible.

Por ejemplo:

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@mipmap/country_flag" />

Cómo darle formato al texto en mensajes

Una de las tareas más comunes en una app es darle formato al texto. Esto se logra insertando texto y datos numéricos en las posiciones apropiadas. Lamentablemente, cuando se trata de una IU o datos RTL, el formato simple puede mostrar una salida de texto incorrecta o incluso ilegible.

Idiomas como el árabe, hebreo, persa y urdu suelen escribirse en dirección RTL. Sin embargo, algunos de sus elementos, como los números y el texto LTR incorporado, están escritos en la dirección LTR dentro del texto RTL. Los idiomas que utilizan secuencias de comandos LTR, incluido el inglés, también son bidireccionales porque pueden contener secuencias de comandos RTL incorporados que deben visualizarse en una dirección RTL.

La mayoría de las veces, son las mismas apps las que generan estas instancias de texto incorporado en dirección opuesta. Insertan datos de texto de un idioma arbitrario (y una dirección de texto arbitraria) en mensajes localizados. Esta mezcla de direcciones a menudo no incluye una indicación clara de dónde comienza y termina el texto en dirección opuesta. Estas características del texto generado por apps causan la mayoría de los problemas.

Aunque el manejo predeterminado del sistema de texto bidireccional por lo general muestra el texto como se espera, es posible que no se muestre correctamente cuando la app lo inserte en un mensaje localizado. Las siguientes situaciones presentan ejemplos de casos en los que es más probable que el texto no se muestre correctamente:

  • Insertado al principio del mensaje:

    PERSON_NAME te está llamando

  • Comienza con un número, como en direcciones o números de teléfono:

    987 654-3210

  • Comienza con un signo de puntuación, como en un número de teléfono:

    +19876543210

  • Termina con un signo de puntuación:

    ¿Confirmas la acción?

  • Ya contiene ambas direcciones:

    La palabra בננה significa "banana" en hebreo.

Ejemplo

Por ejemplo, supongamos que una app necesita mostrar el mensaje "¿Quisiste decir %s?", con una dirección en lugar de "%s" durante el tiempo de ejecución. Debido a que la app admite diferentes configuraciones regionales de IU, el mensaje proviene de un recurso específico de ubicación y utiliza la dirección RTL cuando se está utilizando una configuración regional RTL. Una IU en hebreo debe aparecer de la siguiente forma:

האם התכוונת ל %s?

La sugerencia, sin embargo, podría provenir de una base de datos que no incluye texto en el idioma de la configuración regional. Por ejemplo, si la dirección en cuestión es para un lugar en California, aparece en la base de datos usando texto en inglés. Si insertas la dirección "15 Bay Street, Laurel, CA" en el mensaje RTL sin indicar la dirección del texto, el resultado no es el esperado ni el correcto:

האם התכוונת ל 15 Bay Street, Laurel, CA?

Ten en cuenta que el número de la casa aparece a la derecha de la dirección, no a la izquierda como se pretende, lo que hace que el número se parezca más a un código postal que a una dirección. El mismo problema puede ocurrir si incluyes texto RTL en un mensaje que utiliza la dirección LTR.

Explicación y solución

El problema en el ejemplo anterior ocurre porque el formateador de texto no especifica que "15" es parte de la dirección, por lo que el sistema no puede determinar si el "15" es parte del texto RTL que viene antes o del texto LTR que viene después.

Para resolver este problema, usa el método unicodeWrap() que se encuentra en la clase BidiFormatter, en cada porción de texto que insertes en un mensaje localizado. No deberías usar unicodeWrap() en los siguientes casos:

  • El texto se inserta en una string legible por máquina, como un URI o una consulta SQL.
  • Ya sabes que la porción de texto está bien envuelta.

El método unicodeWrap() detecta la dirección de una string y la envuelve en caracteres de formato Unicode que declaran esa dirección. Debido a que ahora "15" aparece dentro del texto que se declara como LTR, se muestra en la posición correcta:

האם התכוונת ל 15 Bay Street, Laurel, CA?

El siguiente fragmento de código demuestra cómo usar unicodeWrap():

Kotlin

val mySuggestion = "15 Bay Street, Laurel, CA"
val myBidiFormatter: BidiFormatter = BidiFormatter.getInstance()

// The "did_you_mean" localized string resource includes
// a "%s" placeholder for the suggestion.
String.format(getString(R.string.did_you_mean), myBidiFormatter.unicodeWrap(mySuggestion))

Java

String mySuggestion = "15 Bay Street, Laurel, CA";
BidiFormatter myBidiFormatter = BidiFormatter.getInstance();

// The "did_you_mean" localized string resource includes
// a "%s" placeholder for the suggestion.
String.format(getString(R.string.did_you_mean),
        myBidiFormatter.unicodeWrap(mySuggestion));

Nota: Si tu app está dirigida a Android 4.3 (nivel de API 18) o posterior, utiliza la versión de BidiFormatter que se encuentra en el marco de trabajo de Android. De lo contrario, usa la versión de BidiFormatter que se encuentra en la Biblioteca de compatibilidad.

Formato de números

Usa strings de formato, y no llamadas de método, para convertir números en strings en la lógica de tu app:

Kotlin

var myIntAsString = "$myInt"

Java

String myIntAsString = String.format("%d", myInt);

Esto dará el formato adecuado a los números según la configuración regional, lo que puede incluir el uso de un conjunto diferente de dígitos.

Si usas String.format() para crear una consulta SQL en un dispositivo cuya configuración regional utiliza su propio conjunto de dígitos, como el persa y la mayoría de las variantes árabes, se generarán problemas si alguno de los parámetros de la consulta es un número. Esto se debe a que el número sigue el formato de los dígitos de la configuración regional, y esos dígitos no son válidos en SQL.

Para preservar los números con formato ASCII y que la consulta SQL sea válida, deberías utilizar en su lugar la versión sobrecargada de String.format(), que incluye una configuración regional como primer parámetro. El argumento de esta configuración debería ser Locale.US.

Compatibilidad con espejado de diseños

Las personas que usan secuencias de comandos RTL prefieren una interfaz de usuario RTL, que incluya menús alineados a la derecha, texto alineado a la derecha y flechas hacia adelante apuntando a la izquierda.

La Figura 4 muestra el contraste entre la versión LTR de una pantalla dentro de la app de Settings y su contraparte RTL:

El área de notificación está alineada a la derecha cerca de la esquina superior derecha,            el botón del menú en la barra de aplicaciones está cerca de la esquina superior izquierda, el            contenido en la parte principal de la pantalla está alineado a la izquierda y parece            LTR, y el botón Atrás está cerca de la esquina inferior izquierda y apunta            a la izquierda. El área de notificación está alineada a la izquierda cerca de la esquina superior izquierda, el            botón del menú en la barra de aplicaciones está cerca de la esquina superior derecha, el contenido            en la parte principal de la pantalla está alineado a la derecha y parece RTL, y            el botón Atrás está cerca de la esquina inferior derecha y apunta            a la derecha.
Figura 4. Variantes LTR y RTL de una pantalla

Cuando agregues compatibilidad con RTL a tu app, es importante que tengas en cuenta los siguientes puntos:

Nota: Para ver las pautas adicionales relacionadas con el espejado del diseño, incluida una lista de elementos que debes y no debes espejar, consulta las pautas de material design sobre bidireccionalidad.

Para espejar el diseño de la IU en tu app de manera que aparezca RTL en una configuración regional RTL, completa los pasos que se indican en las siguientes secciones.

Cómo modificar los archivos de manifiesto y compilación

Modifica el archivo build.gradle del módulo de tu app y el archivo de manifiesto de la siguiente manera:

build.gradle (Módulo: app)

android {
    ...
    defaultConfig {
        targetSdkVersion 17 // Or higher
        ...
    }
}

AndroidManifest.xml

<manifest ... >
    ...
    <application ...
        android:supportsRtl="true">
    </application>
</manifest>

Nota: Si tu app se dirige a Android 4.1.1 (nivel de API 16) o una versión anterior, se ignorará el atributo android:supportsRtl, junto con cualquier valor de atributo start y end que aparezca en los archivos de diseño de tu app. En este caso, el espejado del diseño RTL no se realizará automáticamente en la app.

Cómo actualizar recursos existentes

Convierte left y right en start y end, respectivamente, en cada uno de tus archivos de recursos de diseño existentes. De esta forma, permitirás que el marco de trabajo alinee los elementos de la interfaz de usuario de tu app en función de la configuración de idioma del usuario.

Nota: Antes de actualizar tus recursos, aprende cómo brindar compatibilidad para apps heredadas o apps que se dirijan a Android 4.1.1 (nivel de API 16) y versiones anteriores.

Para utilizar las funciones de alineación RTL del marco de trabajo, cambia los atributos de los archivos de diseño que aparecen en la Tabla 1.

Tabla 1. Atributos que puedes usar cuando tu app admite varias direcciones de texto

Atributo que solo admite LTR Atributo que admite LTR y RTL
android:gravity="left" android:gravity="start"
android:gravity="right" android:gravity="end"
android:layout_gravity="left" android:layout_gravity="start"
android:layout_gravity="right" android:layout_gravity="end"
android:paddingLeft android:paddingStart
android:paddingRight android:paddingEnd
android:drawableLeft android:drawableStart
android:drawableRight android:drawableEnd
android:layout_alignLeft android:layout_alignStart
android:layout_alignRight android:layout_alignEnd
android:layout_marginLeft android:layout_marginStart
android:layout_marginRight android:layout_marginEnd
android:layout_alignParentLeft android:layout_alignParentStart
android:layout_alignParentRight android:layout_alignParentEnd
android:layout_toLeftOf android:layout_toStartOf
android:layout_toRightOf android:layout_toEndOf

La Tabla 2 muestra cómo maneja el sistema los atributos de alineación de la IU en función de la versión del SDK de destino, así como si se han definido los atributos left, right, start y end.

Tabla 2. Comportamiento de alineación de elementos de IU basado en la versión del SDK de destino y los atributos definidos

Dirección de contenido en Android 4.2
¿Nivel de API 17 o versiones posteriores?
¿Definición hacia la izquierda o la derecha? ¿Definición de inicio y fin? Resultado
Se resuelven start y end, y se anulan left y right
No Solo se usan left y right
No Solo se usan start y end
No Se usan left y right (se ignoran start y end)
No No Solo se usan left y right
No No start y end se resuelven como left y right

Cómo agregar recursos específicos de dirección e idioma

Este paso implica agregar versiones específicas de tu diseño, elementos de diseño y archivos de recursos que contienen valores personalizados para diferentes idiomas y direcciones de texto.

En Android 4.2 (nivel de API 17) y versiones posteriores, puedes utilizar los calificadores de recursos -ldrtl (diseño, dirección, derecha a izquierda) y -ldltr (diseño, dirección, izquierda a derecha). Para mantener la compatibilidad con la carga de recursos existentes, las versiones antiguas de Android utilizan los calificadores de idioma de un recurso para inferir la dirección correcta del texto.

Supongamos que quieres agregar un archivo de diseño específico para admitir secuencias de comandos RTL, como el hebreo, el árabe y el persa. Para ello, agregas un directorio layout-ldrtl/ en res/, como se muestra en el siguiente ejemplo:

res/
    layout/
        main.xml This layout file is loaded by default.
    layout-ldrtl/
        main.xml This layout file is loaded for languages using an
                 RTL text direction, including Arabic, Persian, and Hebrew.

Si quieres agregar una versión específica del diseño solo para texto en árabe, la estructura de directorios se convierte en la siguiente:

res/
    layout/
        main.xml This layout file is loaded by default.
    layout-ar/
        main.xml This layout file is loaded for Arabic text.
    layout-ldrtl/
        main.xml This layout file is loaded only for non-Arabic
                 languages that use an RTL text direction.

Nota: Los recursos específicos de idioma tienen prioridad sobre los de la dirección del diseño, que, a su vez, tienen prioridad sobre los recursos predeterminados.

Cómo usar widgets compatibles

A partir de Android 4.2 (nivel de API 17), la mayoría de los elementos de la IU del marco de trabajo admiten automáticamente la dirección de texto RTL. Sin embargo, varios elementos del marco de trabajo, como ViewPager, no lo hacen.

Los widgets de pantalla principal admiten la dirección de texto RTL siempre que sus correspondientes archivos de manifiesto incluyan la asignación de atributos android:supportsRtl="true".

Cómo brindar compatibilidad con apps heredadas

Si tu app se dirige a Android 4.1.1 (nivel de API 16) o versiones anteriores, incluye también los atributos left y right, además de start y end.

Para comprobar si tu diseño debe utilizar la dirección de texto RTL, usa la siguiente lógica:

Kotlin

private fun shouldUseLayoutRtl(): Boolean {
    return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
        View.LAYOUT_DIRECTION_RTL == layoutDirection
    } else {
        false
    }
}

Java

private boolean shouldUseLayoutRtl() {
    if (android.os.Build.VERSION.SDK_INT >=
            android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return View.LAYOUT_DIRECTION_RTL == getLayoutDirection();
    } else {
        return false;
    }
}

Nota: Para evitar problemas de compatibilidad, usa la versión 23.0.1 o una posterior de Android SDK Build Tools.

Cómo realizar pruebas usando las opciones para desarrolladores

En los dispositivos que ejecutan Android 4.4 (nivel de API 19) o una versión posterior, puedes habilitar Force RTL layout direction desde las opciones para desarrolladores en el dispositivo. Esta configuración te permite ver el texto que utiliza secuencias de comandos LTR, como el texto en inglés, en modo RTL.

Cómo actualizar la lógica de una app

Esta sección describe lugares específicos en la lógica de tu app que debes actualizar cuando la adaptes para habilitar varias direcciones de texto.

Cambios en las propiedades

Para implementar un cambio en cualquier propiedad relacionada con RTL, como la dirección de diseño, los parámetros de diseño, el relleno, la dirección del texto, la alineación del texto o la posición del elemento de diseño, usa la devolución de llamada onRtlPropertiesChanged(). Esto te permite obtener la dirección de diseño actual y actualizar los objetos View de la actividad según corresponda.

Vistas

Si quieres crear un widget de IU que no forme parte directamente de la jerarquía de vistas de una actividad, como un cuadro de diálogo o una notificación, establece la dirección de diseño correcta en función del contexto. El siguiente fragmento de código demuestra cómo completar este proceso:

Kotlin

val config: Configuration = context.resources.configuration
view.layoutDirection = config.layoutDirection

Java

final Configuration config =
    getContext().getResources().getConfiguration();
view.setLayoutDirection(config.getLayoutDirection());

Varios métodos de la clase View requieren consideración adicional:

onMeasure()
Las medidas de vista pueden variar según la dirección del texto.
onLayout()
Si creas tu propia implementación de diseño, deberás invocar a super() en tu versión de onLayout() y adaptar tu lógica personalizada para admitir secuencias de comandos RTL.
onDraw()
Si quieres implementar una vista personalizada o agregar funcionalidad avanzada a un elemento de diseño, deberás actualizar el código para que sea compatible con las secuencias de comandos RTL. Utiliza el siguiente código para determinar si tu widget está en modo RTL:

Kotlin

// On devices running Android 4.1.1 (API level 16) and lower,
// you can call the isLayoutRtl() system method directly.
fun isLayoutRtl(): Boolean = layoutDirection == LAYOUT_DIRECTION_RTL

Java

// On devices running Android 4.1.1 (API level 16) and lower,
// you can call the isLayoutRtl() system method directly.
public boolean isLayoutRtl() {
    return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}

Elementos de diseño

Si tienes un elemento de diseño que debes espejar para un diseño RTL, completa uno de estos pasos según la versión de Android que ejecute el dispositivo:

  • En dispositivos que ejecutan Android 4.3 (nivel de API 18) o una versión anterior, es necesario agregar y definir los archivos de recurso -ldrtl.
  • En Android 4.4 (nivel de API 19) y versiones posteriores, puedes utilizar android:autoMirrored="true" al definir el elemento de diseño, lo que permite que el sistema gestione por ti el espejado del diseño RTL.

    Nota: El atributo android:autoMirrored solo funciona para los elementos de diseño sencillos cuyo espejado bidireccional es simplemente un espejado gráfico de todo el elemento. Si tu elemento de diseño contiene varios elementos, o si reflejarlo cambiaría su interpretación, debes realizar el espejado tú mismo. Siempre que sea posible, consulta con un experto en bidireccionalidad para determinar si tus elementos de diseño espejados tienen sentido para los usuarios.

Gravedad

Si el código de tu app es Gravity.LEFT o Gravity.RIGHT, tendrás que cambiar estos valores a Gravity.START y Gravity.END, respectivamente.

Por ejemplo, si usas el siguiente código:

Kotlin

when (gravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
    Gravity.LEFT -> {
        // Handle objects that are left-aligned.
    }
    Gravity.RIGHT -> {
        // Handle objects that are right-aligned.
    }
}

Java

switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    case Gravity.LEFT:
        // Handle objects that are left-aligned.
        break;
    case Gravity.RIGHT:
        // Handle objects that are right-aligned.
        break;
}

Deberás cambiarlo por:

Kotlin

val absoluteGravity: Int = Gravity.getAbsoluteGravity(gravity, layoutDirection)
when (absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
    Gravity.LEFT -> {
        // Handle objects that are left-aligned.
    }
    Gravity.RIGHT -> {
        // Handle objects that are right-aligned.
    }
}

Java

final int layoutDirection = getLayoutDirection();
final int absoluteGravity =
        Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    case Gravity.LEFT:
        // Handle objects that are left-aligned.
        break;
    case Gravity.RIGHT:
        // Handle objects that are right-aligned.
        break;
}

Esto significa que puedes controlar tu código existente que maneja valores alineados a la izquierda y a la derecha, incluso si usas start y end para los valores de gravedad.

Nota: Cuando apliques los ajustes de gravedad, usa una versión sobrecargada de Gravity.apply() que incluya un argumento layoutDirection.

Margen y relleno

Para admitir secuencias de comandos RTL en tu app, implementa estas prácticas recomendadas relacionadas con los valores de margen y relleno:

Consulta también