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モードでは矢印アイコンが水平方向に反転し、テキストとスペースは論理的プロパティを通じて方向を認識します。すべてのアイコンを反転させる必要はなく、方向や動きを示すものだけを反転させます。