Cómo soportar idiomas de derecha a izquierda (RTL) en TanStack Start v1

Diseños espejo para árabe y hebreo

Problema

La mayoría de los diseños web asumen que el texto fluye de izquierda a derecha. Los menús de navegación se anclan a la izquierda, las barras laterales aparecen a la izquierda y el contenido se lee de izquierda a derecha. Para idiomas como el árabe y el hebreo que se leen de derecha a izquierda, esto crea una experiencia desorientadora donde el flujo visual contradice la dirección de lectura. Los usuarios ven la navegación en el lado incorrecto, los iconos posicionados de manera incómoda y diseños que se sienten al revés. Sin un soporte adecuado para RTL, la interfaz se vuelve difícil de navegar y entender para los hablantes de idiomas RTL.

Solución

Establecer la dirección del texto del documento dinámicamente según la configuración regional actual, permitiendo que el navegador refleje automáticamente el diseño para idiomas RTL. Utilizar propiedades lógicas de CSS en lugar de propiedades direccionales físicas para que el espaciado, el posicionamiento y la alineación se adapten a la dirección del texto sin código adicional. Este enfoque permite que los diseños permanezcan agnósticos a la dirección: lo que aparece al "inicio" del contenido en inglés aparecerá correctamente al "inicio" (lado derecho) en árabe, con el navegador manejando la transformación.

Pasos

1. Determinar la dirección del texto desde la configuración regional

Crear una función auxiliar que devuelva la dirección del texto para una configuración regional determinada utilizando la API de internacionalización integrada del navegador.

export function getTextDirection(locale: string): "ltr" | "rtl" {
  try {
    const localeObj = new Intl.Locale(locale);
    if (
      "getTextInfo" in localeObj &&
      typeof localeObj.getTextInfo === "function"
    ) {
      return localeObj.getTextInfo().direction;
    }
  } catch (e) {
    console.warn(`Could not determine direction for locale: ${locale}`);
  }

  const rtlLocales = ["ar", "he", "fa", "ur"];
  const lang = locale.split("-")[0];
  return rtlLocales.includes(lang) ? "rtl" : "ltr";
}

Esta función utiliza Intl.Locale.getTextInfo() cuando está disponible y recurre a una lista de idiomas RTL conocidos como alternativa. Devuelve 'ltr' o 'rtl' según la configuración regional.

2. Establece el atributo dir en el elemento html

En tu ruta raíz, recupera el idioma actual y aplica la dirección correspondiente al elemento <html> del documento.

import {
  createRootRoute,
  Outlet,
  Scripts,
  HeadContent,
} from "@tanstack/react-router";
import { useIntl } from "react-intl";
import { getTextDirection } from "~/utils/text-direction";

export const Route = createRootRoute({
  component: RootComponent,
});

function RootComponent() {
  return (
    <RootDocument>
      <Outlet />
    </RootDocument>
  );
}

function RootDocument({ children }: { children: React.ReactNode }) {
  const intl = useIntl();
  const dir = getTextDirection(intl.locale);

  return (
    <html lang={intl.locale} dir={dir}>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  );
}

El atributo dir en el elemento <html> indica al navegador que invierta el diseño para idiomas RTL. Flexbox, grid y el contenido en línea se reflejan automáticamente.

3. Reemplaza las propiedades físicas de CSS con propiedades lógicas

Actualiza tus hojas de estilo para usar propiedades lógicas que respondan a la dirección del texto en lugar de direcciones físicas fijas.

.sidebar {
  padding-inline-start: 1rem;
  margin-inline-end: 2rem;
  border-inline-start: 1px solid #ccc;
}

.icon {
  margin-inline-end: 0.5rem;
}

.card {
  inset-inline-start: 0;
  text-align: start;
}

Las propiedades lógicas como padding-inline-start se mapean a padding-left en LTR y padding-right en RTL. El navegador aplica la propiedad física correcta basándose en el atributo dir, por lo que tus estilos funcionan en ambas direcciones sin duplicación.

4. Utiliza valores de alineación agnósticos a la dirección

Reemplaza las palabras clave de alineación física con las lógicas en tu CSS y estilos en línea.

export function Header() {
  return (
    <header
      style={{
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
      }}
    >
      <nav style={{ display: "flex", gap: "1rem" }}>
        <a href="/">Home</a>
        <a href="/about">About</a>
      </nav>
      <div style={{ textAlign: "end" }}>
        <button>Menu</button>
      </div>
    </header>
  );
}

Usar textAlign: 'end' en lugar de 'right' asegura que el texto se alinee al final de la dirección de lectura. Las propiedades de Flexbox como justifyContent y alignItems respetan automáticamente la dirección establecida por el atributo dir.