こんにちはjunです。個人開発で多言語対応のLaravelアプリを作っています。多言語では各種言語の単語・文章のマッピング(辞書)をする配列・JSONを作成し、レンダリング時にメソッドを使用して文字を表示します。Laravelではresources/lang
配下にen
、ja
のようなディレクトリを作成し、その中に言語のマッピングをします。大体は以下のような配列を作ります。
<?php
return [
'login'=>'ログイン',
'logout'=>'ログアウト'
]
そしてビューファイルなどで__('words.login')
のように使用します。
__
みたいな名称である理由としては、使いまくるので簡単な名称になっています。Vueとかでは$t()
みたいなものを使用します。
上記のようなPHPファイルを作成してもできますが、resources/lang
直下にen.json
、ja.json
のようなJSONファイルを作成しても、多言語メソッドで呼び出せます。
JSONで作るメリットはマッピングのデータを他のアプリでも利用できることです。例えば、Vue・ReactではVuei18n、react-i18nextなどを使用します。同じように多言語メソッドを使用します。その際のマッピングデータとしてJSONを使用します。であればJSONファイルを作っておくことで、Vueや外部へマッピングデータを提供しやすくなります。
ただしJSONで作成するとLaravel側で__('words.login')
のような呼び出しができません。この時JSONは以下のようになっています。
{
"words":{
"login":"ログイン",
"logout":"ログアウト"
}
}
すべて一次元にしてしまうと後で混乱してしまうので、カスケードさせておくと良いです。Vuei18nでは$t('words.login')
で呼び出せますが、なぜかLaravelではJSONのカスケード配下のキーを呼び出すことができません。
そのため
という2重管理でそれぞれ設定しないといけなくなり、非効率的です。
解決方法としてはPHPで作成した配列をJSONにダンプすることです。そうすることでPHPファイルとJSONの言語ファイルを作成することができます。そこで今回の記事ではPHPの言語ファイルをカスケードさせたJSONに出力するようなartisan
コマンドを作ってみたいと思います。
まずはLaravelの言語ファイルのドキュメントの通り、resources/lang
配下にen
、ja
のようなディレクトリを作成しておきましょう。今回は英語と日本語にしておきます。
resources
├── lang
│ ├── en
│ │ ├── auth.php
│ │ ├── words.php
│ │ └── exceptions.php
│ └── ja
│ ├── auth.php
│ ├── words.php
│ └── exceptions.php
...
そしてそのディレクトリごとにphpファイルを分けます。とりあえずこのようにしておきます。
それではカスタムのコマンドを作成しましょう。
php artisan make:command Dumplang
コマンドもartisanで作成できます。app/Console/Commands
というディレクトリが作成され、そこにファイルが作成されます。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Dumblang extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lang:dump';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Convert each php lang files to JSON files.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
}
}
まずは上記のように、シグネチャと説明を記述します。シグネチャではlang:dump
とすると
php artisan lang:dump
と入力するとこのコマンドを実行できます。
コマンドの内容はhandle()
に記述します。全体は以下の通りです。
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Lang;
public function handle()
{
$suports = ["ja","en"];
foreach($suports as $lng){
$langdir = resource_path('lang/'.$lng);
if(is_dir($langdir)){
$files = scandir($langdir);
if($files === false) continue;
$files = array_filter($files,function($f){
return strpos($f,'.php') !== false;
});
$trans = [];
foreach($files as $f){
$content_key = str_replace('.php','',$f);
$content = include resource_path("lang/$lng/$f");
$trans[$content_key] = $content;
}
$json = json_encode($trans,JSON_UNESCAPED_UNICODE);
File::put(resource_path('lang/'.$lng.'.json'),$json);
File::put(base_path('nuxt/lang/'.$lng.'.json'),$json);
}else{
print("Lang directory for ${$lng} dose not exisits");
}
}
return Command::SUCCESS;
}
$suports = ["ja","en"];
foreach($suports as $lng){
}
まずは取得予定の言語の配列を用意します。それを再帰的に処理します。
元となる言語ディレクトリからファイルの一覧を取得します。
foreach($suports as $lng){
$langdir = resource_path('lang/'.$lng);
if(is_dir($langdir)){
$files = scandir($langdir);
if($files === false) continue;
$files = array_filter($files,function($f){
return strpos($f,'.php') !== false;
});
$trans = [];
foreach($files as $f){
$content_key = str_replace('.php','',$f);
$content = include resource_path("lang/$lng/$f");
$trans[$content_key] = $content;
}
// ....
}
}
resource_path('lang/'.$lng)
で先程の言語ディレクトリのパスを取得し、scandir()
を用いて内部のファイルを配列で取得します。scandir()
はphp以外のファイルや.
みたいなSELinuxが勝手に作る謎ファイルも読み取ってしまうので、array_filter()
を用いてフィルターします。
$trans = [];
foreach($files as $f){
$content_key = str_replace('.php','',$f);
$content = include resource_path("lang/$lng/$f");
$trans[$content_key] = $content;
}
JSONではphpファイル名を一次キーとして利用したいので str_replace('.php','',$f)
でファイル名を取得します。include resource_path("lang/$lng/$f")
でphpファイルの記述を取得ます。そしてJSONにする配列に、ファイル名をキーとして打ち込みます。$trans[$content_key] = $content;
それをスキャンした言語PHPファイル全てに行います。
$json = json_encode($trans,JSON_UNESCAPED_UNICODE);
File::put(resource_path('lang/'.$lng.'.json'),$json);
そして1つにまとめた配列をjson_encode
をしておき、それをresources/lang
配下します。その際には言語名.json
となるようにしておきます。
コードは以下の通りです。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Lang;
class Dumblang extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lang:dump';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Convert each php lang files to JSON files.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$suports = config('app.support_langs');
foreach($suports as $lng){
$langdir = resource_path('lang/'.$lng);
if(is_dir($langdir)){
$files = scandir($langdir);
if($files === false) continue;
$files = array_filter($files,function($f){
return strpos($f,'.php') !== false;
});
$trans = [];
foreach($files as $f){
$content_key = str_replace('.php','',$f);
$content = include resource_path("lang/$lng/$f");
$trans[$content_key] = $content;
}
$json = json_encode($trans,JSON_UNESCAPED_UNICODE);
File::put(resource_path('lang/'.$lng.'.json'),$json);
}else{
print("Lang directory for ${$lng} dose not exisits");
}
}
return Command::SUCCESS;
}
}
ちょっと気をつける点としては
is_dir($langdir)
で言語ディレクトリの存在チェックscandir()
で取得したファイルをフィルタする実行してみるとja.json
、en.json
というのが作成され、みてみると
resources
├── lang
│ ├── en
│ │ ├── auth.php
│ │ ├── words.php
│ │ └── exceptions.php
│ └── ja
│ ├── auth.php
│ ├── words.php
│ └── exceptions.php
...
↓
{
"auth":{
"login":"ログイン",
"logout":"ログアウト",
},
"words":{
"save":"保存",
"update":"更新"
},
"exceptions":{
"401":"ログインしてください。",
"model":{
"403":"このデータにアクセスできません。",
"404":"対応するデータが見つかりません。"
}
}
}
{
"auth":{
"login":"Login",
"logout":"Logout",
},
"words":{
"save":"Saving",
"update":"Updating"
},
"exceptions":{
"401":"Please login.",
"model":{
"403":"You can not access to this data.",
"404":"The data you request is not found."
}
}
}
これでPHPファイルだけでJSONの言語ファイルも作成して、フロントでのVueの言語ファイルなどに提供することができるようになります。動的にこのJSONファイルは生成するので、バージョン管理をするときは.gitignore
で指定しておくといいです。そしてデプロイや更新時にはこのコマンドを打ち忘れないようにしましょう。
ちなみにですが、実際にやっていることは特定のディレクトリのPHPファイルの内容を取得して、それをJSONにして生成したファイルを置いているだけなので素のPHP、pythonやrubyとか他の言語でも行けると思いますよ。