UE4 ローカライゼーションダッシュボードを試してみる

# 概要
UnrealEngineでのローカライズツールであるローカライゼーションダッシュボードを試したメモです。


## 更新履歴
| 日付 | 内容 |
|:----|:-----|
|2022/04/04| UKismetInternationalizationLibrary について追記 |

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.25, 4.26


## 参考
[UE4公式 : ローカライゼーション](https://docs.unrealengine.com/ja/ProductionPipelines/Localization/index.html)
[UE4公式 : ローカライゼーションツール](https://docs.unrealengine.com/ja/ProductionPipelines/Localization/LocalizationTools/index.html)
[UE4のローカライズ機能紹介 (UE4 Localization Deep Dive) ](https://www.slideshare.net/EpicGamesJapan/ue4-ue4-localization-deep-dive-191115517)
[最新UE4タイトルでのローカライズ事例 (UE4 Localization Deep Dive) ](https://www.slideshare.net/EpicGamesJapan/ue4-ue4-localization-deep-dive)
[ローカライゼーションのCommandlet紹介](https://shama-coo.hatenablog.com/entry/2020/04/06/235810)
[[UE4]翻訳テキストデータ(Portable Object)について](https://historia.co.jp/archives/13223/)


# 実装準備
## ストリングテーブルを用意する
新規アセットからストリングテーブルを作成します。
![create_stringtable.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/006e6943-f240-c117-8027-ecf9ab0f3e39.jpeg)

ストリングテーブルの `キー` はローカライズ文字列のID、 `ソースストリング` は翻訳対象の文字列を指し示すタグとして扱われます。
![StringTable.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/ba10b24e-7ee3-4319-fe51-37a52a4de464.jpeg)


## ローカライゼーションダッシュボードの設定
UEエディタの[ウィンドウ]->[ローカライゼーションダッシュボード]を選択します。

`テキストを収集` は先ほどのストリングテーブルが入るように設定します。
![LDB_Setting_TextGather.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/d2e77bf1-9704-5198-d3a7-674383a74764.jpeg)

`カルチャ`はデフォルト言語（ネイティブ）を`en-AU(英語-オーストラリア)`にし、翻訳言語を`en(英語)`、`ja(日本語)`、`de(ドイツ語)`とします。
![LDB_Setting_Culture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/318808ee-c401-e980-a57d-2ebaeaeb6b8f.jpeg)

ここでデフォルト言語(ネイティブ)は使用しない言語にしたほうがいいです。


# ローカライゼーションダッシュボードでの作業
## テキストを収集する
[カルチャ] -> [テキストを収集する]を押下するとストリングテーブルのデータを収集します。
![LDB_TextGather.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/db0fbc73-c2d7-44a5-4f6f-76c121973380.jpeg)

ワード数がストリングテーブルの数と一致します。
この状態で英語(オーストラリア)の「このカルチャーの翻訳を編集」でエディタを開くと以下の様に取り込まれているのが確認できます。

![LDB_Editor_en-AU.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/8659deb1-5255-ce34-54e8-554ecbdf54bd.jpeg)

この時、デフォルト言語以外は未翻訳状態でタグだけ表示されている状態となります。

![LDB_Editor_ja.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/f0f77616-ec38-e518-18db-1664f133d145.jpeg)

## 翻訳作業１（エディタで直接編集）
通常は .poファイルや翻訳サービスから翻訳データをインポートしますが、ここではエディタで直接編集してみます。
各翻訳言語の未翻訳の`Translation`を直接編集し、再度エディタで確認するとこのようになります。

![LDB_Translation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/71078154-e22d-7754-6ef2-7d17854b7f69.png)

デフォルト言語（en-AU）の `Translation` が翻訳言語(ja,en,de)の `Source` になり、各翻訳言語の `Translation` が実際翻訳された文字列となります。

<font color=red>上記では変えていますが、デフォルト言語の`Source`と`Translate`と翻訳言語の`Source`を3つとも同じにしてみて試してみましたが特に問題ありませんでした。途中で翻訳データを一部変更をしない場合（毎回テキスト収集からやる場合）は問題ないと思われます。（検証不十分）</font>


この時点で `Content/Localization` フォルダに各言語の翻訳データがはいったjson形式のファイル `.archive` が作成されています。
![LocalizationFolder.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/3d025d8a-290e-5e14-8758-0ad544e28e5a.jpeg)

中身は以下のような感じになっています。

```json:jp/Game.archive
{
	"FormatVersion": 2,
	"Namespace": "",
	"Subnamespaces": [
		{
			"Namespace": "ST_MessageInfo",
			"Children": [
				{
					"Source":
					{
						"Text": "Str000Tag"
					},
					"Translation":
					{
						"Text": "こんにちは"
					},
					"Key": "Str000"
				},
				{
					"Source":
					{
						"Text": "Str001Tag"
					},
					"Translation":
					{
						"Text": "りんご"
					},
					"Key": "Str001"
				},
				{
					"Source":
					{
						"Text": "Str002Tag"
					},
					"Translation":
					{
						"Text": "いち、に、さん"
					},
					"Key": "Str002"
				},
				{
					"Source":
					{
						"Text": "Str003Tag"
					},
					"Translation":
					{
						"Text": "2020年12月31日"
					},
					"Key": "Str003"
				}
			]
		}
	]
}
```

## 翻訳作業２（.poファイルからのインポート）
各言語フォルダ`（Localization/Game/ja など）` に .poファイルを置いてテキストをインポートすることでも翻訳データを取り込むことができます。

■デフォルト言語(en-AU)のpoファイル

```text:en-AU/Game.po
# Game English (Australia) translation.
# Copyright Epic Games, Inc. All Rights Reserved.
# 
msgid ""
msgstr ""
"Project-Id-Version: Game\n"
"POT-Creation-Date: 2020-12-17 05:59\n"
"PO-Revision-Date: 2020-12-17 05:59\n"
"Language-Team: \n"
"Language: en-AU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. Key:	Str000
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str000"
msgid "Str000Tag"
msgstr "Str000Tag"

#. Key:	Str001
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str001"
msgid "Str001Tag"
msgstr "Str001Tag"

#. Key:	Str002
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str002"
msgid "Str002Tag"
msgstr "Str002Tag"

#. Key:	Str003
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str003"
msgid "Str003Tag"
msgstr "Str003Tag"
```

■翻訳言語(ja)のpoファイル

```text:ja/Game.po
# Game Japanese translation.
# Copyright Epic Games, Inc. All Rights Reserved.
# 
msgid ""
msgstr ""
"Project-Id-Version: Game\n"
"POT-Creation-Date: 2020-12-17 05:59\n"
"PO-Revision-Date: 2020-12-17 05:59\n"
"Language-Team: \n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. Key:	Str000
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str000"
msgid "Str000Tag"
msgstr "こんにちは"

#. Key:	Str001
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str001"
msgid "Str001Tag"
msgstr "りんご"

#. Key:	Str002
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str002"
msgid "Str002Tag"
msgstr "いち、に、さん"

#. Key:	Str003
#. SourceLocation:	/Game/ST_MessageInfo.ST_MessageInfo
#: /Game/ST_MessageInfo.ST_MessageInfo
msgctxt "ST_MessageInfo,Str003"
msgid "Str003Tag"
msgstr "2020年12月31日"
```

このpoファイルの作成は`Poedit`というツールなどで作成することができます。（テキストなので自前で作成も可能です。）


poファイルの取り込みはローカライゼーションダッシュボードにて、[カルチャ] -> [テキストインポート] から行うことができます。（個別の言語で行う場合は右の小さいボタンで）
![TextImport.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/10570b02-9f93-6718-eba5-b7923100523e.jpeg)



## テキストをコンパイル
ローカライゼーションダッシュボードにて、[カルチャ] -> [テキストをコンパイル] で最終データ(.locres)を出力できます。


# 翻訳テキストの出力
## カルチャの確認
以下のBPで現在のカルチャとネイティブカルチャを確認することができます。
![GetCultureBP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/087103aa-fbba-eee9-c84b-f002a5e553bf.jpeg)

実行結果は以下の様になります。
![GetCultureResult.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/133bd336-4dcf-1fc4-5bf2-a76785e7165a.jpeg)

C++では以下のようになります。

```cpp:カルチャの確認
#include "Kismet/KismetInternationalizationLibrary.h"

auto _Culture = UKismetInternationalizationLibrary::GetCurrentCulture();
auto _Native = UKismetInternationalizationLibrary::GetNativeCulture(ELocalizedTextSourceCategory::Game);
```

### 関連コード
"Engine\Source\Runtime\Engine\Classes\Kismet\KismetInternationalizationLibrary.h"


エディタの言語は
[編集] -> [エディタの環境設定] -> [一般-地域＆言語] -> [国際化] -> [ゲームのプレビュー言語] で切り替えることができます。
![EditorSetting.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5d3db8d4-7c03-5986-ba03-1a31f65caba0.jpeg)


## BPでの翻訳テキストの表示
以下BPで翻訳テキストの出力ができます。
![PrintTextBP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/e92a6114-1314-69d8-b8c1-2acd58b09e07.jpeg)

実行結果は以下の様になります。
![PrintTextResult.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/6bb0865e-19c2-c6d2-57e1-f8a756184733.jpeg)


## c++での翻訳テキストの表示
マクロ `LOCTEXT`で、キーとソース文字列を指定します。ネームスペースを含む場合は`NSLOCTEXT`を使います。
以下サンプルコード

```cpp:.cpp
#define LOCTEXT_NAMESPACE "ST_MessageInfo"

void Sample()
{
	FText _Text0 = LOCTEXT("Str000", "Str000Tag");
	UE_LOG(LogTemp, Log, TEXT("%s"), *_Text0.ToString() );
			
	FText _Text1 = LOCTEXT("Str001", "Str001Tag");
	UE_LOG(LogTemp, Log, TEXT("%s"), *_Text1.ToString() );
			
	FText _Text2 = LOCTEXT("Str002", "Str002Tag");
	UE_LOG(LogTemp, Log, TEXT("%s"), *_Text2.ToString() );
			
	FText _Text3 = LOCTEXT("Str003", "Str003Tag");
	UE_LOG(LogTemp, Log, TEXT("%s"), *_Text3.ToString() );
}

#undef LOCTEXT_NAMESPACE
```

実行結果は以下の様になります。

```text:アウトプットログ
LogTemp: こんにちは
LogTemp: りんご
LogTemp: いち、に、さん
LogTemp: 2020年12月31日
```


# パッケージ化設定
パッケージに翻訳データを含む場合は以下の設定に対応言語を設定する必要があります。
[プロジェクト設定] -> [プロジェクト - パッケージ化] -> [パッケージ化するローカライゼーション]　

![PackageSetting.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/cfc49aed-646d-744e-77f1-f708bc21185c.jpeg)


# その他
## ローカライゼーションダッシュボード用コマンドレットについて 
ローカライゼーションダッシュボード用のコマンドレットで、`GatherTextCommandlet` が用意されています。
"Engine\Source\Editor\UnrealEd\Classes\Commandlets\GatherTextCommandlet.h"

これを使用することでGUIエディタを立ち上げずにコマンドラインで[テキストコンパイル]等ができます。ローカライズ用iniファイルを指定する必要があります。
以下コマンド例：

```terminal:コマンド実行例
# テキストを収集
>{エンジンパス}\Binaries\Win64\UE4Editor-Cmd.exe {プロジェクトパス}\MyProject.uproject -run=GatherText -config=Config\Localization\Game_Gather.ini

# テキストをインポート
>{エンジンパス}\Binaries\Win64\UE4Editor-Cmd.exe {プロジェクトパス}\MyProject.uproject -run=GatherText -config=Config\Localization\Game_Import.ini

# テキストをコンパイル
>{エンジンパス}\Binaries\Win64\UE4Editor-Cmd.exe {プロジェクトパス}\MyProject.uproject -run=GatherText -config=Config\Localization\Game_Compile.ini
```

その他、テキスト収集用のコマンドレットが対象別で用意されているようです。`GatherTextFromAssetsCommandlet`
`GatherTextFromMetadataCommandlet`
`GatherTextFromSourceCommandlet`


▼ストリングテーブルをコマンドレットでインポート/エクスポートするには以下を参考に。

https://qiita.com/unknown_ds/items/9c6d43e58dcb3986af7a



# まとめ
バージョン4.25.4の時点では実験段階扱いになっていますが、マニュアルでは
「このツールはまだ実験段階のものですが、安定して機能しており、社内のすべてのプロジェクトで使用されています。」
となっています。
翻訳エディタなどはまだ扱いにくい感じですので早く正式リリースになってもらいたいです。