Cómo soportar idiomas de derecha a izquierda (RTL) en React Router v7

Reflejar 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 a 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 deben reflejarse en consecuencia: lo que aparece a la izquierda en inglés debe 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 se extiende más allá de la simple alineación del texto. Los márgenes, el relleno, los bordes y el posicionamiento deben adaptarse. Un botón con margen izquierdo en inglés debe tener margen derecho en árabe. Los iconos que apuntan a la derecha deben 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 los diseños usando 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 cambie la dirección del texto. Las propiedades lógicas son independientes de la dirección: definen el espaciado en relación con el 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 automáticamente sin código adicional.

Pasos

1. Detectar la dirección del texto de la configuración regional 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 configuración regional 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 configuración regional compatible, con un valor predeterminado de izquierda a derecha para configuraciones regionales desconocidas.

2. Establecer el atributo dir en el elemento html

En tu diseño raíz, recupera la configuración regional 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 configuración regional. Si tu aplicación almacena la configuración regional actual en un loader o contexto, léela aquí y pásala a getTextDirection.

3. Usar 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 dir="rtl" está establecido, paddingInlineStart se aplica al lado derecho y paddingInlineEnd al izquierdo, reflejando automáticamente el diseño. El mismo componente funciona correctamente en contextos LTR y RTL sin lógica condicional.

4. Aplicar propiedades lógicas a contenedores de diseño

Usa 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, voltéalos 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 voltea el icono de flecha horizontalmente en modo RTL mientras mantiene el texto y el espaciado conscientes de la dirección a través de propiedades lógicas. No todos los iconos necesitan voltearse, solo aquellos que indican dirección o movimiento.