wandres.dev
LOCALIZACIÓN · idiomas y regiones

Layout internacional: dirección, elasticidad y pseudoidiomas

Traducir los textos es la parte fácil; sobrevivir a ellos es la difícil. Un idioma que se lee de derecha a izquierda invierte la geometría entera de la interfaz, el alemán alarga las etiquetas hasta un cincuenta por ciento y el árabe exige más altura de línea. Esta lección explica cómo se escribe una interfaz sin lados fijos, qué se refleja y qué no, cómo se prueba en alemán y en árabe sin tener una sola traducción, y qué detalles tipográficos rompen lo que parecía terminado.

⏱ 18 min

Hay una asimetría curiosa en cómo se distribuye el esfuerzo de internacionalizar. La traducción, que es la parte visible, cuesta dinero pero es un proceso conocido y acotado; el layout, que nadie presupuesta, es donde aparecen los defectos que obligan a rehacer pantallas. La razón es que el diseño original codifica sin querer un conjunto de supuestos sobre el texto que lo va a habitar: que se lee hacia la derecha, que una etiqueta de botón cabe en ochenta puntos, que un icono de flecha apunta hacia delante, que dos líneas bastan. Ninguno de esos supuestos es una propiedad del diseño; todos son propiedades del inglés. Una interfaz internacional no es una interfaz traducida: es una interfaz que dejó de suponer, y esa renuncia se hace en el momento de escribirla o no se hace nunca a coste razonable.

🎯 Al terminar esta lección sabrás
  • Escribir layouts sin lados absolutos y controlar el reflejo en idiomas RTL.
  • Distinguir qué elementos deben reflejarse, cuáles no y cómo forzar cada caso.
  • Diseñar para la elasticidad del texto y su combinación con tamaños de tipografía grandes.
  • Probar en alemán y en árabe con pseudoidiomas antes de tener ninguna traducción.

Dirección: no hay izquierda ni derecha

El árabe, el hebreo, el persa y el urdu se leen de derecha a izquierda, y eso no afecta solo al texto: invierte el eje de progresión de toda la interfaz. La barra de navegación coloca el botón de retroceso a la derecha, las filas alinean su contenido al otro lado, el indicador de detalle apunta al lado contrario, los gestos de deslizamiento cambian de sentido y hasta el orden de lectura de un formulario se refleja. El sistema hace la mayor parte de ese trabajo por su cuenta, pero solo si el código no le ha impedido hacerlo.

La regla operativa es que en una interfaz internacional no existen los conceptos de izquierda y derecha: existen inicio y final, que el sistema resuelve según la dirección activa. SwiftUI está construido sobre esa idea, de modo que las alineaciones leading y trailing, los rellenos por borde y las pilas horizontales ya se comportan correctamente. Los problemas aparecen justo donde alguien decidió ser explícito.

// Mal: fija la geometría a una dirección concreta
.padding(.left, 16)
.frame(maxWidth: .infinity, alignment: .init(horizontal: .leading, vertical: .center))
.offset(x: 12)

// Bien: relativo a la dirección de lectura
.padding(.leading, 16)
.frame(maxWidth: .infinity, alignment: .leading)

Los desplazamientos por coordenada son el caso que más se escapa, porque no tienen versión relativa: un valor positivo en el eje horizontal siempre va hacia la derecha física, también en árabe. Lo mismo ocurre con las transformaciones, los gradientes con puntos de inicio y fin y cualquier dibujo hecho en un Canvas. En todos esos sitios hay que leer la dirección del entorno y decidir el signo.

@Environment(\.layoutDirection) private var direccion

var body: some View {
    Insignia()
        .offset(x: direccion == .rightToLeft ? -12 : 12)
}

Queda la pregunta que más discusiones genera: qué se refleja y qué no. La respuesta no es estética sino semántica, y se resume en un criterio: se refleja lo que representa progresión o dirección de lectura, y no se refleja lo que representa una realidad física o un sentido temporal universal.

↩️

Sí se refleja

Flechas de navegación, indicadores de detalle, barras de progreso, deslizadores y cualquier icono que signifique avanzar o retroceder.

▶️

No se refleja

Los controles de reproducción, los relojes analógicos, las cifras y los símbolos de objetos del mundo real que no cambian de forma.

🔢

Islas de dirección

Números, códigos y direcciones de correo mantienen su orden dentro de un párrafo RTL. El algoritmo bidireccional lo resuelve solo.

Los símbolos del sistema ya llevan esa distinción codificada y se reflejan solos cuando corresponde. Para un recurso propio, el catálogo de imágenes permite declarar la dirección del reflejo, y desde código existe el modificador que fuerza el volteo de una vista concreta. Conviene usarlo con parsimonia: el reflejo indiscriminado de una pantalla completa produce logotipos invertidos y texto ilegible.

⚠️
El texto mixto no se arregla con alineación

