Next.js(Pages Router)v16で右から左(RTL)言語をサポートする方法

アラビア語とヘブライ語のレイアウトをミラーリング

問題

ほとんどのWebレイアウトは、左から右へのテキストフローを前提としています。ナビゲーションメニューは左に配置され、コンテンツは左から右へ読まれ、margin-leftpadding-rightなどのプロパティで余白が適用されます。アプリケーションがアラビア語やヘブライ語に翻訳されると、これらの言語は右から左へ読まれるため、視覚的なレイアウト全体をミラーリングして一致させる必要があります。ミラーリングがないと、ユーザーは視覚的な階層が自然な読み方向と矛盾する違和感のあるインターフェースを体験し、ナビゲーションが混乱し、アプリケーションが洗練されていないと感じます。

課題はテキストの配置だけにとどまりません。leftrightmargin-leftpadding-rightなどの物理的なCSSプロパティは、コンテンツフローではなく固定された画面位置に紐付いています。テキスト方向が変わっても、これらのプロパティは同じ物理的なエッジに固定されたままであり、レイアウトが自然に適応することを妨げます。

解決策

ドキュメントのテキスト方向属性を現在のロケールに合わせて設定し、ブラウザがRTL言語のレイアウトフローを自動的に反転できるようにします。物理的なCSSプロパティを、画面位置ではなくコンテンツフローを参照する論理プロパティに置き換えます。margin-inline-startpadding-inline-endなどの論理プロパティは、テキスト方向に基づいて正しい物理的なエッジに自動的にマッピングされ、追加のコードや条件付きスタイリングなしでレイアウトを自動的にミラーリングできます。

このアプローチにより、スタイルを複製したり複雑なロジックを追加したりすることなく、LTR言語とRTL言語の両方で正しく機能する方向に依存しないレイアウトが作成されます。

手順

1. ロケールからテキスト方向を判定するヘルパーを作成

論理プロパティはテキストの方向に基づいて適応し、margin-inline-startはLTRコンテキストではmargin-leftに、RTLコンテキストではmargin-rightに相当します。ロケールをテキストの方向にマッピングするユーティリティ関数を構築します。

export function getDirection(locale: string): "ltr" | "rtl" {
  const rtlLocales = ["ar", "he", "fa", "ur"];
  return rtlLocales.includes(locale) ? "rtl" : "ltr";
}

この関数はRTL言語を識別し、HTMLおよびCSSで使用する適切な方向値を返します。

2. ドキュメントの方向属性を設定する

カスタムDocumentクラスはrenderメソッドをオーバーライドして、langやdirなどの属性でHTML要素をカスタマイズできます。現在のロケールに基づいてルートHTML要素にdir属性を設定するカスタムDocumentを作成します。

import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from "next/document";
import { getDirection } from "../utils/direction";

class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);
    return initialProps;
  }

  render() {
    const locale = this.props.__NEXT_DATA__.locale || "en";
    const dir = getDirection(locale);

    return (
      <Html lang={locale} dir={dir}>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Next.jsルーターはlocaleプロパティを介して現在アクティブなロケールを提供します。ドキュメントはNext.jsルーティングデータからロケールを読み取り、対応する方向をHTML要素に適用することで、ブラウザレベルのRTLサポートを有効にします。

3. 物理的なCSSプロパティを論理的な同等物に置き換える

margin-inline-start、padding-inline-start、およびそれらの短縮形であるmargin-inlineやpadding-inlineなどの論理プロパティは、書字方向と方向に基づいて自動的に物理プロパティにマッピングされます。コンポーネントのスタイルを更新して、フロー相対プロパティを使用します。

export default function Navigation() {
  return (
    <nav className="nav">
      <ul className="nav-list">
        <li className="nav-item">Home</li>
        <li className="nav-item">About</li>
        <li className="nav-item">Contact</li>
      </ul>
    </nav>
  );
}
.nav {
  padding-inline: 1rem;
  border-inline-start: 4px solid blue;
}

.nav-list {
  display: flex;
  gap: 1rem;
  margin-block: 0;
  padding-inline-start: 0;
}

.nav-item {
  margin-inline-end: 1.5rem;
}

margin、padding、insetの論理プロパティにより、書字モード全体で要素の配置がより簡単かつ効率的になります。これらのプロパティは、追加のスタイルやメディアクエリなしで、RTLコンテキストで自動的に反転します。

4. 配置に論理プロパティを使用する

絶対配置または相対配置された要素の場合、leftrightinset-inline-startinset-inline-endに置き換えます。

export default function Sidebar() {
  return (
    <aside className="sidebar">
      <button className="close-button">×</button>
      <p>Sidebar content</p>
    </aside>
  );
}
.sidebar {
  position: fixed;
  inset-inline-start: 0;
  inset-block-start: 0;
  inline-size: 250px;
  block-size: 100vh;
  padding-inline: 1rem;
}

.close-button {
  position: absolute;
  inset-inline-end: 0.5rem;
  inset-block-start: 0.5rem;
}

論理プロパティの方法論では、widthプロパティはinline-sizeに、heightはblock-sizeに置き換えられます。これらのプロパティにより、配置された要素が現在のテキスト方向に対して正しい側に表示されることが保証されます。

5. 論理的なテキスト配置を適用する

text-align: lefttext-align: righttext-align: starttext-align: endに置き換えます。

.content {
  text-align: start;
}

.metadata {
  text-align: end;
  margin-inline-start: auto;
}

論理プロパティは、物理的なビューポートの寸法ではなく、コンテンツのフローに関連するボックスのエッジを参照します。startendのテキスト配置値は、テキスト方向に自動的に適応し、LTR言語とRTL言語の両方に対してコンテンツを適切に配置します。