Laravel + Inertia + React + TypeScriptで多言語対応する方法

Laravelでは __('Good morning!') のように__ ヘルパ関数を使うことで簡単に多言語対応できますが、Inertia + React スタックでは自作する必要がったのでそのメモです。

前提

  • Laravel BreezeをReact + TypeScriptスタックでインストール済み。
  • フォールバックロケールには対応していないので、使用する言語の翻訳ファイルを確実に用意しておく必要があります。
  • __ ヘルパで実装されている複数形/単数形の切り替えには対応していません。
  • ロケールの切り替えはサーバー側で行うことを想定してます。

環境

  • Laravel 10.5
  • Inertia + React + TypeScript

余談ですが、BreezeをインストールするときにTypeScriptが指定できるようになってめちゃめちゃ便利になりましたね。

php artisan breeze:install react --typescript

翻訳ファイルを作成する

langディレクトリ下に翻訳ファイルを作成します。

{
    "Good morning!": "Selamat pagi!"
}
<?php

return [
    'failed'   => 'Identitas tersebut tidak cocok dengan data kami.',
    'password' => 'Kata sandi yang dimasukkan salah.',
    'throttle' => 'Terlalu banyak upaya masuk. Silahkan coba lagi dalam :seconds detik.',
];

HandleInertiaRequestsを修正する

HandleInertiaRequestsミドルウェアから翻訳ファイルのデータをReactに渡してあげます。

JSONファイルのみの場合。

    public function share(Request $request): array
    {
        $locale = config('app.locale');
        $json = lang_path("{$locale}.json");
        $content = file_get_contents($json);
        $translations = json_decode($content, true);

        return array_merge(parent::share($request), [
            'translations' => $translations,
            'auth' => [
                'user' => $request->user(),
            ],
            'ziggy' => function () use ($request) {
                return array_merge((new Ziggy)->toArray(), [
                    'location' => $request->url(),
                ]);
            },
        ]);
    }

PHPファイルも含める場合。

    public function share(Request $request): array
    {
        $locale = config('app.locale');
        $json = lang_path("{$locale}.json");
        $content = file_get_contents($json);
        $translations = json_decode($content, true);
        foreach (glob(lang_path("/{$locale}/*.php")) as $file) {
            $array = require $file;
            $fileName = basename($file, '.php');
            $modifiedArray = array_combine(
                array_map(function($key) use ($fileName) {
                    return "{$fileName}.{$key}";
                }, array_keys($array)),
                $array
            );
            $translations = array_merge($translations, $modifiedArray);
        }

        return array_merge(parent::share($request), [
            'translations' => $translations,
            'auth' => [
                'user' => $request->user(),
            ],
            'ziggy' => function () use ($request) {
                return array_merge((new Ziggy)->toArray(), [
                    'location' => $request->url(),
                ]);
            },
        ]);
    }

PHPファイルも含めるとなると、コードも冗長になるしReactに渡すデータも大きくなってしまいます。PHPファイルの翻訳情報はコントローラーから渡してあげて、Reactに渡すのはJSONだけのほうがいいかなー、と個人的には思います。

app.tsxファイルに翻訳用関数を追加する

Laravelのヘルパー関数みたいにどこでも呼び出せるようにグローバルで定義しておきます。

import './bootstrap';
import '../css/app.css';

import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

declare global {
  function __(key: string, replace?: Record<string, unknown>): string;
}

createInertiaApp({
  title: (title) => `${title} - ${appName}`,
  resolve: (name) => resolvePageComponent(`./Pages/${name}.tsx`, import.meta.glob('./Pages/**/*.tsx')),
  setup({ el, App, props }) {
    const root = createRoot(el);
    const translations = Object(props.initialPage.props.translations);

    window.__ = (key: string, replace?: Record<string, unknown>): string => {
      let translatedText: string = translations[key] || key;
      for (const key in replace) {
        const replacedText = String(replace[key as keyof typeof replace]);
        translatedText = translatedText.replace(`:${key}`, replacedText);
      }
      return translatedText;
    };

    root.render(<App {...props} />);
  },
  progress: {
    color: '#4B5563',
  },
});

以上で、Laravelの翻訳ファイルをReactで使えるようになりました。

<p>{__('Good morning!'}</p> // rendered as <p>Selamat pagi!</p>

ルート関数みたいに公式でサポートしてくれる日が来るといいですね。

コメント

タイトルとURLをコピーしました