技術系

多言語対応でイキろう!

技術系

こんにちは。なかにしです。

今回は多言語対応をしてイキります!

言語は色々選べるのですが、とりあえず英語に対応します。
ボタンを押したらページ全体が英語に変わる、というのをゴールとして実装していきます!

やり方の概要

ざっくり説明すると、「この単語は英語ではこう表示してね」というのを書いたファイルを用意し、ボタンを押すとそのファイルを読み込んで翻訳しているように見せます。

翻訳機をこちらで用意するわけではないです。

結構手動の部分が多いですが、PHPのようにページを動的に作成しない限りは完璧に動作しますので、ユーザーにはバレないと思います。

準備

今回も愛用のViteを使用します。

やり方が分からない方はこちらをご参照ください。

▽とりあえず中身を全消しして、テキトーに文章を入れたら準備完了です!
今回はお手紙風にしました。

import './App.css'

function App() {

  return (
    <div className="App">
      <h1>こんにちは。</h1>
      <p>お元気ですか?</p>
      <p>私は元気です。</p>
      <p>すごく元気で、めっちゃ元気です。</p>
      <button>言語切り替えボタン</button>
    </div>
  )
}

export default App

▽現在の状態

いい感じに元気アピを決めたところで早速実装していきます!

必要なモジュールをインポート

Reactでの翻訳は「i18next」「React-intl」が2強です。
個人的には「i18next」が好きなのでこっちにします。

▽npmでインポート

npm install --save react-i18next i18next

翻訳用フォルダの設定

任意の場所に翻訳用の「translate」フォルダを作成。
その配下に「en」「ja」のフォルダと「en.json」、「ja.json」を作成。
※翻訳に使用するのはjsonファイルのみなので、フォルダとかは何でも大丈夫です。
ぱっと見で分かりやすいよう、フォルダに分けているだけです。

▽こんな感じにしました。

▽ ja.jsonの中身

{
  "greeting": "こんにちは。"
}

▽ en.jsonの中身

{
  "greeting": "hello"
}

次に、任意の場所に「i18n.js」というファイルを作成します。

▽中身はこちら

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en from "./translate/en/en.json";
import ja from "./translate/ja/ja.json";

i18n.use(initReactI18next).init({
  resources: {
    en: {
      translation: en,
    },
    ja: {
      translation: ja,
    },
  },
  debug: process.env.NODE_ENV === "development",
  fallbackLng: "ja", // デフォルトの言語を設定する
  interpolation: {
    escapeValue: false,
  },
});

▽ i18nの設定をimportし、
「I18nextProvider」で使用したいもの
(今回はアプリ全体で使いたいので全体)を包みます。

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { I18nextProvider } from 'react-i18next' //追記
import i18n from './i18n'; //追記

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <I18nextProvider i18n={i18n}> //追記
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </I18nextProvider> //追記
)

▽使いたい場所のファイルで「useTranslation」をインポートします。
「changeLanguage」という組み込み関数があるので、それを使用して言語を切り替えます。

import './App.css'
import { useTranslation } from 'react-i18next'; //追記

function App() {

  const {t, i18n}:any = useTranslation(); //追記

  const changeLang = (lang:any) => { //追記
    i18n.changeLanguage(lang)       //追記
  }

  return (
    <div className="App">
      <h1>{t('greeting')}</h1> //追記
      <p>お元気ですか?</p>
      <p>私は元気です。</p>
      <p>すごく元気で、めっちゃ元気です。</p>
      <button onClick={() => changeLang('en')}>言語切り替えボタン</button>  //追記
    </div>
  )
}

export default App

切り替え対象は{t}で囲みます。
流れとしては、{t}で囲まれた部分を検知→翻訳ファイルを参照→翻訳 です。

今回は{t(‘greeting’)}と記載した為、
「greeting」という文字が翻訳対象か、en.jsonを探しにいきます。

▽もう一度 en.json を見てみましょう。

{
  "greeting": "hello"
}

「greeting」という文字列を「hello」に変換しろと書いてありますので、
「こんにちは。」が「hello」に変わるはずです。

▽もう一度全体の流れをまとめます。

①ボタンクリック
②changeLang関数が発火、引数に’en’を渡しているので’enに切り替え’
③en.jsonの中から、{t}で囲まれている箇所を探す。(今回はgreeting)
④見つかれば翻訳、見つからなければ何もしない。

動作確認

翻訳はできたものの、日本語→英語への一方通行になっています。
ボタンを押すと日本語→英語→日本語…となるように改修します。

改修

▽改修後

import './App.css'
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';

