Laravel mix vue No.8 - Laravel Internationalization - Laravel多言語化

こんにちは、あすかのkoheiです。

今回はLaravel側を多言語化します。

連載記事

Laravel Internationalization - Laravel多言語化

サンプル


フォルダ構成


└─ server
   ├─ app
   |  └─ Http
   |     ├─ Middleware
+  |     |  └─ Language.php
   |     └─ Kernel.php
   ├─ resources
   |  ├─ lang
+  |  |  └─ ja
+  |  |     ├─ auth.php
+  |  |     ├─ pagination.php
+  |  |     ├─ passwords.php
+  |  |     └─ validation.php
   |  ├─ js
   |  |  └─ components
   |  |     └─ Header.vue
+  |  └─ helper.js
   └─ routes
      ├─ api.php
      └─ web.php

dockerスタート

gitからクローンした場合は.envの作成と設定を忘れないように!

# コンテナスタート
docker-compose start

# コンテナに入る
docker-compose exec php bash

# composerをインストール(前回からの続きで行う場合はいらない)
composer install

# npmをインストール(前回からの続きで行う場合はいらない)
npm i

# encryption keyを作成(前回からの続きで行う場合はいらない)
php artisan key:generate

# ホットリリード開始
npm run watch

言語のコントロール

言語の切り替えるApiを作成

server\routes\api.phpに言語切替用のルートを作成して、セッションに言語をセットするようにする


    ...
    // トークンリフレッシュ
    Route::get('/refresh-token', function (Illuminate\Http\Request $request) {
        $request->session()->regenerateToken();
        return response()->json();
    })->name('refresh-token');

+   // set lang
+   Route::get('/set-lang/{lang}', function (Illuminate\Http\Request $request, $lang) {
+       $request->session()->put('language', $lang);
+       return response()->json();
+   })->name('set-lang');

言語切替用にミドルウェアを作成

artisanコマンドを使っていLanguageという名前でミドルウェアを作成


php artisan make:middleware Language

server\app\Http\Middleware\Language.phpが作られるので編集


<?php

namespace App\Http\Middleware;

    use Closure;
+   use App;

    class Language
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
+           // 設定されているロケールを取得
+           $locale = App::getLocale();
+
+           // セッションのロケールを取得ないときはデフォルトロケール
+           $leng = $request->session()->get('language');
+
+           // ロケールがあって、言語が変わる場合
+           if ($leng && $locale !== $leng) {
+               // 言語をセット
+               // ない場合は「config/app.php」に設定した「fallback_locale」の値になる
+               App::setLocale($leng);
+           }

            return $next($request);
        }
    }

作成したミドルウェアを登録

ルートでミドルウェアを使うのでserver\app\Http\Kernel.phprouteMiddlewareに登録


    ...

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+       'language' => \App\Http\Middleware\Language::class,
    ];

    ...

作成したミドルウェアをルートにセット

server\routes\api.phpを編集


    ...

+   Route::middleware(['language'])->group(function () {
        // register
        Route::post('/register', 'Auth\RegisterController@sendMail')->name('register');
        // login
        Route::post('/login', 'Auth\LoginController@login')->name('login');
        // logout
        Route::post('/logout', 'Auth\LoginController@logout')->name('logout');
        // forgot
        Route::post('/forgot', 'Auth\ForgotPasswordController@forgot')->name('reset');
        // reset
        Route::post('/reset', 'Auth\ResetPasswordController@reset')
            ->name('reset');
        // user
        Route::get('/user', function () {
            return Auth::user();
        })->name('user');
        // トークンリフレッシュ
        Route::get('/refresh-token', function (Illuminate\Http\Request $request) {
            $request->session()->regenerateToken();
            return response()->json();
        })->name('refresh-token');
+   });

    // set lang
    Route::get('/set-lang/{lang}', function (Illuminate\Http\Request $request, $lang) {
        $request->session()->put('language', $lang);
        return response()->json();
    })->name('set-lang');

server\routes\web.phpを編集


    ...

+   Route::middleware(['language'])->group(function () {
        // verification callback
        Route::get('/verification/{token}', 'Auth\VerificationController@register')
            ->name('verification');

        // reset password callback
        Route::get('/reset-password/{token}', 'Auth\ResetPasswordController@resetPassword')
            ->name('reset-password');

        // socialite 各プロバイダにリダイレクトするルート
        Route::get('/login/{provider}', 'Auth\LoginController@redirectToProvider');

        // socialite 各プロバイダからのコールバックを受けるルート
        Route::get('/login/{provider}/callback', 'Auth\LoginController@handleProviderCallback');

        // API以外はindexを返すようにして、VueRouterで制御
        Route::get('/{any?}', fn () => view('index'))->where('any', '.+');
+   });