Una frase en árabe que contiene un número de teléfono, una dirección de correo o un nombre de marca en alfabeto latino activa el algoritmo bidireccional de Unicode, que resuelve por su cuenta el orden de cada tramo. El resultado suele ser correcto, pero los caracteres neutros de los extremos, como paréntesis, dos puntos o signos, pueden quedar en el lado equivocado. La solución no es cambiar la alineación de la vista sino insertar marcas de dirección en la cadena, y esa decisión pertenece a quien traduce, no a quien maqueta.

Texto que crece

La segunda fuente de roturas es la elasticidad. Las estimaciones habituales del sector, recogidas en las guías de internacionalización de las grandes plataformas, sitúan la expansión respecto del inglés en torno al treinta por ciento para textos medios, y muy por encima para textos cortos: una palabra de cinco caracteres puede triplicarse. El alemán es el caso arquetípico por sus compuestos, pero el finés, el ruso y el francés canadiense compiten de cerca. En la dirección contraria, el chino y el coreano contraen mucho, y una etiqueta pensada para dos líneas puede quedar ridículamente corta.

De ahí se sigue una regla de diseño que conviene tratar como no negociable: ningún contenedor de texto lleva ancho fijo. Un frame con ancho constante alrededor de una etiqueta es una afirmación sobre la longitud de una frase que no ha sido traducida todavía. La alternativa es dejar que el texto negocie su tamaño y controlar solo lo que de verdad importa, que es el número de líneas y el comportamiento al desbordar.

Text(titulo)
    .lineLimit(2)
    .minimumScaleFactor(0.8)      // último recurso, no política general
    .fixedSize(horizontal: false, vertical: true)

El orden de preferencia entre las salidas al desbordamiento importa más de lo que parece. Permitir más líneas es casi siempre mejor que truncar, y truncar es mejor que encoger, porque el texto encogido rompe la jerarquía tipográfica y a partir de cierto punto deja de ser legible para quien tiene baja visión. La reducción de escala debería reservarse a números y a etiquetas donde la pérdida de una palabra impide entender el control.

La elasticidad tiene además una consecuencia estructural que va más allá del ajuste de una etiqueta: cambia qué disposición es la correcta. Una fila con título a la izquierda y valor a la derecha es legible en inglés y se convierte en dos columnas estranguladas en alemán, donde la solución no es reducir el cuerpo de letra sino apilar los elementos. ViewThatFits expresa exactamente esa decisión, ofreciendo una alternativa vertical cuando la horizontal no cabe, y lo hace midiendo el texto real en lugar de comparar identificadores de idioma.

ViewThatFits(in: .horizontal) {
    HStack { Text(titulo); Spacer(); Text(valor) }
    VStack(alignment: .leading) { Text(titulo); Text(valor) }
}

El peor escenario no es el alemán por sí solo, sino el alemán combinado con el tamaño de texto máximo del sistema. Ese cruce multiplica los dos factores y es donde aparecen los recortes, los solapamientos y los botones cuyo rótulo desaparece. Diseñar pensando en él tiene además un efecto lateral valioso: una fila que sobrevive a esa combinación es una fila que se adapta a cualquier cosa, lo que incluye tamaños de ventana pequeños en iPad y en Mac.

Pseudoidiomas: probar sin traducciones

La objeción habitual a todo lo anterior es que no se puede comprobar hasta tener traducciones, y es falsa. Xcode incluye pseudoidiomas, que son transformaciones sintéticas del texto de origen que se activan como idioma del esquema de ejecución sin que exista ni una sola cadena traducida.

flowchart TB
a[Texto en el idioma de desarrollo] --> b[Pseudoidioma en el esquema]
b --> c[Doble longitud: revela recortes y solapamientos]
b --> d[Derecha a izquierda: revela lados fijos]
b --> e[Acentuado: revela texto no localizado]
b --> f[Delimitado: revela truncamiento silencioso]
c --> g[Defectos de layout antes de gastar en traducir]
d --> g
e --> g
f --> g

Cada uno de esos modos responde una pregunta distinta y conviene usarlos con intención en lugar de a la vez. El de doble longitud simula el peor caso de expansión y encuentra los anchos fijos. El de derecha a izquierda invierte la interfaz y localiza todo lo que quedó anclado a un lado físico. El acentuado sustituye las letras por variantes con diacríticos, de modo que cualquier texto que siga apareciendo en inglés limpio es texto que no está pasando por el catálogo, y esa es la forma más rápida que existe de encontrar cadenas sin extraer. Y el delimitado rodea cada cadena con marcas, lo que hace visible el truncamiento incluso cuando el resultado parece una frase razonable.

💡
El pseudoidioma acentuado es un detector de cadenas perdidas

Ejecutar la app entera con el pseudoidioma acentuado y recorrer los flujos principales anotando todo lo que se lee con normalidad produce en media hora una lista de textos no localizados que de otro modo aparecerían uno a uno durante meses. Es la única técnica del capítulo que encuentra defectos de extracción, no de layout, y por eso conviene ejecutarla antes que las demás.

Las previsualizaciones cubren el resto del ciclo, porque permiten ver varias combinaciones a la vez sin recompilar. Fijar el Locale y la dirección en el entorno de una previsualización es barato y convierte la comprobación en algo que ocurre mientras se escribe la vista, no en una fase posterior.

