目次
はじめに
4月から新入社員として入社しました。臼井と申します。
今回の記事では、Webアプリケーションの多言語対応について書きます。
現在担当している開発では、日本語と英語で表示切り替えできるよう、フロント側とAPI側の両方で対応を進めています。
まだ言語の表示切り替え機能の実装は完了していませんが、どのような方針で進めているか、実装時にどのような対応をしているかを中心にまとめます。
特に、既存画面への影響を抑えながら少しずつ多言語対応を進める方法や、AIを活用してハードコードされた文言を翻訳ファイルに切り出す進め方について書いています。
開発環境
今回の多言語対応では、以下の環境で実装を進めています。
- Laravel 12.42.0
- Vue.js 2.7.16
- vue-i18n 8.28.2
方針
多言語対応を進めるにあたり、以下の方針で進めています。
- 日本語と英語の切り替えのみ対応する
- 画面に直接書かれている文字列を翻訳ファイルに切り出す
- いきなり全ページを対応するのではなく、影響範囲の小さいページから対応する
ソーイでは少数精鋭で開発を行っているため、全ページを一気に多言語対応すると、不具合が多発した際に通常の開発へ影響する可能性があります。
そのため、不具合の影響を受けにくいページから順番に対応しています。
全体構成
今回の多言語対応では、フロント側とAPI側でそれぞれ翻訳ファイルを管理しています。
フロント側では vue-i18n を使用し、ja.json と en.json から表示する文言を取得します。
API側では、ミドルウェアでLaravelのロケールを設定し、lang/ja または lang/en の翻訳ファイルからメッセージを取得します。
実装
実装は大きく分けて、フロント側とAPI側で対応しています。
フロント
フロント側では、画面に直接書かれている文字列を翻訳ファイルから取得する形に変更しています。
例えば、画面に直接以下のように書かれている文言を、
アカウント管理
以下のように翻訳ファイルから取得する形に変更します。
{{ $t('account.title') }}
i18nの設定
app.js
vue-i18n と日本語・英語の翻訳ファイルを読み込み、Vueアプリ全体で利用できるように設定しています。
まず、vue-i18n と翻訳ファイルを読み込み、Vueに登録します。
import VueI18n from 'vue-i18n'
import ja from '../../i18n/locales/ja.json'
import en from '../../i18n/locales/en.json'
Vue.use(VueI18n)
次に、ブラウザの言語設定を取得し、初期表示する言語を判定します。
const detectLocale = () => {
const langs = (navigator.languages && navigator.languages.length)
? navigator.languages
: [navigator.language]
const first = (langs[0] || '').toLowerCase()
if (first.startsWith('ja')) return 'ja'
if (first.startsWith('en')) return 'en'
return 'ja'
}
ブラウザの優先言語が日本語の場合はja、英語の場合はenを設定します。それ以外の言語の場合は、日本語を設定しています。
判定した言語を使用して、VueI18nのインスタンスを作成します。
const i18n = new VueI18n({
locale: detectLocale(),
fallbackLocale: 'ja',
messages: { ja, en },
})
最後に、作成したi18nをVueインスタンスに設定します。
new Vue({
el: '#app',
store,
router,
i18n, // 追加
})
これにより、Vueアプリ配下の各コンポーネントでは、VueI18n を個別に読み込むことなく、$t()やthis.$i18nを使用できます。
コンポーネント
フロント側では確認箇所が多いため、動作確認を行いやすいよう、確認対象のコンポーネントにlocaleを渡し、日本語と英語を切り替えて表示確認できるようにしています。
export default {
name: 'Account',
props: {
locale: {
type: String,
default: 'ja',
},
},
}
コンポーネントの生成時に、受け取ったlocaleを表示言語として設定しています。
localeのデフォルト値には日本語を設定しています。
created: function() {
this.$i18n.locale = this.locale ? this.locale : 'ja'
}
翻訳ファイル
翻訳キーは、対象となるコンポーネントのファイルパスを基に命名しています。
例えば、対象のファイルがresources/components/account.vueの場合、
resources/componentsより後のパスを基に、account.titleのような翻訳キーを作成しています。
以下のような形で翻訳ファイルを用意しています。
en.json
{
"account": {
"title": "Account Management"
}
}
ja.json
{
"account": {
"title": "アカウント管理"
}
}
API
API側では、返却するメッセージを翻訳ファイルから取得する形に変更しています。
例えば、直接記述していた返却メッセージを、
アカウント管理
以下のように翻訳ファイルから取得する形に変更します。
__('locale.account.title')
ミドルウェア
Laravelのミドルウェアを使って言語の切り替え設定を行っています。
app/Http/Middleware/SetLocale.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\App;
class SetLocale
{
public function handle($request, Closure $next)
{
$lang = 'ja'; // 現在はリクエストヘッダーから取得せず、日本語に固定
if ($lang) {
$lang = strtolower($lang);
App::setLocale(str_starts_with($lang, 'ja') ? 'ja' : 'en');
}
return $next($request);
}
}
将来的にはリクエストヘッダーの Accept-Language をもとに言語を切り替える想定です。
$lang = $request->header('Accept-Language');
取得した言語が ja から始まる場合は日本語、それ以外の場合は英語に設定するようにしています。
App::setLocale(str_starts_with($lang, 'ja') ? 'ja' : 'en');
次に、作成したミドルウェアをAPIのミドルウェアグループに追加します。
bootstrap/app.php
use App\Http\Middleware\SetLocale;
use Illuminate\Foundation\Configuration\Middleware;
// ...
->withMiddleware(function (Middleware $middleware): void {
$middleware->api(prepend: [
SetLocale::class,
]);
})
翻訳ファイル
API側では、Laravelの lang ディレクトリ配下に翻訳ファイルを作成しています。
lang/en/locale.php
lang/ja/locale.php
API側でも、対象となるファイルのパスを基に命名し、 app/Http より後のパスを基準に翻訳キーを作成しています。
lang/en/locale.php
<?php
return [
'account' => [
'title' => 'Account Management',
],
];
lang/ja/locale.php
<?php
return [
'account' => [
'title' => 'アカウント管理',
],
];
進め方
多言語対応では、画面に直接書かれている文字列を探し、翻訳ファイルに切り出していく必要があります。
現在は、ChatGPTにファイル名とソースコードを貼り付け、翻訳対象の洗い出しや修正後のコード作成に活用しています。
入力例
ハードコードしている箇所を{{ $t('..') }} を使って en.json / ja.json から取得するように変更したいです。
改行・インデント・スペース数を一切変えずに、ハードコード文字列だけを$t()に置き換える形で出してください。
export default{}のpropsにlocale{typeをString,defaultをja}を追加してください。
created function()のすぐ下にthis.$i18n.locale = this.locale ? this.locale : 'ja'を追加してください。
「ハードコードされている箇所の洗い出し」「ja.json」「en.json」「下記のソースコードの修正後のソースコード(ファイル名)」の順で出力してください。
ファイルパス:
resources/components/account.vue
ソースコード:
<template>
<div>
<h1>アカウント管理</h1>
<p>ユーザー情報を確認・編集できます。</p>
<button>保存</button>
</div>
</template>
<script>
export default {
name: 'Account',
created: function() {
console.log('created')
}
}
</script>
出力例
ハードコードされている箇所の洗い出し
アカウント管理
ユーザー情報を確認・編集できます。
保存
ja.json
{
"account": {
"title": "アカウント管理",
"description": "ユーザー情報を確認・編集できます。",
"save": "保存"
}
}
en.json
{
"account": {
"title": "Account Management",
"description": "You can view and edit user information.",
"save": "Save"
}
}
修正後のソースコード(account.vue)
<template>
<div>
<h1>{{ $t('account.title') }}</h1>
<p>{{ $t('account.description') }}</p>
<button>{{ $t('account.save') }}</button>
</div>
</template>
<script>
export default {
name: 'Account',
props: {
locale: {
type: String,
default: 'ja'
}
},
created: function() {
this.$i18n.locale = this.locale ? this.locale : 'ja'
console.log('created')
}
}
</script>
一方で、出力結果をそのまま使えるとは限りません。
特に以下のような点には注意しています。
- インデントや改行が変わって差分が多くなることがある
- 本来変更しなくてよい箇所まで変更されることがある
- 翻訳キーの命名が不自然になることがある
-
$t()に置き換えるべきではない文字列まで置き換えられることがある - 出力されたコードがそのまま動かないことがある
そのため、ChatGPTの出力をそのまま貼り付けるのではなく、差分を確認しながら必要に応じてプロンプトを修正しています。
学び・振り返り
今回の多言語対応を通して、単純に日本語を英語に置き換えるだけではなく、今後の保守性を考えながら進めることが重要だと感じました。
特に、翻訳キーの命名や翻訳ファイルの分け方は、後から見たときに分かりやすい形にしておかないと、修正や追加対応が難しくなります。
また、AIを活用することで作業スピードは上がりますが、最終的には自分で差分や動作を確認する必要があります。AIに任せきりにするのではなく、作業を補助するものとして使うことが大切だと感じました。
参考文献
お知らせ
技術ブログを週1〜2本更新中、ソーイをフォローして最新記事をチェック!
https://qiita.com/organizations/sewii