Vue側の修正

使用するモジュールインストール

言語をストレージに保存するので、便利なモジュールを使用


  npm i vue-localstorage

使用例


    // データ保存
    this.$localStorage.set(key,value)

    // データ削除
    this.$localStorage.remove(key)

    // データ取得
    this.$localStorage.get(key)

    //データ取得 デフォルト値を仕様
    this.$localStorage.get(key, default)

Vueで使う用意

server\resources\js\app.jsに追加


    ...

    /**
     * vue-localstorage
     * https://www.npmjs.com/package/vue-localstorage
     */
    // vue-localstorageをインポート
+   import VueLocalStorage from "vue-localstorage";
    // VueLocalStorage宣言
    // 「$this.storage.get()」のように使えるように名前を変更
    // この名前でプロパティを宣言する
+   Vue.use(VueLocalStorage, {
+       name: 'storage'
+   });

    ...

言語取得のメソッドを作る

いろんなとこで使う関数を登録しておくヘルパーserver\resources\js\helper.jsとしてを作って、言語取得の関数を作る


export default class Helper {

    static capitalizeFirstLetter(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    /**
     * get language
     * ブラウザから言語を取得
     *
     */
    static getLanguage() {
        const language = (window.navigator.languages && window.navigator.languages[0]) ||
            window.navigator.language ||
            window.navigator.userLanguage ||
            window.navigator.browserLanguage;
        return language.slice(0, 2);
    }
}

言語を取得と設定

server\resources\js\components\Header.vueを編集してヘッダコンポネントで言語切り替えできるようにする


    <template>
        <header>
            <!-- リンクを設定 -->
            <RouterLink to="/">{{ $t('word.home') }}</RouterLink>
            <RouterLink v-if="!isLogin" to="/login">{{ $t('word.login') }}</RouterLink>
            <!-- ログインしている場合はusernameを表示 -->
            <span v-if="isLogin">{{username}}</span>
            <!-- クリックイベントにlogoutメソッドを登録 -->
            <span v-if="isLogin" @click="logout">logout</span>
            <!-- 言語切替 -->
+           <select v-model="selectedLang" @change="changeLang">
+               <option v-for="lang in langList" :value="lang.value" :key="lang.value">{{ lang.text }}</option>\
+           </select>
        </header>
    </template>

    <script>
+   import Cookies from "js-cookie";
+   import Helper from "../helper";

    export default {
        computed: {
            ...
        },
        // app.jsでVueLocalStorageの名前を変更したので「storage」で宣言
+       storage: {
+           language: {
+               type: String,
+               default: null
+           }
+       },
        methods: {
            ...
              // 言語切替メソッド
+             changeLang() {
+                 // ローカルストレージに「language」をセット
+                 this.$storage.set("language", this.selectedLang);
+                 // Apiリクエスト 言語を設定
+                 axios.get(`/api/set-lang/${this.selectedLang}`);
+             }
        },
+       created() {
+           // ローカルストレージから「language」を取得
+           this.selectedLang = this.$storage.get("language");
+
+           // サーバ側をクライアント側に合わせる
+
+           // storageLangがない場合
+           if (!this.selectedLang) {
+               // ブラウザーの言語を取得
+               const defaultLang = Helper.getLanguage();
+               // ローカルストレージに「language」をセット
+               this.$storage.set("language", defaultLang);
+               // Apiリクエスト 言語を設定
+               axios.get(`/api/set-lang/${defaultLang}`);
+           } 
+           // ある場合はサーバ側をクライアント側に合わせる
+           else {
+               axios.get(`/api/set-lang/${this.selectedLang}`);
+           }
+       }
    };
    </script>

laravelに準備してある言語ファイル

laravelに準備してある言語ファイルの日本語版を作るために server\resources\langjaフォルダを作成して以下を作成する

  • auth.php
  • pagination.php
  • passwords.php
  • validation.php

auth.php

server\resources\lang\ja\auth.phpを作成


<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Language Lines
    |--------------------------------------------------------------------------
    |
    | The following language lines are used during authentication for various
    | messages that we need to display to the user. You are free to modify
    | these language lines according to your application's requirements.
    |
    */

    'failed'   => '認証に失敗しました。',
    'throttle' => 'ログイン試行回数が制限に達しました。 :seconds 秒以上開けて再度お試しください。',

];