#Preview("Árabe, texto grande") {
    FilaTarea(tarea: .ejemplo)
        .environment(\.locale, Locale(identifier: "ar"))
        .environment(\.layoutDirection, .rightToLeft)
        .environment(\.dynamicTypeSize, .accessibility3)
}

Lo que no se ve en una captura

Quedan tres familias de detalles que sobreviven a todas las comprobaciones anteriores porque no producen un layout roto sino un resultado sutilmente pobre. La primera es tipográfica: las escrituras árabe, tailandesa, devanagari y similares tienen ascendentes y descendentes mayores que el alfabeto latino, de modo que una altura de línea ajustada al milímetro recorta los signos diacríticos. Un espaciado de línea fijado a mano en puntos, que en inglés se ve elegante, en árabe amputa la parte superior de las letras.

La segunda es la ordenación. El orden alfabético no es una propiedad universal de las cadenas: en sueco ciertas letras acentuadas van al final del alfabeto, en alemán la diéresis se ordena de dos maneras distintas según el contexto, y en varios idiomas hay dígrafos que cuentan como una sola letra. Ordenar comparando cadenas byte a byte produce listas que a un hablante nativo le parecen desordenadas. La comparación sensible a la configuración regional existe justo para eso.

La tercera es la entrada de datos. Un campo que valida un nombre con letras del alfabeto latino rechaza nombres perfectamente válidos; uno que espera un código postal numérico rechaza los del Reino Unido; uno que impone un número fijo de dígitos para un teléfono rechaza medio mundo. Cada validación estricta de formato es una decisión cultural disfrazada de comprobación técnica, y casi todas conviene relajarlas.

Un layout que se rompe al traducirlo no se rompió al traducirlo: ya estaba roto

Conviene desplazar la causa al sitio correcto, porque de ese desplazamiento se sigue una forma distinta de escribir vistas. Cuando una pantalla se descuadra en alemán, la lectura inmediata del equipo es que el alemán la rompió, y esa lectura conduce a una reparación puntual: se amplía el ancho, se añade una condición, se acorta la traducción. La lectura correcta es la contraria. Esa pantalla contenía desde el principio una afirmación falsa sobre el mundo, del tipo el rótulo de este botón mide como mucho ochenta puntos, y llevaba meses siendo falsa sin que nada la contradijera porque el único texto que se le había suministrado la cumplía por casualidad. El alemán no rompió nada: actuó como un caso de prueba que reveló una precondición no documentada. Vista así, la elasticidad del texto pertenece a la misma familia de fenómenos que la concurrencia y que los datos de red, es decir, la familia de las condiciones que un programa cumple durante mucho tiempo por suerte y deja de cumplir un día sin que nadie haya cambiado nada relevante. Y por eso las técnicas que funcionan son las mismas: no confiar en el caso feliz observado, sino hacer imposible expresar el supuesto. Una vista que no puede fijar anchos, que no puede nombrar la izquierda, que no puede suponer dos líneas, no necesita después arreglos por idioma, del mismo modo que un tipo que no puede representar un estado inválido no necesita comprobaciones defensivas. La implicación práctica va más allá de la localización y esta es la parte que conviene llevarse: la internacionalización es el mejor generador de casos adversos que tienes gratis. Un diseño que sobrevive al doble de longitud, a la dirección invertida y al cuerpo de letra máximo ha demostrado no depender de ninguna medida concreta, y esa propiedad es exactamente la que hace que la misma vista funcione después en una ventana estrecha, en un dispositivo nuevo, con un idioma que aún no existe en tu lista y con una tipografía que todavía no se ha diseñado. Se paga una vez y sirve para todos los futuros que no puedes prever.

📝
Lo esencial

En una interfaz internacional no hay izquierda ni derecha, solo inicio y final; los desplazamientos por coordenada y los dibujos manuales son la excepción y exigen leer la dirección del entorno. Se refleja lo que indica progresión, no lo que representa objetos físicos o reproducción. El texto se expande alrededor de un treinta por ciento y mucho más en etiquetas cortas, así que ningún contenedor de texto lleva ancho fijo y el peor caso es alemán con tipografía máxima. Los pseudoidiomas permiten probarlo todo sin traducciones, y el acentuado además encuentra cadenas sin extraer.

⚔️ Romper tu interfaz antes de traducirla
  1. Ejecuta la app con el pseudoidioma acentuado y anota cada texto que siga apareciendo legible: esa es tu lista de cadenas sin localizar.
  2. Ejecuta con el pseudoidioma de doble longitud y captura las cinco pantallas peores; corrige los anchos fijos en lugar de acortar los textos.
  3. Ejecuta en modo de derecha a izquierda y busca desplazamientos, gradientes e iconos que no se hayan reflejado o que se hayan reflejado mal.
  4. Combina árabe con el tamaño de texto de accesibilidad más grande y revisa el recorte de diacríticos en cabeceras con espaciado fijo.
  5. Ordena una lista de nombres con acentos y diéresis comparando con la configuración regional activa y compara el resultado con el orden por bytes.