React Router v7에서 오른쪽에서 왼쪽(RTL) 언어 지원하는 방법
아랍어와 히브리어를 위한 레이아웃 미러링
문제
대부분의 디자인 시스템은 텍스트가 왼쪽에서 오른쪽으로 흐른다고 가정합니다. 내비게이션은 왼쪽에서 시작하고, 사이드바는 왼쪽에 고정되며, 콘텐츠는 왼쪽에서 오른쪽으로 읽힙니다. 그러나 아랍어와 히브리어는 오른쪽에서 왼쪽으로 읽히며, 그들의 레이아웃은 그에 맞게 미러링되어야 합니다—영어에서 왼쪽에 표시되는 것이 아랍어에서는 오른쪽에 표시되어야 합니다. 이러한 미러링이 없으면 전체 인터페이스가 거꾸로 느껴집니다. 시각적 흐름이 읽기 방향과 모순되어, 사용자가 자연스럽게 읽기 위해 레이아웃과 싸워야 하는 혼란스러운 경험을 만듭니다.
이 문제는 단순한 텍스트 정렬을 넘어섭니다. 여백, 패딩, 테두리, 위치 지정 모두 적응해야 합니다. 영어에서 왼쪽 여백이 있는 버튼은 아랍어에서 오른쪽 여백이 있어야 합니다. 오른쪽을 가리키는 아이콘은 왼쪽을 가리켜야 합니다. 인터페이스의 전체 공간 논리가 읽기 방향에 맞게 뒤집혀야 합니다.
해결책
문서의 <html> 요소에 dir 속성을 설정하여 현재 언어의 텍스트 방향을 지정합니다. 영어와 같은 왼쪽에서 오른쪽으로 읽는 언어에는 ltr을, 아랍어와 히브리어와 같은 오른쪽에서 왼쪽으로 읽는 언어에는 rtl을 사용합니다. 이 단일 속성으로 브라우저는 많은 레이아웃 동작을 자동으로 미러링합니다.
margin-left와 같은 물리적 속성 대신 margin-inline-start와 같은 CSS 논리적 속성을 사용하여 레이아웃을 설계하면 텍스트 방향이 변경될 때 간격이 자동으로 조정됩니다. 논리적 속성은 방향에 구애받지 않습니다—고정된 화면 위치가 아닌 텍스트 흐름에 상대적으로 간격을 정의합니다. 문서 방향이 RTL일 때, margin-inline-start는 margin-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 모드에서 화살표 아이콘이 수평으로 뒤집히면서 논리적 속성을 통해 텍스트와 간격이 방향을 인식하게 됩니다. 모든 아이콘을 뒤집을 필요는 없으며, 방향이나 움직임을 나타내는 아이콘만 뒤집으면 됩니다.