Laravel + Inertia + React で多言語化する

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、私もプログラムが好きな人間として定期的に個人サイトをつくったりしています。

正直なところ「鳴かず飛ばず」なサイトばかりでほとんど終了させてきているのですが、個人開発はなんといっても「自由にできる」ので趣味として続けたいところです。

しかし、そんな個人開発であっても今後視野に入れておくべきものがあります。

それが・・・・・・

多言語化

です。

というのも、みなさんご存知のとおり2022年11月月に出された統計では、出生数が77万人になり、人口がどんどん減っていくため、今後は国外マーケットを視野にいれないといけないからです。(しかも、国の予測よりも8年早くこうなってしまったようです😥)

そこで❗

今回は「Laravel + Inertia + React」で多言語化する方法をご紹介します。

ぜひ何かのお役に立てると嬉しいです。😄✨

「もしも JavaScript が日本製なら、
「Vanilla JS」は、
「小豆 JS」と呼ばれてたんでしょうか??」

開発環境: Laravel 9x、React、Vite、Inertia.js、TailwindCSS

やりたいこと

今回はReact側で表示するテキストを多言語化できるようにします。

今回対応する言語は以下の3つです。

  • 日本語
  • 英語
  • 中国語

また、せっかくなのでReactで言語を変更できる機能もつくってみたいと思います。

ぜひ楽しんでやっていきましょう❗

翻訳データをつくる

まず今回の機能をつくるに当たって必要になるものが「翻訳データ」です。

LaravelにはJSONを使った多言語化がはじめから備わっているので、これに合わせてlangフォルダ直下にJSONファイルをセットして使います。

なお、このファイルは以下のように「英文をキーとして翻訳データをセットする」ようになっています。

lang/ja.json

{
    // 省略

    "Not Found": "見つかりません",
    
    // 省略
}

そこで、今回は以下2つのJSONファイルを作成してこの中に翻訳データを登録していきましょう。

lang/ja.json

{
    "Hi, :user_name, It is now :hours::minutes.": "こんにちは、:user_name さん。 現在時刻は、:hours時:minutes分です。"
}

lang/zh.json

{
    "Hi, :user_name, It is now :hours::minutes.": "你好,:user_name。目前的时间是:hours::minutes。"
}

※ ちなみに翻訳は DeepL を使っています。

なお、この中に入っている以下3つの部分は各パラメータで置き換えができるようになっています。

  • :user_name → ユーザー名
  • :hours → 時
  • :minutes → 分

例えば、日本語の場合だと以下のようにすると、

echo __('Hi, :user_name, It is now :hours::minutes.', [
    'user_name' => '山田太郎',
    'hours' => now()->format('H'),
    'minutes' => now()->format('i'),
]);

出力はこのようになります。👇

こんにちは、山田太郎 さん。 現在時刻は、03時19分です。

では、この翻訳データを「Inertia + React」でも使えるようにしていきましょう❗

LaravelからReact側へ翻訳データを送る

続いては先ほどつくった翻訳データをReactInertia)側へ送って使えるようにしていきます。

app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Inertia\Inertia;

class AppServiceProvider extends ServiceProvider
{
    // 省略

    public function boot()
    {
        // 翻訳データを Inertia へ渡す
        $locale_data = [];
        $locales = ['ja', 'zh']; // 👈 できれば Enum での管理がベターです

        foreach ($locales as $locale) {

            $json_path = lang_path($locale .'.json');
            $json_content = file_get_contents($json_path);
            $locale_data[$locale] = json_decode($json_content, true);

        }

        $default_locale = config('app.locale');
        $current_locale = request('locale', $default_locale);

        Inertia::share('locale', [
            'currentLocale' => $current_locale,
            'localeData' => $locale_data,
        ]);
    }
}

※ なお、コメントにも書いていますが、使用する言語はEnumで管理したほうがいいです。というのも別の場所でも使うことが想定されるので一元管理しておかないと、後でコードすべてを変更しないといけなくなるからです。

これで、Inertia側へ翻訳データが渡るようになりました❗

Inertia + React で翻訳データが使えるようにする

次に、Inertia側へ送信された翻訳データを取得する方法ですが、シンプルにいうとpropsから取得できます。

export default function Index(props) {

    console.log(props.locale); // 👈 翻訳データ

    // 省略

}