pagination.php

server\resources\lang\ja\pagination.phpを作成


<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Pagination Language Lines
    |--------------------------------------------------------------------------
    |
    | The following language lines are used by the paginator library to build
    | the simple pagination links. You are free to change them to anything
    | you want to customize your views to better match your application.
    |
    */

    'previous' => '« 前へ',
    'next'     => '次へ »',

];

passwords

server\resources\lang\ja\passwords.phpを作成


<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Password Reset Language Lines
    |--------------------------------------------------------------------------
    |
    | The following language lines are the default lines which match reasons
    | that are given by the password broker for a password update attempt
    | has failed, such as for an invalid token or invalid new password.
    |
    */

    'reset'     => 'パスワードがリセットされました!',
    'sent'      => 'パスワードリセットリンクを電子メールで送信しました!',
    'throttled' => '再試行する前にお待ちください。',
    'token'     => 'このパスワードリセットトークンは無効です。',
    'user'      => "この電子メールアドレスを持つユーザーが見つかりません。",

];

validation

server\resources\lang\ja\validation.phpを作成


<?php

return [

    /*
    |--------------------------------------------------------------------------
    | バリデーション言語行
    |--------------------------------------------------------------------------
    |
    | 以下の言語行はバリデタークラスにより使用されるデフォルトのエラー
    | メッセージです。サイズルールのようにいくつかのバリデーションを
    | 持っているものもあります。メッセージはご自由に調整してください。
    |
    */

    'accepted'             => ':attributeを承認してください。',
    'active_url'           => ':attributeが有効なURLではありません。',
    'after'                => ':attributeには、:dateより後の日付を指定してください。',
    'after_or_equal'       => ':attributeには、:date以降の日付を指定してください。',
    'alpha'                => ':attributeはアルファベットのみがご利用できます。',
    'alpha_dash'           => ':attributeはアルファベットとダッシュ(-)及び下線(_)がご利用できます。',
    'alpha_num'            => ':attributeはアルファベット数字がご利用できます。',
    'array'                => ':attributeは配列でなくてはなりません。',
    'before'               => ':attributeには、:dateより前の日付をご利用ください。',
    'before_or_equal'      => ':attributeには、:date以前の日付をご利用ください。',
    'between'              => [
        'numeric' => ':attributeは、:minから:maxの間で指定してください。',
        'file'    => ':attributeは、:min kBから、:max kBの間で指定してください。',
        'string'  => ':attributeは、:min文字から、:max文字の間で指定してください。',
        'array'   => ':attributeは、:min個から:max個の間で指定してください。',
    ],
    'boolean'              => ':attributeは、trueかfalseを指定してください。',
    'confirmed'            => ':attributeと、確認フィールドとが、一致していません。',
    'date'                 => ':attributeには有効な日付を指定してください。',
    'date_equals'          => ':attributeには、:dateと同じ日付けを指定してください。',
    'date_format'          => ':attributeは:format形式で指定してください。',
    'different'            => ':attributeと:otherには、異なった内容を指定してください。',
    'digits'               => ':attributeは:digits桁で指定してください。',
    'digits_between'       => ':attributeは:min桁から:max桁の間で指定してください。',
    'dimensions'           => ':attributeの図形サイズが正しくありません。',
    'distinct'             => ':attributeには異なった値を指定してください。',
    'email'                => ':attributeには、有効なメールアドレスを指定してください。',
    'ends_with'            => ':attributeには、:valuesのどれかで終わる値を指定してください。',
    'exists'               => '選択された:attributeは正しくありません。',
    'file'                 => ':attributeにはファイルを指定してください。',
    'filled'               => ':attributeに値を指定してください。',
    'gt'                   => [
        'numeric' => ':attributeには、:valueより大きな値を指定してください。',
        'file'    => ':attributeには、:value kBより大きなファイルを指定してください。',
        'string'  => ':attributeは、:value文字より長く指定してください。',
        'array'   => ':attributeには、:value個より多くのアイテムを指定してください。',
    ],
    'gte'                  => [
        'numeric' => ':attributeには、:value以上の値を指定してください。',
        'file'    => ':attributeには、:value kB以上のファイルを指定してください。',
        'string'  => ':attributeは、:value文字以上で指定してください。',
        'array'   => ':attributeには、:value個以上のアイテムを指定してください。',
    ],
    'image'                => ':attributeには画像ファイルを指定してください。',
    'in'                   => '選択された:attributeは正しくありません。',
    'in_array'             => ':attributeには:otherの値を指定してください。',
    'integer'              => ':attributeは整数で指定してください。',
    'ip'                   => ':attributeには、有効なIPアドレスを指定してください。',
    'ipv4'                 => ':attributeには、有効なIPv4アドレスを指定してください。',
    'ipv6'                 => ':attributeには、有効なIPv6アドレスを指定してください。',
    'json'                 => ':attributeには、有効なJSON文字列を指定してください。',
    'lt'                   => [
        'numeric' => ':attributeには、:valueより小さな値を指定してください。',
        'file'    => ':attributeには、:value kBより小さなファイルを指定してください。',
        'string'  => ':attributeは、:value文字より短く指定してください。',
        'array'   => ':attributeには、:value個より少ないアイテムを指定してください。',
    ],
    'lte'                  => [
        'numeric' => ':attributeには、:value以下の値を指定してください。',
        'file'    => ':attributeには、:value kB以下のファイルを指定してください。',
        'string'  => ':attributeは、:value文字以下で指定してください。',
        'array'   => ':attributeには、:value個以下のアイテムを指定してください。',
    ],
    'max'                  => [
        'numeric' => ':attributeには、:max以下の数字を指定してください。',
        'file'    => ':attributeには、:max kB以下のファイルを指定してください。',
        'string'  => ':attributeは、:max文字以下で指定してください。',
        'array'   => ':attributeは:max個以下指定してください。',
    ],
    'mimes'                => ':attributeには:valuesタイプのファイルを指定してください。',
    'mimetypes'            => ':attributeには:valuesタイプのファイルを指定してください。',
    'min'                  => [
        'numeric' => ':attributeには、:min以上の数字を指定してください。',
        'file'    => ':attributeには、:min kB以上のファイルを指定してください。',
        'string'  => ':attributeは、:min文字以上で指定してください。',
        'array'   => ':attributeは:min個以上指定してください。',
    ],
    'not_in'               => '選択された:attributeは正しくありません。',
    'not_regex'            => ':attributeの形式が正しくありません。',
    'numeric'              => ':attributeには、数字を指定してください。',
    'password'             => 'パスワードが正しくありません。',
    'present'              => ':attributeが存在していません。',
    'regex'                => ':attributeに正しい形式を指定してください。',
    'required'             => ':attributeは必ず指定してください。',
    'required_if'          => ':otherが:valueの場合、:attributeも指定してください。',
    'required_unless'      => ':otherが:valuesでない場合、:attributeを指定してください。',
    'required_with'        => ':valuesを指定する場合は、:attributeも指定してください。',
    'required_with_all'    => ':valuesを指定する場合は、:attributeも指定してください。',
    'required_without'     => ':valuesを指定しない場合は、:attributeを指定してください。',
    'required_without_all' => ':valuesのどれも指定しない場合は、:attributeを指定してください。',
    'same'                 => ':attributeと:otherには同じ値を指定してください。',
    'size'                 => [
        'numeric' => ':attributeは:sizeを指定してください。',
        'file'    => ':attributeのファイルは、:sizeキロバイトでなくてはなりません。',
        'string'  => ':attributeは:size文字で指定してください。',
        'array'   => ':attributeは:size個指定してください。',
    ],
    'starts_with'          => ':attributeには、:valuesのどれかで始まる値を指定してください。',
    'string'               => ':attributeは文字列を指定してください。',
    'timezone'             => ':attributeには、有効なゾーンを指定してください。',
    'unique'               => ':attributeの値は既に存在しています。',
    'uploaded'             => ':attributeのアップロードに失敗しました。',
    'url'                  => ':attributeに正しい形式を指定してください。',
    'uuid'                 => ':attributeに有効なUUIDを指定してください。',

    /*
    |--------------------------------------------------------------------------
    | Custom バリデーション言語行
    |--------------------------------------------------------------------------
    |
    | "属性.ルール"の規約でキーを指定することでカスタムバリデーション
    | メッセージを定義できます。指定した属性ルールに対する特定の
    | カスタム言語行を手早く指定できます。
    |
    */

    'custom' => [
        '属性名' => [
            'ルール名' => 'カスタムメッセージ',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | カスタムバリデーション属性名
    |--------------------------------------------------------------------------
    |
    | 以下の言語行は、例えば"email"の代わりに「メールアドレス」のように、
    | 読み手にフレンドリーな表現でプレースホルダーを置き換えるために指定する
    | 言語行です。これはメッセージをよりきれいに表示するために役に立ちます。
    |
    */

    'attributes' => [],

];

独自に設定したメッセージの言語ファイル

翻訳メッセージの取得箇所を調べる

ここまででLang::get()__()のどちらもつかっていいるので、この2つで検索
関数をヘルパ関数__()に統一する。

server\app\Http\Controllers\Auth\LoginController.phpを確認


    ...

    // メッセージをつけてリダイレクト
-   $message = Lang::get('socialite login success.');
+   $message = __('socialite login success.');

    ...

    // メッセージをつけてリダイレクト
-   $message = Lang::get('authentication failed.');
+   $message = __('authentication failed.');

    ...

server\app\Http\Controllers\Auth\ResetPasswordController.phpを確認


    ...

    // メッセージをクッキーにつけてリダイレクト
-   $message = Lang::get('password reset email has not been sent.');
+   $message = __('password reset email has not been sent.');

    ...

    $validator->errors()->add('token', __('invalid token.'));

    ...

    $validator->errors()->add('token', __('expired token.'));

    ...

    $validator->errors()->add('token', __('is not user.'));

    ...

server\app\Http\Controllers\Auth\VerificationController.phpを確認


    ...
    $message = __('not provisionally registered.');
    ...
    $message = __('completion of registration.');
    ...

多言語ヘルパを忘れていたのでつける

server\app\Mail\VerificationMail.phpを修正


    ...
    public function build()
    {
        // 件名
-       $subject = 'Verification mail';
+       $subject = __('verification mail');
    ...

server\app\Mail\ResetPasswordMail.phpを修正


    ...
    public function build()
    {
        // 件名
-       $subject = 'Reset password mail';
+       $subject = __('reset password mail');
    ...

言語ファイルの作成

Laravel側で多言語化するのはメールテンプレートとメッセージぐらいなので、今回はJsonファイルを使用。

Jsonファイルを使用する場合は翻訳文字列をキーとして利用

英語の場合はserver\resources\lang\en.json
日本語の場合はserver\resources\lang\ja.json
のようにresources/langディレクトリ下に「言語コード + .json」として保存

"--- auth ---": "","--- mail template ---": "",はコメントとして使用


    {
        "--- auth ---": "",
        "authentication failed.": "authentication failed.",
        "socialite login success.": "socialite login success.",
        "password reset email has not been sent.": "password reset email has not been sent.",
        "invalid token.": "invalid token.",
        "expired token.": "expired token.",
        "is not user.": "is not user.",
        "not provisionally registered.": "not provisionally registered.",
        "completion of registration.": "completion of registration.",
        "--- mail template ---": "",
        "verification mail": "Verification mail",
        "registration certification": "registration certification.",
        "please click this link to verify your email.": "please click this link to verify your email.",
        "reset password mail": "Reset password mail",
        "password reset": "password reset",
        "click this link to go to password reset.": "click this link to go to password reset."
    }

    {
        "--- auth ---": "",
        "authentication failed.": "認証に失敗しました。",
        "socialite login success.": "ソーシャルログインに成功しました。",
        "password reset email has not been sent.": "パスワード再設定メールが送信されていません。",
        "invalid token.": "無効なトークンです。",
        "expired token.": "期限切れのトークンです。",
        "is not user.": "ユーザーではありません。",
        "not provisionally registered.": "仮登録されていません。",
        "completion of registration.": "登録の完了しました。",
        "--- mail template ---": "",
        "verification mail": "登録認証メール",
        "registration certification": "登録認証",
        "please click this link to verify your email.": "こちらのリンクをクリックして、登録認証してください。",
        "reset password mail": "パスワードリセットメール",
        "password reset": "パスワードリセット",
        "click this link to go to password reset.": "こちらのリンクをクリックして、パスワードリセットへ移動してください。"
    }

apiで言語が変更されるかテスト

  1. ログイン画面で間違ってみると、英語でバリデーションメッセージが出る

キャプチャ1

  1. Apiで言語を日本語に変えてみる

セレクトボタンで言語を日本語に切り替える

  1. もう一度ログイン画面で間違ってみると、日本語でバリデーションメッセージが出る

キャプチャ4

これでLaravelから送られてくるメッセージは多言語化できました。 次はVueを多言語化してみます。

Laravel mix vue No.9 - Vue Internationalization - Vue多言語化

okuda

Webdeveloper

Laravel mix vue No.8 - Laravel Internationalization - Laravel多言語化

お気軽に
お問い合わせください。

お問い合わせ