目次
React i18n: Reactアプリの国際化完全ガイド
国際化(i18n)は、外から見るとシンプルに見えるのに、実装を始めると何週間もかかるラビットホールになりがちな機能の一つです。英語でユーザーを迎えるReactアプリが真にグローバルなオーディエンスに対応するには、単純な文字列の置き換え以上のものが必要です。通貨のフォーマット、複数形のルール、日付のローカライズ、右から左へのレイアウトサポート、そして開発者がキーを変更するたびに壊れないような翻訳ワークフローが必要です。
このガイドでは、2026年にプロダクション対応の多言語Reactアプリをリリースするために知っておくべきすべてのことを説明します。適切なライブラリの選択から、後で時間のロスとなるミスを避ける方法まで網羅しています。
TL;DR / 重要なポイント
- Reactにはi18nが組み込まれていません。ブラウザの
IntlAPIはフォーマットを担当しますが、翻訳文字列の管理、動的ローディング、ロケールルーティングには専用のライブラリが必要です。 - react-intl(FormatJS)とreact-i18nextは、大きなコミュニティと長年のプロダクション実績を持つ最も広く採用されているオプションです。
- LinguiJSは、コンパイル時の抽出と最小限のランタイムバンドルサイズを求める場合に最適な選択肢です。
- Better i18nは最新の参入者で、AIによる翻訳とCDN配信を追加していますが、コミュニティは確立された代替案より小さいです。
- 適切なライブラリはチームの規模、SSRの要件、翻訳ワークフローの自動化とエコシステムの成熟度のどちらを重視するかによって異なります。
ReactにはなぜI18nライブラリが必要か
ReactはUIをレンダリングします。翻訳の管理、ブラウザのロケール検出、言語ファイルの非同期ローディング、言語間で異なる複雑なフォーマットルールの処理は行いません。これは設計上の意図です。Reactはビューライブラリです。
ブラウザにはIntl APIが搭載されており、これは本当に強力です。Intl.DateTimeFormat、Intl.NumberFormat、Intl.PluralRulesは外部依存なしでロケール対応のフォーマットを提供します。しかしIntlはフォーマットを解決するのであって、文字列管理ではありません。
Intlを超えてまだ必要なもの:
- 文字列カタログ — ロケールとメッセージIDでキー付けされた翻訳文字列を格納する構造。
- Reactバインディング — カタログから読み取り、ロケールが変わると再レンダリングするフックとコンポーネント。
- 動的ローディング — 全ての文字列を一度にではなく、アクティブなロケールの文字列のみをローディング。
- 複数形と補間 —
"1個のアイテム"対"3個のアイテム"の処理と、ユーザー名などの動的な値をメッセージに挿入。 - 翻訳ワークフロー — 翻訳者が新しい文字列を受け取り、翻訳し、その翻訳がアプリに戻ってくる方法。
これを自分で全部行うことは可能ですが、お勧めしません。以下のライブラリは、何百万ものプロダクションデプロイメントでこれらの問題を解決してきました。そのうちの一つから始めてください。
人気のReact i18nライブラリ比較
| ライブラリ | バンドルサイズ | ICUサポート | TypeScriptサポート | SSRサポート | 学習曲線 |
|---|---|---|---|---|---|
| react-intl (FormatJS) | ~20KB gzipped | フル | 強い | あり | 中程度 |
| react-i18next | ~6KB gzipped | プラグイン経由 | 強い | あり | 低〜中 |
| LinguiJS | ~2KB runtime | フル | 強い | あり | 中〜高 |
| Better i18n React SDK | ~2KB | 部分的 | フル(推論) | あり | 低 |
バンドルサイズはおおよその数値で、ツリーシェイキングと使用する機能によって異なります。ICU(International Components for Unicode)メッセージフォーマットは、複雑な複数形、性別選択、条件付きテキストの標準です。全てのライブラリがネイティブでサポートしているわけではありません。
react-intl(FormatJS)
react-intlはFormatlyが管理するFormatJSスイートの一部です(旧Yahoo)。Reactにおける複雑なi18nの事実上の標準として長年君臨しており、エコシステムの中で最も完全なICUメッセージフォーマットの実装を提供しています。
インストール
npm install react-intl
IntlProviderでアプリをラップする
IntlProviderはコンポーネントツリー全体のロケールコンテキストを確立します。その下にある全てのFormattedMessageとuseIntlの呼び出しはこのコンテキストから読み取ります。
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { IntlProvider } from "react-intl";
import App from "./App";
import enMessages from "./locales/en.json";
import frMessages from "./locales/fr.json";
const messages: Record<string, Record<string, string>> = {
en: enMessages,
fr: frMessages,
};
const userLocale = navigator.language.split("-")[0] ?? "en";
const activeLocale = userLocale in messages ? userLocale : "en";
ReactDOM.createRoot(document.getElementById("root")!).render(
<IntlProvider locale={activeLocale} messages={messages[activeLocale]}>
<App />
</IntlProvider>
);
メッセージカタログはフラットなJSONファイルです:
// src/locales/en.json
{
"greeting": "Hello, {name}!",
"itemCount": "{count, plural, one {# item} other {# items}}",
"welcomeBack": "Welcome back, <bold>{name}</bold>!"
}
FormattedMessageコンポーネント
翻訳内にリッチテキストフォーマットやインラインJSXが必要な場合はFormattedMessageを使用します:
// src/components/Welcome.tsx
import React from "react";
import { FormattedMessage } from "react-intl";
interface WelcomeProps {
name: string;
itemCount: number;
}
export function Welcome({ name, itemCount }: WelcomeProps) {
return (
<div>
<h1>
<FormattedMessage
id="greeting"
values={{ name }}
/>
</h1>
<p>
<FormattedMessage
id="itemCount"
values={{ count: itemCount }}
/>
</p>
<p>
<FormattedMessage
id="welcomeBack"
values={{
name,
bold: (chunks) => <strong>{chunks}</strong>,
}}
/>
</p>
</div>
);
}
useIntlフック
JSXの外、イベントハンドラ内、またはaria-label属性のために文字列をフォーマットするといった命令的な使用にはuseIntlフックを使います:
// src/components/SearchBar.tsx
import React from "react";
import { useIntl } from "react-intl";
export function SearchBar() {
const intl = useIntl();
const placeholder = intl.formatMessage({
id: "search.placeholder",
defaultMessage: "Search products...",
});
const formattedPrice = intl.formatNumber(29.99, {
style: "currency",
currency: "USD",
});
const formattedDate = intl.formatDate(new Date(), {
year: "numeric",
month: "long",
day: "numeric",
});
return (
<div>
<input type="search" placeholder={placeholder} aria-label={placeholder} />
<span>{formattedPrice}</span>
<time>{formattedDate}</time>
</div>
);
}
react-intlのドキュメントはformatjs.io/docs/react-intlにあります。
react-i18next
react-i18nextは、最も広く使用されているJavaScript i18nフレームワークであるi18nextのReactバインディングです。その強みは柔軟性にあります。React、React Native、Node.js、そしてReact以外の環境でも動作します。チームが他の場所でi18nextをすでに使用している場合、共有されたメンタルモデルは本当の利点となります。
インストール
npm install i18next react-i18next
設定
// src/i18n/config.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import enCommon from "./locales/en/common.json";
import enDashboard from "./locales/en/dashboard.json";
import frCommon from "./locales/fr/common.json";
import frDashboard from "./locales/fr/dashboard.json";
i18n
.use(initReactI18next)
.init({
resources: {
en: {
common: enCommon,
dashboard: enDashboard,
},
fr: {
common: frCommon,
dashboard: frDashboard,
},
},
lng: "en",
fallbackLng: "en",
defaultNS: "common",
interpolation: {
escapeValue: false, // Reactは既に値をエスケープしています
},
});
export default i18n;
コンポーネントがレンダリングされる前にアプリのエントリーポイントで設定をインポートします:
// src/main.tsx
import "./i18n/config";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
useTranslationフックとネームスペース分割
ネームスペース分割は、大規模なアプリケーションにおけるreact-i18nextの最も便利な機能の一つです。一つの巨大なJSONファイルを読み込む代わりに、翻訳を機能やドメインごとに分割し、必要に応じてネームスペースを読み込みます。
// src/i18n/locales/en/common.json
{
"nav.home": "Home",
"nav.dashboard": "Dashboard",
"nav.settings": "Settings",
"button.save": "Save",
"button.cancel": "Cancel"
}
// src/i18n/locales/en/dashboard.json
{
"title": "Your Dashboard",
"stats.users": "{{count}} active user",
"stats.users_other": "{{count}} active users",
"welcome": "Welcome back, {{name}}!"
}
// src/components/Dashboard.tsx
import React from "react";
import { useTranslation } from "react-i18next";
interface DashboardProps {
userName: string;
activeUsers: number;
}
export function Dashboard({ userName, activeUsers }: DashboardProps) {
// 「dashboard」ネームスペースを特定してロード
const { t } = useTranslation("dashboard");
return (
<main>
<h1>{t("title")}</h1>
<p>{t("welcome", { name: userName })}</p>
<p>{t("stats.users", { count: activeUsers })}</p>
</main>
);
}
// src/components/Nav.tsx
import React from "react";
import { useTranslation } from "react-i18next";
export function Nav() {
// デフォルトの「common」ネームスペースを使用
const { t, i18n } = useTranslation();
const switchLanguage = (locale: string) => {
i18n.changeLanguage(locale);
};
return (
<nav>
<a href="/">{t("nav.home")}</a>
<a href="/dashboard">{t("nav.dashboard")}</a>
<button onClick={() => switchLanguage("fr")}>Francais</button>
<button onClick={() => switchLanguage("en")}>English</button>
</nav>
);
}
react-i18nextのドキュメントはreact.i18next.comにあります。
LinguiJS
LinguiJSは根本的に異なるアプローチを採用しています。IDで参照するJSONファイルで翻訳キーを管理する代わりに、JSX内で直接自然言語でUIを記述します。CLIがそれらの文字列をカタログに抽出し、翻訳者がそれを埋めます。ビルド時にメッセージは最適化されたバイナリフォーマットにコンパイルされます。
結果として、ランタイムバンドルが小さくなり、壊れたキー参照のリスクも低くなります。ただし、他のライブラリでは不要なビルドステップが必要になります。
インストール
npm install @lingui/react @lingui/core npm install --save-dev @lingui/cli @lingui/macro @lingui/vite-plugin
設定
// lingui.config.js
/** @type {import('@lingui/conf').LinguiConfig} */
module.exports = {
locales: ["en", "fr", "de"],
sourceLocale: "en",
catalogs: [
{
path: "src/locales/{locale}/messages",
include: ["src"],
},
],
format: "po",
};
@lingui/macroの使用
マクロはコンパイル時に自然言語の文字列を変換するため、ソースコードはシンプルな英語として読めながら、完全にローカライズ可能です:
// src/components/ProductCard.tsx
import React from "react";
import { Trans, Plural } from "@lingui/macro";
import { useLingui } from "@lingui/react";
interface ProductCardProps {
productName: string;
price: number;
reviewCount: number;
}
export function ProductCard({ productName, price, reviewCount }: ProductCardProps) {
const { i18n } = useLingui();
const formattedPrice = i18n.number(price, {
style: "currency",
currency: "USD",
});
return (
<article>
<h2>
<Trans>Buy {productName} today</Trans>
</h2>
<p>{formattedPrice}</p>
<p>
<Plural
value={reviewCount}
one="# customer review"
other="# customer reviews"
/>
</p>
<p>
<Trans>
Free shipping on orders over <strong>$50</strong>.
</Trans>
</p>
</article>
);
}
抽出ワークフロー
コンポーネントを記述した後、CLIを実行して文字列を抽出します:
# ソースファイルから全ての文字列をカタログに抽出 npx lingui extract # .poファイルを翻訳(または翻訳サービスに送る) # 本番用にコンパイル npx lingui compile
コンパイルされたカタログは小さく、高速にロードされます。LinguiJSは、翻訳者に優しいPOファイルワークフローと可能な限り小さいランタイムペイロードを求めるチームに特に魅力的です。
完全なドキュメントはlingui.devにあります。
Better i18n React SDK
Better i18nはReact i18nスペースの新しい参入者です。純粋に翻訳レンダリングライブラリとして競合するのではなく、完全なワークフロープラットフォームをバンドルしています。ブランドコンテキスト認識を持つAI駆動の翻訳、ASTスキャンによる自動キー発見、そしてリビルドなしで翻訳アップデートを公開できるCDN配信です。
選択前にトレードオフについて正直になりましょう: コミュニティはreact-intlやi18nextの数分の一のサイズです。Stack Overflowの回答はわずかです。エッジケースに遭遇した場合、コミュニティソリューションよりも公式ドキュメントやサポートチャンネルに頼る可能性が高いです。ライブラリは適切にメンテナンスされ積極的に開発されていますが、確立されたオプションが持つような長年の実績はまだありません。
インストール
npm install @better-i18n/react
セットアップ
// src/providers/I18nProvider.tsx
import React, { type ReactNode } from "react";
import { BetterI18nProvider } from "@better-i18n/react";
interface I18nProviderProps {
children: ReactNode;
locale: string;
}
export function I18nProvider({ children, locale }: I18nProviderProps) {
return (
<BetterI18nProvider
projectId={process.env.VITE_BETTER_I18N_PROJECT_ID!}
locale={locale}
>
{children}
</BetterI18nProvider>
);
}
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { I18nProvider } from "./providers/I18nProvider";
const locale = navigator.language.split("-")[0] ?? "en";
ReactDOM.createRoot(document.getElementById("root")!).render(
<I18nProvider locale={locale}>
<App />
</I18nProvider>
);
useTranslationsフック
// src/components/HeroSection.tsx
import React from "react";
import { useTranslations } from "@better-i18n/react";
interface HeroSectionProps {
userName: string;
planName: string;
}
export function HeroSection({ userName, planName }: HeroSectionProps) {
const t = useTranslations("hero");
return (
<section>
<h1>{t("title", { name: userName })}</h1>
<p>{t("subtitle", { plan: planName })}</p>
<a href="/signup">{t("cta")}</a>
</section>
);
}
翻訳キーはBetter i18nダッシュボードで定義されるか、CLIスキャナーによってソースコードから自動的に発見されます。翻訳者が変更を承認すると、CloudflareのCDNエッジネットワークを介してアプリに配信されます。リビルドや再デプロイは不要です。
Better i18nのドキュメントはbetter-i18n.com/docsにあります。
代替案よりBetter i18nを選ぶ場面: チームが最初からAI支援の翻訳を求めている、完全に自己完結したライブラリではなくマネージドプラットフォームに問題がない、そしてワークフローの自動化(自動キー発見、CDN配信、GitHubのPRベースの同期)がエコシステムの広さよりも価値がある場合です。
別のものを選ぶ場面: できるだけ大きなコミュニティ、Stack Overflowの長年の回答、または外部サービスへの依存なしの完全なセルフホストソリューションが必要な場合です。その場合、react-i18nextまたはreact-intlがより安全な選択肢です。
主要な実装パターン
言語の切り替え
言語の切り替えは状態を持ち、URLに反映される必要があります。これにより、ユーザーがローカライズされたリンクを共有できます。最もシンプルなアプローチはロケールを状態に保存してプロバイダーに渡すことですが、URLベースのアプローチはSSRとディープリンクにより堅牢です。
// src/hooks/useLocale.ts
import { useState, useCallback } from "react";
const SUPPORTED_LOCALES = ["en", "fr", "de", "es"] as const;
type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
function getInitialLocale(): SupportedLocale {
const fromUrl = window.location.pathname.split("/")[1] as SupportedLocale;
if (SUPPORTED_LOCALES.includes(fromUrl)) return fromUrl;
const fromBrowser = navigator.language.split("-")[0] as SupportedLocale;
if (SUPPORTED_LOCALES.includes(fromBrowser)) return fromBrowser;
return "en";
}
export function useLocale() {
const [locale, setLocale] = useState<SupportedLocale>(getInitialLocale);
const switchLocale = useCallback((next: SupportedLocale) => {
setLocale(next);
document.documentElement.lang = next;
// オプション:URLベースのルーティングのためルーター履歴にプッシュ
}, []);
return { locale, switchLocale, supportedLocales: SUPPORTED_LOCALES };
}
翻訳の遅延ロード
全てのロケールバンドルを事前にロードすることは帯域幅の無駄です。アクティブなロケールのみをロードし、他は必要に応じてフェッチしましょう:
// src/i18n/loader.ts
const localeCache = new Map<string, Record<string, string>>();
export async function loadLocale(locale: string): Promise<Record<string, string>> {
if (localeCache.has(locale)) {
return localeCache.get(locale)!;
}
// 動的インポート — バンドラーはロケールごとに別々のチャンクに分割します
const messages = await import(`./locales/${locale}.json`);
const resolved = messages.default as Record<string, string>;
// インプレースで変更するのではなく、新しいマップエントリを返します
const updated = new Map(localeCache);
updated.set(locale, resolved);
localeCache.clear();
updated.forEach((v, k) => localeCache.set(k, v));
return resolved;
}
SSR / SSGの考慮事項
サーバーサイドでレンダリングする場合、ロケール検出はnavigator.languageからHTTPヘッダー(Accept-Language)またはURLセグメントに移行します。サーバーとクライアントが同一のHTMLを生成するように、レンダリングが始まる前にロケールを解決する必要があります。
// src/app/[locale]/layout.tsx (Next.js App Routerの例)
import { notFound } from "next/navigation";
import { type ReactNode } from "react";
const SUPPORTED_LOCALES = ["en", "fr", "de"];
interface LocaleLayoutProps {
children: ReactNode;
params: { locale: string };
}
export default function LocaleLayout({ children, params }: LocaleLayoutProps) {
if (!SUPPORTED_LOCALES.includes(params.locale)) {
notFound();
}
return (
<html lang={params.locale} dir={params.locale === "ar" ? "rtl" : "ltr"}>
<body>{children}</body>
</html>
);
}
export function generateStaticParams() {
return SUPPORTED_LOCALES.map((locale) => ({ locale }));
}
URLベースのロケールルーティング
検索エンジンはexample.com/ja/productsとexample.com/en/productsを別々のインデックス可能なページとして扱います。URLベースのルーティングは、SEOのためにクッキーベースやヘッダーベースの検出よりも強く推奨されます。ルートをルートに[locale]セグメントで構造化し、サポートされている各ロケールの<head>にhreflangリンクタグを追加してください。
複数形
ここで説明する全てのライブラリは複数形を異なる方法で処理しますが、基礎となるルールはCLDR(Common Locale Data Repository)から来ています。英語には2つの複数形があります。アラビア語には6つあります。複数形ロジックを自分でハードコードしないでください。
react-i18nextを使用した場合(_one / _otherサフィックス使用):
{
"notification": "{{count}}件の通知",
"notification_other": "{{count}}件の通知"
}
t("notification", { count: 1 }); // "1件の通知"
t("notification", { count: 5 }); // "5件の通知"
日付と数値のフォーマット
ライブラリの組み込みフォーマットユーティリティを使用するか、直接Intlにフォールバックしてください。フォーマットされた値を文字列として連結しないでください。単位の順序はロケールによって異なります。
// Intlを直接使用(どのライブラリでも機能します)
const formatter = new Intl.NumberFormat(locale, {
style: "currency",
currency: "EUR",
});
// en-USでは"€1,234.56"、fr-FRでは"1 234,56 €" — 同じ数値、異なるフォーマット
formatter.format(1234.56);
よくあるミス
文字列の連結。 +またはテンプレートリテラルで翻訳フラグメントを結合して文章を作らないでください。語順は言語によって異なります。"こんにちは " + t("world")は、名詞の後に挨拶が来る言語では壊れます。常に補間変数を翻訳関数に渡してください。
コンテキストを忘れること。 英語の「File」は名詞(ドキュメント)にも動詞(申請する)にもなり得ます。翻訳者は正しい言葉を選ぶためにコンテキストが必要です。ほとんどのライブラリはcontextパラメーターや説明フィールドをサポートしています。使用してください。
ローディング状態の処理を忘れること。 翻訳が非同期でロードされる場合、文字列が利用可能になる前にコンポーネントがレンダリングされる窓があります。スケルトン、スピナー、または何もないものをレンダリングしてください。しかし"dashboard.title"のような生の翻訳キーをユーザーに表示しないでください。
全てのロケールをバンドルすること。 英語しか読まないユーザーにフランス語、ドイツ語、スペイン語、日本語の翻訳を送ることは帯域幅の無駄で、Time to Interactiveを増加させます。必要なものだけをロードするために動的インポートまたはCDNベースの配信戦略を使用してください。
ロケール固有のフォーマットをハードコードすること。 MM/DD/YYYYのような日付フォーマットをハードコードしないでください。ドイツのユーザーはDD.MM.YYYYを期待しています。常にフォーマットをIntlまたはライブラリのフォーマットユーティリティに委譲してください。
翻訳されたHTMLを生のマークアップとしてレンダリングすること。 翻訳文字列を生のHTMLとして注入することは、翻訳ソースが侵害された場合のXSSリスクです。HTMLをReactで適切にエスケープおよびサニタイズできるライブラリの安全なリッチテキストコンポーネントを使用してください。react-intlではFormattedMessageと要素値を、react-i18nextではTransコンポーネントを使用してください。
よくある質問
Q: ICUメッセージフォーマットのサポートが必要ですか?
ICUは複数形、性別選択、条件付きテキストを含む複雑なメッセージの標準です。「JohnとX人がこれを気に入りました」のような文がある場合、ICUはそれらのルールを表現することをクリーンでポータブルにします。アプリが単純な文字列置換のみを必要とする場合、ICUはオプションのオーバーヘッドです。react-intlとLinguiJSはICUをネイティブにサポートします。react-i18nextにはi18next-icuプラグインが必要です。
Q: Next.js App Routerで最も機能するライブラリは何ですか?
react-i18nextとreact-intlはどちらもいくつかの設定でApp Routerをサポートしています。Next.jsのエコシステムは、Server Componentsのファーストクラスサポートにより、App Routerプロジェクトにはnext-intlに収束しています。Better i18nも専用のNext.js SDKを提供しています。LinguiJSは機能しますが、ビルドパイプラインでのコンパイルステップの慎重な処理が必要です。
Q: 翻訳ファイルはどのように構造化すべきですか?
小規模なアプリの場合、ロケールごとに単一のJSONファイル(en.json、fr.json)で問題ありません。大規模なアプリの場合、機能やドメインごとに分割してください(i18nextのネームスペース、LinguiJSの別々のカタログ)。深くネストされたオブジェクトは避けてください。フラットまたは1レベルの深さのキーはスケールでの管理が容易で、マージコンフリクトが少ないです。
Q: 太字テキストやリンクなどのマークアップを含む翻訳はどのように扱いますか?
ほとんどのライブラリはrenderプロップまたはスロットシステムでリッチテキストをサポートしています。react-intlでは、JSX要素をFormattedMessageの値として渡します。react-i18nextではTransコンポーネントを使用します。これらのアプローチはHTMLをReactに保持し、適切にエスケープおよびサニタイズすることができます。
Q: i18nについていつから考え始めるべきですか? 思っているより早くです。既存のコードベースにi18nを後付けすることは、全てのハードコードされた文字列、全ての日付フォーマット、全ての複数形の仮定、そしてドイツ語の長い単語で壊れる全てのレイアウトを探すことを意味します。アプリが英語以外の話者に届く可能性が少しでもあるなら、最初の日に英語のみを提供する場合でも、初期セットアップにi18nインフラを追加してください。
まとめ
React i18nには唯一の正しい答えはありません。適切なライブラリは、チームのワークフロー、アプリの複雑さ、エコシステムリスクへの許容度に合ったものです。
新しいプロジェクトを開始していて、最大のコミュニティサポートと最も深いエコシステムが欲しい場合、react-i18nextが安全なデフォルトです。ネームスペースシステムは適切にスケールし、ドキュメントは徹底的で、Stack Overflowのほとんどのエッジケースに対する回答が見つかります。
アプリが本当に複雑なメッセージフォーマット要件を持っている場合(条件付き性別、マルチレベルの複数形、リッチなインラインマークアップ)、react-intlはReactエコシステムで最も完全なICU標準の実装です。
最小のランタイムバンドルとコンパイラで強制される抽出ワークフローが欲しい場合、LinguiJSはビルドステップの追加に慣れているチームに特に説得力のある代替案を提供します。
AI支援の翻訳、リビルドサイクルなしのCDN配信、翻訳パイプラインを自動化するマネージドプラットフォームが欲しい場合、Better i18nは評価する価値があります。ただし、コミュニティがより小さい若いエコシステムに賭けているという正直な注意書きがあります。
何を選んでも、早めにコミットし、翻訳ファイルを整理し、i18nを後付けではなくファーストクラスの機能として扱ってください。海外のユーザーは違いに気づくでしょう。
参考資料
- react-intl (FormatJS): formatjs.io/docs/react-intl
- react-i18next: react.i18next.com
- i18next core: www.i18next.com
- LinguiJS: lingui.dev
- Better i18n React SDK: better-i18n.com/docs
- Unicode CLDR Plural Rules: cldr.unicode.org/index/cldr-spec/plural-rules
- MDN Intl API reference: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl