🐭

【React/Next.js】新規開発における多言語化(i18n)で得た知見共有 | Offers Tech Blog

2023/10/17に公開

概要

こんにちは、Offers を運営している株式会社 overflow でフロントエンドのテックリードをしている Kazuya です。今回は、筆者が担当しているプロダクト「Offers MGR(オファーズマネージャー) 」で先日実装した多言語対応について書かせていただきます。

近年はプロダクトがグローバル化していくケースが増加しており、それに伴い、WEBアプリケーションも英語や中国語をはじめとした複数言語のサポートをする必要がでてきていると思います。ただ、多言語化は実装する現場からするとやることが漠然としており、どのように進めればよいのか、構成はどのようにすればいいのかなど、多言語化をする際に考慮すべきことが多くあり、なかなか実行に移すのは難しいです。(筆者も右往左往していました)

そこで今回は、新規プロダクト開発で実際に実装してみて得た知見をベースに多言語化の構成やポイントなどを紹介していければと思いますので、ぜひ参考にしてもらえればと思います。

https://prtimes.jp/main/html/rd/p/000000137.000053307.html

[AD] Offers MGR(オファーズマネージャー)

本記事で紹介する方法は、筆者が担当しているプロダクトである「Offers MGR(オファーズマネージャー) 」で活用されています。「Four Keys分析」や「サイクルタイム分析」など開発組織の生産性を最大化するために必要となる指標を可視化させることができます。開発組織の健全性・生産性を中長期的に改善していきたい方はぜひお問い合わせください!

https://offers-mgr.com/lp/
https://zenn.dev/offersmgr

はじめに

前述した通り、本記事では多言語化(i18n)の構成と進め方を筆者のこれまでの経験を元に紹介します。各社における技術戦略やチームメンバーの構成、スキルアセットなど様々な要因で本記事で紹介する内容とマッチしない場合があります。今回は一例であることをご理解の上、適宜参考にしていただけると幸いです。

構築する環境

  • Node: 18.16.0
  • Next.js: 12.3.0

使用するツール

多言語化する上で必ず作成する辞書ファイルを管理するSaaSとして「Localazy」を使用しています。Google翻訳やDeepL翻訳などの翻訳サービスを活用して翻訳作業をすることができます。辞書ファイル作成(翻訳作業)の工数を減らせるだけでなく、Figmaなどをはじめとした様々なサービスと連携できるため、リソースを一括で管理することができます。料金も比較的にリーズナブルなため、あまりコストを掛けたくない新規のプロダクトでも安心して利用できると思います。
導入方法や詳しい使い方は以下のページをご参照ください。

https://localazy.com/

弊社のフロントにおける事例と具体的な使い方に関しては以下の記事にて紹介しておりますので、ぜひご参照ください。

https://zenn.dev/overflow_offers/articles/ecea914256a8e1

実装までの進め方

対応する範囲(スコープ)を定める

多言語化以外にも言えることですが、最初にすべきことは、対応する範囲(スコープ)を明確にすることです。範囲が明確になっていないと工数を算出できないだけでなく、共通認識をチームメンバー間で持つことができません。どのページ/コンポーネントを対応するのか可視化させておきましょう。当たり前のことですが、これができていないと不明瞭なまま実装していくことになり、結果としてスケジュールの遅延や意図しない実装になってしまう可能性があります。

また、理想は全てのページを多言語化することですが、新規プロダクトではリソースの兼ね合いで厳しいことが多いと思います。そこでフェイズを分けて主要機能のあるページから優先的に対応する方法がコストとリスクヘッジの観点でおすすめです。

対象となるワードを洗い出す

対象となるページ/コンポーネントが定まったら、次は辞書に登録するワードを洗い出します。常時表示されているUIだけでなく、ポップアップやエラー時のみに表示されるなど条件を満たしたときにのみ表示されるUIも考慮しないといけないため、コードベースで確認する必要があります。全て洗い出しておきたいところですが、コストがかかるので、常時表示されるUIをベースに確認できる範囲で可視化させるだけでも良いでしょう。あくまで主目的が、工数把握のためなので、ワード総数と実装箇所が大枠把握できていれば精度は落ちますが、算出は可能です。

概算工数を算出する

対象範囲(スコープ) > 辞書に登録するワード数が洗い出せたので、大枠の工数を算出できます。多言語化は辞書ファイルの作成/反映がタスクのほとんどを占めるケースが多いと思います。ワード数を元に辞書への置き換えなどにかかる時間を算出してみましょう。コンポーネント内のワードの置き換えは比較的容易に済むと思いますが、Hooks以外のメソッド内に書かれているものを置き換える際は、最悪Hooks化させるなどの対応が必要になるので、注意が必要です。(弊社は処理の一部がクラスメソッドで書かれており、全てHooks化させるという謎の作業が多数発生しました...)

弊社における多言語化に関する構成

バックエンド側でLocaleを管理

