1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

個人開発で多言語対応する、最もシンプルな方法

1
Posted at

はじめに

こんにちは、ひるげです。

皆さんは、自分のサービスを英語対応したいと思ったことはありますか?

世界人口は約81億人。そのうち日本語を話す人は約1.3億人で、世界全体の約1.6%です。
一方、英語を話す人(母語+第二言語)は約15億人、約18.5%にのぼります。
つまり、英語対応するだけで、潜在的なリーチが10倍以上に広がります。
(あくまで数字上の話ですが...)

「いつかやりたいな」と思いつつ後回しにしている方も多いと思います。
多言語対応ってなんとなく難しそうで、どこから手をつけていいか分かりませんよね。

この記事では、私が個人開発のLPを英語対応した直近の経験をもとに、個人開発における最初の一歩として、最もシンプルな英語対応の方法を紹介します。

多言語対応の選択肢と個人開発の現実

まず正攻法の話をします。Webアプリの多言語対応には、react-i18next などのi18nライブラリを使うのが一般的です。翻訳ファイルを言語ごとに管理し、コンポーネントから t('key') のような形で呼び出す方式です。チーム開発や大規模なサービスでは、この仕組みが適切に機能します。

ただ、個人開発の文脈では話が変わります。

i18nライブラリを導入すると、すべてのコピーをキー管理する必要が生まれます。
既存のコンポーネントを作り替えて、翻訳ファイルを整備して……とやっていくと、それ自体に多大なコストが発生します。

さらに言うと、「日本語の文言を機械的に英語翻訳して終わり」は往々にして不十分な対応となります。
言葉のニュアンスや文化的な背景が違うので、日本語の文言を英訳しても、伝わらないことがあります。
特にLPのようなマーケティング色の強いページは、コピーなどのニュアンスを絶対に落としたくないので、英語版は英語版で独立して設計した方がシンプルになり得ます。

そしてもう一つ、「最初から全ページを多言語対応すること」はオススメしません。
なぜなら、その対応に本当に需要があるのかわからないからです。
「まずはLPを多言語対応してみて、流入があるなら本格的に多言語対応する」というくらいが、小さく始められて個人開発向きなのかなと思います。

そうなってくると、1ページのためにi18nライブラリを入れることはまずオーバーエンジニアリングでしょう。
個人開発では、技術の正しさより、実装コストと得られる効果のバランスを重視する判断が重要になります。
"まずは小さく試す"を徹底してください。

前提:この記事で対象となったアプリの構成

この記事では、以下の構成を前提に話を進めます。
とはいえ、話の大枠は「ブラウザの言語設定をみて日本語版と英語版を出し分けよう」に過ぎないので、完全に同じ構成でなくても参考になる部分はあるかと思います。

  • フレームワーク:React Router v7
  • 設定:ssr: false(SPA)+ prerender(静的HTML配信)
  • 対象:LPページ1枚(/lp)を英語対応する(日本語ページと別で /lp/en を用意する)

実装:別ルートで英語版を用意する

ルート定義

React Router v7 のファイルベースルーティングで、以下のようにルートを追加します。

app/
  routes/
    lp.tsx      # /lp(日本語版)
    lp.en.tsx   # /lp/en(英語版)

react-router.config.ts でprerenderの対象に両方のパスを追加します。

export default {
  ssr: false,
  async prerender() {
    return ["/lp", "/lp/en"];
  },
} satisfies Config;

英語版のコンポーネント(lp.en.tsx)は、日本語版とは別ファイルとして管理します。
どうです?既にできそうな感じがしてきませんか?

今回のコア:navigator.language + clientLoader で言語自動切り替え

ここが今回のコアです。
アクセスしてきたユーザーのブラウザ言語に応じて、自動的に適切なページへリダイレクトします。SSRではないので Accept-Language ヘッダーは使えません。クライアントサイドで navigator.language を参照します。

日本語版(lp.tsx)の clientLoader に以下を書きます。

import { redirect } from "react-router";

export async function clientLoader() {
  const lang = navigator.language;
  if (lang.startsWith("en")) {
    throw redirect("/lp/en");
  }
  return null;
}

英語版(lp.en.tsx)には逆向きのリダイレクトを書きます。

export async function clientLoader() {
  const lang = navigator.language;
  if (!lang.startsWith("en")) {
    throw redirect("/lp");
  }
  return null;
}

これにより、英語環境のユーザーは /lp にアクセスしても自動で /lp/en へ飛び、日本語環境のユーザーは /lp/en にアクセスしても /lp へ戻ります。

hreflangの設定

検索エンジンに「このページには言語違いの版がある」と伝えるための設定です。React Router の meta 関数から link タグを返します。

export function meta(): MetaDescriptor[] {
  return [
    {
      tagName: "link",
      rel: "alternate",
      hreflang: "ja",
      href: "https://roamble.app/lp",
    },
    {
      tagName: "link",
      rel: "alternate",
      hreflang: "en",
      href: "https://roamble.app/lp/en",
    },
    {
      tagName: "link",
      rel: "alternate",
      hreflang: "x-default",
      href: "https://roamble.app/lp",
    },
  ];
}

x-default は「どの言語にも該当しない場合のデフォルト」を指定するタグです。
今回は日本語版をデフォルトとして設定しています。
日本語版・英語版の両方のページに同じhreflangセットを設定する点に注意してください。

おわりに

以上が、最もシンプルな多言語対応になります。

i18nライブラリを使わず別ルートで管理する方針は、すべての場面に適しているわけではありません。対応言語が増えるほど管理コストが上がりますし、全ページ対応が必要になってきたら素直にi18nライブラリに乗り換えるべきでしょう。ただ、「1ページを英語対応する」という最初の一歩としては、十分シンプルで実用的な選択肢だと思います。

英語対応、ぜひ一歩踏み出してみてください。

宣伝

最後に少しだけ宣伝させてください。

現在、Roambleという新しいお店開拓アプリを開発・運営しています。

「気になっているカフェがあるけどなんか入りにくい」「結局いつもの店に行ってしまう」
こういった悩みに少しでも心当たりがあるなら、Roambleは"あなたのためのアプリ"と言えるでしょう。

現在、Webベータ版を運営中であり、つい先日iOS化のための開発に着手しました!
以下のLPからiOS先行登録を受け付けています。
少しでも気になった方はチェック・先行登録していただけると嬉しいです!!!!

せっかくなので、この記事で紹介した英語版LPも見ていってください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?