TanStack Start v1で右から左(RTL)言語をサポートする方法
アラビア語とヘブライ語のレイアウトをミラーリング
問題
ほとんどのWebレイアウトは、テキストが左から右に流れることを前提としています。ナビゲーションメニューは左に配置され、サイドバーは左に表示され、コンテンツは左から右に読まれます。アラビア語やヘブライ語のように右から左に読む言語では、視覚的な流れが読む方向と矛盾するため、違和感のある体験となります。ユーザーはナビゲーションが間違った側にあり、アイコンが不自然に配置され、レイアウトが逆向きに感じられます。適切なRTLサポートがないと、RTL言語を使用するユーザーにとってインターフェースのナビゲートと理解が困難になります。
解決策
現在のロケールに基づいてドキュメントのテキスト方向を動的に設定し、ブラウザがRTL言語のレイアウトを自動的にミラーリングできるようにします。物理的な方向プロパティの代わりにCSSの論理プロパティを使用することで、追加のコードなしでスペーシング、配置、整列がテキスト方向に適応します。このアプローチにより、レイアウトは方向に依存しなくなります。英語でコンテンツの「開始」に表示されるものは、アラビア語では正しく「開始」(右側)に表示され、ブラウザが変換を処理します。
手順
1. ロケールからテキスト方向を決定
ブラウザの組み込み国際化APIを使用して、指定されたロケールのテキスト方向を返すヘルパー関数を作成します。
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";
}
この関数は、利用可能な場合はIntl.Locale.getTextInfo()を使用し、既知のRTL言語のリストにフォールバックします。ロケールに基づいて'ltr'または'rtl'のいずれかを返します。
2. html要素にdir属性を設定
ルートルートで現在のロケールを取得し、対応する方向をドキュメントの<html>要素に適用します。
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>
);
}
<html>要素のdir属性は、RTL言語のレイアウトを反転するようブラウザに指示します。Flexbox、グリッド、インラインコンテンツは自動的にミラーリングされます。
3. 物理的なCSSプロパティを論理プロパティに置き換える
固定された物理的な方向ではなく、テキスト方向に応答する論理プロパティを使用するようにスタイルシートを更新します。
.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;
}
padding-inline-startのような論理プロパティは、LTRではpadding-leftに、RTLではpadding-rightにマッピングされます。ブラウザはdir属性に基づいて正しい物理プロパティを適用するため、スタイルは重複することなく両方向で機能します。
4. 方向に依存しない配置値を使用する
CSSとインラインスタイルで物理的な配置キーワードを論理的なものに置き換えます。
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>
);
}
'right'の代わりにtextAlign: 'end'を使用することで、テキストが読み取り方向の終端に配置されます。justifyContentやalignItemsなどのFlexboxプロパティは、dir属性によって設定された方向を自動的に尊重します。