自身が担当している「Offers MGR(オファーズマネージャー) 」では、バックエンド側でログインユーザー毎のLocale(言語)を管理する仕組みにしています。これは多言語化の対応範囲がSlackやメールなどアプリケーション外も含まれるためです。そのため、フロントエンド側ではログインユーザーのデータをフェッチした際に設定されている言語ページにリダイレクトさせる必要があります。

以下は、その処理をまとめたHooksでこれを全ページで呼び出される_app.tsxに配置することで、全ページで実行されます。

useValidateLocale.ts
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { localeState } from '@/states/locale.state';
import { validateNoAuthRequiredPaths } from '@/utils/auth';

export const useValidateLocale = () => {
  const { pathname, query, isReady, replace } = useRouter();
  const { locale: _locale } = useRouter();
  const locale = useRecoilValue(localeState);

  useEffect(() => {
    if (locale === _locale || !isReady) return;
    if (!query?.debug && !validateNoAuthRequiredPaths(pathname))
      replace({ pathname, query }, undefined, { locale });
  }, [locale, _locale, pathname, isReady, query, replace]);
};

Next.jsのi18nを使用

実装自体は、Next.jsのi18nを素直に使用しています。設定が簡単で導入コストが非常に低いことに加え、フレームワーク組み込みの機能のため、中長期的な運用でも破綻しづらいです。ただし、公式のドキュメントにも記載されていますが、Next.jsをサーバーとして利用している(SSR)際しか使用できないため、Exportして配信している場合などは別の方法を採用する必要があります。

辞書ファイルは外部サービスで管理

前述でも少し書きましたが、辞書ファイルを管理するSaaSとして「Localazy」を採用しています。当初はGoogleスプレッドシートでの管理を検討していましたが、CTOに相談したところ、スプレッドシートでの管理だと中長期的に運用が厳しくなるという失敗談を伺ったので、外部サービスで管理しようということになりました。色々探していたところ、VPoEから「Localazy」というサービスを紹介され、調べてみたところ、ローコスト且つ多機能で扱いやすそうだったので、こちらを採用しました。

今回は抜粋して紹介しますが、目玉は「一括翻訳機能」だと感じています。ベースとなる言語のワードを辞書に登録して、その後翻訳したい言語への翻訳一括で行える機能です。操作も簡単で、「Google翻訳」「DeepL翻訳」など複数ある翻訳エンジンから使用したいものを選ぶだけです。これならエンジニア以外のデザイナーやPMでも作業できるため、エンジニアは実装に集中することができます。

他にも魅力的な機能が多数ありますが、こちらはCTOが書く意欲を見せていたので、気長にお待ちいただければと思います。

個人的なポイント

ページ単位で多言語化していく

多言語化は非常に多くのページが対象になると思いますが、作業する際は1ページずつコツコツと進めていくことをおすすめします。これは一度の実装コストを抑えることでPRの肥大化を防ぎ、コードレビューのコストを削減するためです。影響範囲が広いが故に更新対象のファイルも比例して増加します。複数ページ一気に対応してしまうと差分が多すぎてコードレビューにも時間が非常にかかってしまい、結果的に生産性を落とす可能性が高くなります。

また、エンジニア以外のデザイナーやPMの確認も一括で行うと、どうしても確認漏れなどが発生するリスクが高くなるため、ページ単位で分割して対応していくと効率的に進めることができると思います。

言語の含む処理はHooks化させておく

Next.js組み込みのi18nを使用する際は、useRouterなどを経由して現在のlocaleを取得することができます。そのため、辞書ファイルの切り替えも必然的にHooks内で行うことになります。そのため、Hooks以外のメソッドなどで言語を制御していると、処理が難しいまたは面倒になるケースが多いです。当たり前ではありますが、その手の処理を書く際はHooksで書くようにしましょう。(自戒)

前述で少し書いた辞書ファイルの切り替えHooksも以下にコードを載せておくので、興味のある方はご参照ください。

useLocale.ts
import { useRouter } from 'next/router';
import { app as appEn } from '@/config/locales/en';
import { app as appJa } from '@/config/locales/ja';

export const supportedLanguages = ['en', 'ja'] as const;
export type Locale = (typeof supportedLanguages)[number];

export const useLocale = () => {
  const router = useRouter();
  const locale = router?.locale as Locale;
  const assets = locale === 'en' ? appEn : appJa;

  return {
    locale,
    assets,
  };
};

まとめ

今回は、新規プロダクト開発で実際に実装してみて得た知見をベースに多言語化の構成やポイントを紹介しました。筆者も真面目に多言語化対応したのは、今回が初で多くの学びがありました。(実装しているというよりは精神修行だった)これから新規プロダクトで多言語化するという方の参考に少しでも慣れば幸いです。

本記事を最後まで読んで頂き、ありがとうございました。いいねしていただけると記事執筆の励みになりますので、参考になったと思った方は是非よろしくお願いします!

関連記事

https://zenn.dev/overflow_offers/articles/ecea914256a8e1
https://zenn.dev/overflow_offers/articles/20220523-component-design-best-practice
https://zenn.dev/offers/articles/20220418-what-is-bff-architecture

Offers Tech Blog

Discussion