しかし、毎回言語を特定してターゲットの翻訳データを取得するコードを書くのは面倒です 😫(しかも、さらにパラメータも置き換えないといけません)

// ⚠ これは書くのが面倒なバージョンです

export default function Index(props) {

    const currentLocale = props.locale.currentLocale;
    const text = props.locale.localeData[currentLocale]['Hi, :user_name, It is now :hours::minutes.'] || '';

    // 省略

}

そこでLaravelのように__()trans()というヘルパー関数をJavaScript側で登録しておきましょう。

(なお、__()trans()はどちらも同じものです)

resources/js/app.jsx

// 省略

createInertiaApp({

    // 省略

    setup({ el, App, props }) {

        // ヘルパー関数を登録
        window.__ = window.trans = (key = null, replace = {}, locale = null) => {

            const {currentLocale, localeData} = props.initialPage.props.locale;

            if(locale === null) {

                locale = currentLocale;

            }

            let translatedText = localeData[locale][key] || key;

            for(let key in replace) {

                const replacingValue = replace[key];
                translatedText = translatedText.replace(`:${key}`, replacingValue);

            }

            return translatedText;

        };

        return render(<App {...props} />, el);
    },
});

// 省略

これで、以下のように翻訳データを取得することができるようになりました。

// 基本形
const text = __('Hello!');
const text = trans('Hello!');

// パラメータつきの場合
const text = __('Hello, :user_name!', [
    'user_name' => '山田太郎'
]);

翻訳データを表示できるようにする

では、ここまででつくった機能で翻訳データが表示できるようにしていきます。

また、言語の切り替えもできるようにしてみましょう❗

ルートをつくる

まずはルートに以下を追加します。

routes/web.php

Route::get('locale', fn() => Inertia::render('Locale/Index'));

※ なお、今回はひとつなので省略形で書いていますが、複数ある場合はちゃんとコントローラーに書いた方がいいです。

ビューをつくる

次にビューです。
以下のファイルをつくってください。

resources/js/Pages/Locale/Index.jsx

export default function Index(props) {

    const userName = '山田太郎';
    const hours = new Date().getHours();
    const minutes = new Date().getMinutes();
    const locales = ['ja', 'en', 'zh'];
    const handleChangeLocale = locale => {

        location.href = '?locale='+ locale;

    };

    return (
        <div className="p-5">
            <div className="bg-gray-200 p-2 mb-5">
                { __('Hi, :user_name, It is now :hours::minutes.', {
                    user_name: userName,
                    hours: hours,
                    minutes: minutes,
                }) }
            </div>

            Language<br />
            <select onChange={e => handleChangeLocale(e.target.value)}>
                <option value=""></option>
                {locales.map(locale => (
                    <option key={locale} value={locale}>{locale}</option>
                ))}
            </select>
        </div>
    );

}

これで作業は完了です❗
お疲れ様でした😄

テストしてみる

では、実際にテストしてみましょう❗

npm run devを実行してViteを起動し、ブラウザで「http://******/locale」へアクセスしてください。

すると・・・・・・

はい❗
ベースは英語でしたが、言語設定が「ja」なので日本語で表示されています。

では、次は「Language」セレクトボックスで「en」を選択してみましょう。

どうなったでしょうか・・・・・・

はい❗
自動で英語に切り替わりました。

では、次に中国語(zh)に変更してみましょう。
うまくいくでしょうか・・・・・・

はい❗
こちらもうまく変更できました。

すべて成功です😄👍

企業様へのご提案

今後日本の人口が極端に減ってくるのは出生数を見れば明らかです。

そのため、これからは日本向けのサービスだけでは立ち行かなくなるケースも増えてくることかと思います。

そこで、今回のようにサイトを多言語化することで来るべき世界にマッチしたものを準備してみてはいかがでしょうか。

もしそういったご希望やご相談がございましたら、いつでもお気軽にお問い合わせからご連絡ください。

お待ちしております。😄

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで今回は「Laravel + Inertia + React」で多言語化してみました。

翻訳データはJSONなので他の目的にも使いやすいですし、DeepLがあればある程度は精度の高い翻訳データもつくることができます。

ぜひみなさんもやってみてくださいね。

ではでは〜❗

「Go Go カレーは
裏切りませんな🍛✨」

このエントリーをはてなブックマークに追加       follow us in feedly