Cómo dar soporte a idiomas de derecha a izquierda (RTL) en React Router v7
Espejar diseños para árabe y hebreo
Problema
La mayoría de los sistemas de diseño asumen que el texto fluye de izquierda a derecha. La navegación comienza en la izquierda, las barras laterales se anclan a la izquierda y el contenido se lee de izquierda a derecha. Pero el árabe y el hebreo se leen de derecha a izquierda, y sus diseños deberían reflejarse en consecuencia—lo que aparece a la izquierda en inglés debería aparecer a la derecha en árabe. Sin este reflejo, toda la interfaz se siente al revés. El flujo visual contradice la dirección de lectura, creando una experiencia desorientadora donde los usuarios deben luchar contra el diseño para leer de forma natural.
El desafío va más allá de la simple alineación del texto. Márgenes, relleno, bordes y posicionamiento necesitan adaptarse. Un botón con margen izquierdo en inglés debería tener margen derecho en árabe. Los iconos que apuntan a la derecha deberían apuntar a la izquierda. Toda la lógica espacial de la interfaz debe invertirse para coincidir con la dirección de lectura.
Solución
Establece el atributo dir en el elemento <html> del documento para especificar la dirección del texto para el idioma actual. Usa ltr para idiomas de izquierda a derecha como el inglés y rtl para idiomas de derecha a izquierda como el árabe y el hebreo. Este único atributo hace que los navegadores reflejen automáticamente muchos comportamientos de diseño.
Diseña layouts utilizando propiedades lógicas de CSS como margin-inline-start en lugar de propiedades físicas como margin-left, para que el espaciado se adapte automáticamente cuando cambia la dirección del texto. Las propiedades lógicas son agnósticas a la dirección—definen el espaciado relativo al flujo del texto en lugar de posiciones fijas en la pantalla. Cuando la dirección del documento es RTL, margin-inline-start se convierte en margin-right, y el diseño se refleja sin código adicional.
Pasos
1. Detectar la dirección del texto del idioma actual
React Router 7 requiere una ruta raíz en app/root.tsx que renderice el documento HTML. Crea una función auxiliar que mapee los códigos de idioma a su dirección de texto.
const locales = {
en: { dir: "ltr" },
ar: { dir: "rtl" },
he: { dir: "rtl" },
es: { dir: "ltr" },
};
function getTextDirection(locale: string): "ltr" | "rtl" {
return locales[locale as keyof typeof locales]?.dir || "ltr";
}
Esta función devuelve la dirección apropiada para cada idioma soportado, utilizando por defecto de izquierda a derecha para idiomas desconocidos.
2. Establece el atributo dir en el elemento html
En tu layout raíz, recupera el idioma actual y aplica la dirección correspondiente al documento.
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" dir="ltr">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function Root() {
return <Outlet />;
}
Reemplaza el valor codificado dir="ltr" con un valor dinámico basado en tu mecanismo de detección de idioma. Si tu aplicación almacena el idioma actual en un loader o contexto, léelo aquí y pásalo a getTextDirection.
3. Utiliza propiedades lógicas para el espaciado
Reemplaza las propiedades CSS físicas con sus equivalentes lógicos en tus componentes y hojas de estilo.
export default function Card({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<div
style={{
paddingInlineStart: "1rem",
paddingInlineEnd: "1rem",
marginInlineStart: "auto",
borderInlineStart: "4px solid blue",
}}
>
<h2>{title}</h2>
{children}
</div>
);
}
Cuando se establece dir="rtl", paddingInlineStart se aplica al lado derecho y paddingInlineEnd al lado izquierdo, reflejando automáticamente el diseño. El mismo componente funciona correctamente tanto en contextos LTR como RTL sin lógica condicional.
4. Aplica propiedades lógicas a los contenedores de diseño
Utiliza propiedades lógicas para patrones de diseño comunes como barras de navegación y cuadrículas de contenido.
export default function Navigation() {
return (
<nav
style={{
display: "flex",
gap: "1rem",
paddingInline: "2rem",
borderBlockEnd: "1px solid #ccc",
}}
>
<a href="/" style={{ marginInlineEnd: "auto" }}>
Home
</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
);
}
paddingInline establece el padding en ambos lados inline, y marginInlineEnd: "auto" empuja el contenido hacia el borde inline-start, que cambia de izquierda a derecha cuando cambia la dirección. El diseño de navegación se refleja automáticamente para idiomas RTL.
5. Manejar iconos y gráficos direccionales
Para iconos que representan dirección o flujo, inviértelos condicionalmente según la dirección del texto.
function BackButton() {
const dir = document.documentElement.dir;
const iconStyle = {
transform: dir === "rtl" ? "scaleX(-1)" : "none",
marginInlineEnd: "0.5rem",
};
return (
<button>
<span style={iconStyle}>←</span>
Back
</button>
);
}
Esto invierte horizontalmente el icono de flecha en modo RTL mientras mantiene el texto y el espaciado conscientes de la dirección mediante propiedades lógicas. No todos los iconos necesitan invertirse—solo aquellos que indican dirección o movimiento.