React Router v7에서 오른쪽에서 왼쪽(RTL) 언어를 지원하는 방법

아랍어와 히브리어를 위한 레이아웃 미러링

문제

대부분의 디자인 시스템은 텍스트가 왼쪽에서 오른쪽으로 흐른다고 가정합니다. 내비게이션은 왼쪽에서 시작하고, 사이드바는 왼쪽에 고정되며, 콘텐츠는 왼쪽에서 오른쪽으로 읽힙니다. 하지만 아랍어와 히브리어는 오른쪽에서 왼쪽으로 읽히며, 레이아웃도 그에 맞게 미러링되어야 합니다. 영어에서 왼쪽에 표시되는 것은 아랍어에서 오른쪽에 표시되어야 합니다. 이러한 미러링이 없으면 전체 인터페이스가 거꾸로 느껴집니다. 시각적 흐름이 읽기 방향과 모순되어 사용자가 자연스럽게 읽기 위해 레이아웃과 싸워야 하는 혼란스러운 경험을 만듭니다.

문제는 단순한 텍스트 정렬을 넘어 확장됩니다. 여백, 패딩, 테두리, 위치 지정 모두 적응해야 합니다. 영어에서 왼쪽 여백이 있는 버튼은 아랍어에서 오른쪽 여백을 가져야 합니다. 오른쪽을 가리키는 아이콘은 왼쪽을 가리켜야 합니다. 인터페이스의 전체 공간 논리가 읽기 방향에 맞춰 뒤집혀야 합니다.

해결책

문서의 <html> 요소에 dir 속성을 설정하여 현재 언어의 텍스트 방향을 지정하세요. 영어와 같은 왼쪽에서 오른쪽 언어에는 ltr를 사용하고, 아랍어 및 히브리어와 같은 오른쪽에서 왼쪽 언어에는 rtl를 사용하세요. 이 단일 속성으로 브라우저가 많은 레이아웃 동작을 자동으로 미러링합니다.

margin-left와 같은 물리적 속성 대신 margin-inline-start와 같은 CSS 논리 속성을 사용하여 레이아웃을 디자인하면, 텍스트 방향이 변경될 때 간격이 자동으로 조정됩니다. 논리 속성은 방향에 구애받지 않으며, 고정된 화면 위치가 아닌 텍스트 흐름을 기준으로 간격을 정의합니다. 문서 방향이 RTL일 때 margin-inline-startmargin-right가 되며, 추가 코드 없이 레이아웃이 자동으로 미러링됩니다.

단계

1. 현재 로케일의 텍스트 방향 감지

React Router 7은 HTML 문서를 렌더링하는 app/root.tsx의 루트 라우트가 필요합니다. 로케일 코드를 텍스트 방향에 매핑하는 헬퍼 함수를 생성하세요.

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";
}

이 함수는 지원되는 각 로케일에 적합한 방향을 반환하며, 알 수 없는 로케일의 경우 기본적으로 왼쪽에서 오른쪽 방향을 사용합니다.

2. html 요소에 dir 속성 설정

루트 레이아웃에서 현재 로케일을 가져와 문서에 해당 방향을 적용하세요.

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 />;
}

하드코딩된 dir="ltr"를 로케일 감지 메커니즘을 기반으로 한 동적 값으로 교체하세요. 앱이 현재 로케일을 로더나 컨텍스트에 저장하는 경우, 여기서 읽어서 getTextDirection에 전달하세요.

3. 간격에 논리적 속성 사용

컴포넌트와 스타일시트에서 물리적 CSS 속성을 논리적 속성으로 교체하세요.

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>
  );
}

dir="rtl"가 설정되면 paddingInlineStart는 오른쪽에 적용되고 paddingInlineEnd는 왼쪽에 적용되어 레이아웃이 자동으로 미러링됩니다. 동일한 컴포넌트가 조건부 로직 없이 LTR 및 RTL 컨텍스트 모두에서 올바르게 작동합니다.

4. 레이아웃 컨테이너에 논리적 속성 적용

내비게이션 바 및 콘텐츠 그리드와 같은 일반적인 레이아웃 패턴에 논리적 속성을 사용하세요.

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는 양쪽 인라인 측면에 패딩을 설정하고, marginInlineEnd: "auto"는 콘텐츠를 인라인 시작 가장자리로 밀어내며, 방향이 변경되면 왼쪽에서 오른쪽으로 전환됩니다. 내비게이션 레이아웃은 RTL 언어에 대해 자동으로 미러링됩니다.

5. 아이콘 및 방향성 그래픽 처리

방향이나 흐름을 나타내는 아이콘의 경우, 텍스트 방향에 따라 조건부로 뒤집으세요.

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>
  );
}

이는 RTL 모드에서 화살표 아이콘을 수평으로 뒤집으면서 논리적 속성을 통해 텍스트와 간격을 방향 인식 상태로 유지합니다. 모든 아이콘을 뒤집을 필요는 없으며, 방향이나 움직임을 나타내는 아이콘만 뒤집으면 됩니다.