function App() {

  const [ lang, setLang ] = useState('ja');
  const {t, i18n}:any = useTranslation();

  useEffect(() => {
    i18n.changeLanguage(lang);
  }, [lang, i18n]);

  return (
    <div className="App">
      <h1>{t('greeting')}</h1>
      <p>お元気ですか?</p>
      <p>私は元気です。</p>
      <p>すごく元気で、めっちゃ元気です。</p>
      <button onClick={() => setLang(lang === 'en' ? 'ja' : 'en')}>言語切り替えボタン</button>
    </div>
  )
}

export default App

言語切り替えの状態をuseStateで持たせ、ボタンを押す度に現在の言語(jaかen)を取得し、もう一方をセットするようにしました。

useStateの変更はuseEffectで感知し、changeLanguage関数を呼び出して翻訳しています。

流れとしては、
①ボタンクリック
②setLangが発火
③langが更新
④langの変更を感知
⑤changeLanguage関数が発火
⑥{t}の中の文字列をen.jsonもしくはja.jsonの中から探す
⑦あれば翻訳、なければ何もせず終了。

「こんにちは」以外の文字が翻訳されないのは、
en.jsonに「greeting」以外は何も設定していないからです。

さらに改修

▽すべて設定し、翻訳できるようにして完成です。

{
  "greeting": "こんにちは。",
  "お元気ですか?": "お元気ですか?" ,
  "私は元気です": "私は元気です",
  "すごく元気で、めっちゃ元気です。": "すごく元気で、めっちゃ元気です。",
  "言語切り替えボタン": "言語切り替えボタン"
}
{
  "greeting": "hello",
  "お元気ですか?": "How are you?" ,
  "私は元気です": "I'm fine",
  "すごく元気で、めっちゃ元気です。": "I'm so fine, I'm so fine.",
  "言語切り替えボタン": "language switch button"
}

▽完成!

2022/10/22追記

フランス語とアラビア語に対応しました。
▽新たに「fr.json」と「ar.json」を用意して読み込みました。

{
  "こんにちは。": "bonjour",
  "お元気ですか?": "Comment vas-tu?" ,
  "私は元気です": "Je vais bien.",
  "すごく元気で、めっちゃ元気です。": "Je vais si bien, je vais si bien.",
  "日本語": "Japonais",
  "英語": "Anglais",
  "フランス語": "Français",
  "アラビア語": "arabe"
}
{
  "こんにちは。": "أهلا.",
  "お元気ですか?": "كيف حالك؟" ,
  "私は元気です": "أنا بخير.",
  "すごく元気で、めっちゃ元気です。": "أنا بخير ، أنا بخير.",
  "日本語": "اليابانية",
  "英語": "إنجليزي",
  "フランス語": "فرنسي",
  "アラビア語": "عربي"
}

▽「ja.json」「en.json」もちょっと変更してます。

{
  "こんにちは。": "こんにちは。",             //変更
  "お元気ですか?": "お元気ですか?" ,
  "私は元気です": "私は元気です",
  "すごく元気で、めっちゃ元気です。": "すごく元気で、めっちゃ元気です。",
  "日本語": "日本語",
  "英語": "英語",
  "フランス語": "フランス語",
  "アラビア語": "アラビア語"
}
{
  "こんにちは。": "hello",                   //変更
  "お元気ですか?": "How are you?" ,
  "私は元気です": "I'm fine",
  "すごく元気で、めっちゃ元気です。": "I'm so fine, I'm so fine.",
  "日本語": "Japanese",
  "英語": "English",
  "フランス語": "French",
  "アラビア語": "Arabic"
}

▽「i18n.js」で「ar.json」と「fr.json」を読み込み、使えるようにします。

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en from "./translate/en/en.json";
import ja from "./translate/ja/ja.json";
import ar from "./translate/ar/ar.json"; //追記
import fr from "./translate/fr/fr.json"; //追記

i18n.use(initReactI18next).init({
  resources: {
    en: {
      translation: en,
    },
    ja: {
      translation: ja,
    },
    fr: {                //追記
      translation: fr,        //追記
    },                 //追記
    ar: {               //追記
      translation: ar,        //追記
    },                 //追記
  },
  debug: process.env.NODE_ENV === "development",
  fallbackLng: "ja", // デフォルトの言語を設定する
  interpolation: {
    escapeValue: false,
  },
});

export default i18n;

▽完成です!

さいごに

どうです?
何かすごそうでしょ 🙂

APIが~とかではなく手動翻訳なので、簡単ですし、軽いです。
普通のWebサイトとかだったら簡単に実装できます。

でもChromeは翻訳が標準でついてるしな…
ニーズは微妙ですが、知ってて損はないかな?って感じです。

アラビア語とかにも対応できますし!(需要0)

今回はここまで!
Enjoy Hacking!!