UE4 StringTableについてのメモ（Commandletでのインポートエクスポート）

# 概要
UnrealEngineでのStringTableについてのメモ書きと、コマンドレットを使ってcsvファイルからのインポートとcsvファイルへのエクスポートコード例です。


## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.25


## 参考
以下を参考にさせて頂きました、ありがとうございます。

[UE4公式 : UCommandlet](https://docs.unrealengine.com/en-US/API/Runtime/Engine/Commandlets/UCommandlet/index.html)
[UE4公式 : Commandlets](https://docs.unrealengine.com/en-US/API/Editor/UnrealEd/Commandlets/index.html)
[UE4公式 : 文字列テーブル](https://docs.unrealengine.com/ja/ProductionPipelines/Localization/StringTables/index.html)
[[UE4] String Tableの紹介](https://historia.co.jp/archives/9205/)

## 関連ソース
"Engine\Source\Runtime\Engine\Classes\Commandlets\Commandlet.h"


# ストリングテーブルについて
ローカライズ用アセットとして使うことができる文字列テーブルです。`Key`と`SourceString`が設定でき、`Key`に紐づいた`SourceString`を表示することによってアセット切り替え(言語切り替え)を行っても表示側の処理を変えずに表示文字列を切り替えることができます。

![StringTable.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/7f2152d3-91b4-a545-f6e4-27f2535a2aab.jpeg)

データの追加はストリングテーブルエディタの下部にて追加もできますが、CSVファイルでのインポートなどもできます。


## BPノード例
### GetStringTableEntrySourceString
`GetStringTableEntrySourceString`はストリングテーブルに登録されている文字列を取得できます。`Table Id`にストリングテーブルのアセットのパス `Key`にキーを指定します。

![GetStringTableEntrySourceString.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/1acea5ec-332a-7508-9344-cf4fe45be720.jpeg)


### IsStringTableEntryRegistered
`IsStringTableEntryRegistered`はストリングテーブルに指定のキーが登録されているかを調べるノードです。`Table Id`にストリングテーブルのアセットのパス `Key`にキーを指定します。

![IsStringTableEntryRegistered.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/8faa8587-4f25-ddb3-74aa-6b4e735f66b0.jpeg)


## C++コード例
上記BPをC++で書くと以下の様になります。
ストリングテーブルアセットは別途どこかでロードする必要があります。

```cpp:コードサンプル
#include "Kismet/KismetStringTableLibrary.h"

FString _String = UKismetStringTableLibrary::GetTableEntrySourceString(FName("/Game/StringTable/ST_Test.ST_Test"), FString(TEXT("StringKey01")) );
			
bool _IsRegist = UKismetStringTableLibrary::IsRegisteredTableEntry(FName("/Game/StringTable/ST_Test.ST_Test"), FString(TEXT("StringKey99")) );
```

### 関連エンジンソース
"Engine\Source\Runtime\Engine\Classes\Kismet\KismetStringTableLibrary.h"



# コマンドレットでの操作
コマンドレットでインポート/エクスポートが可能です。

## インポート
`Test.csv` というCSVファイルから ストリングテーブル `ST_Test` へインポートする処理です。

```cpp:インポート処理
// アセットデータ
FAssetData _AssetData  = AssetRegistry.GetAssetByObjectPath(TEXT("/Game/StringTable/ST_Test.ST_Test"));
// ストリングテーブル
UStringTable* _StringTable = Cast<UStringTable>(_AssetData.GetAsset());
	
// インポート元のcsv
FString _OutFiles = FString(TEXT("../../../app/Content/Localization/Test.csv"));
		
_StringTable->Modify();
bool _Ret = _StringTable->GetMutableStringTable()->ImportStrings(_OutFiles);
```


## エクスポート
ストリングテーブル`ST_Test`からCSVファイル`Test.csv`へエクスポートする処理です。

```cpp:エクスポート処理
// アセットデータ
FAssetData _AssetData  = AssetRegistry.GetAssetByObjectPath(TEXT("/Game/StringTable/ST_Test.ST_Test"));
// ストリングテーブル
UStringTable* _StringTable = Cast<UStringTable>(_AssetData.GetAsset());

// エクスポート先のcsv
FString _OutFiles = FString(TEXT("../../../app/Content/Localization/Test.csv"));
		
_StringTable->GetStringTable()->ExportStrings(_OutFiles);
```

## クラス例
コマンドレットクラスは以下の様になります。
コード例では入出力ファイルが直値になっているので実際に使用する際はコマンドラインのオプション等で指定できると良いかと思います。（変換が面倒ですが:sweat_smile:）

```cpp:StringTableProcCommandlet.h
#pragma once

#include "CoreMinimal.h"
#include "Commandlets/Commandlet.h"
#include "StringTableProcCommandlet.generated.h"

class UStringTable;

// ストリングテーブル処理コマンドレットクラス
UCLASS()
class UStringTableProcCommandlet : public UCommandlet
{
	GENERATED_BODY()
	

public:
	UStringTableProcCommandlet(const FObjectInitializer& ObjectInitializer);

	virtual int32 Main(const FString& CmdLineParams) override;

};
```

```cpp:StringTableProcCommandlet.cpp
#include "StringTableProcCommandlet.h"
#include "AssetRegistry/Public/AssetData.h"				
#include "AssetRegistry/Public/AssetRegistryModule.h"
#include "AssetRegistry/Public/AssetRegistryHelpers.h"
#include "CoreUObject/Public/UObject/ConstructorHelpers.h"
#include "Internationalization/StringTable.h"
#include "Internationalization/StringTableCore.h"
#include "Internationalization/StringTableCoreFwd.h"

DEFINE_LOG_CATEGORY_STATIC(LogTextSTProcCommandlet, Log, All);

UStringTableProcCommandlet::UStringTableProcCommandlet(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	LogToConsole = true;
}

int32 UStringTableProcCommandlet::Main(const FString& CmdLineParams)
{
	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
	IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
	
	TArray<UPackage*>	_PackagesToSave;
	bool				_IsImport = false;
	bool				_IsExport = false;
	
	// CSVファイルパス
	const TArray<const TCHAR*> _ImportCsvFiles = {
		TEXT("../../../app/Content/Localization/ST_Test1.csv"),
	};
	const TArray<const TCHAR*> _ExportCsvFiles = {
		TEXT("../../../app/Content/Localization/ST_Test2.csv"),
	};

	// アセットデータ
	FAssetData _AssetData  = AssetRegistry.GetAssetByObjectPath(TEXT("/Game/StringTable/ST_Test.ST_Test"));
	
	// ストリングテーブル
	UStringTable* _StringTable = Cast<UStringTable>(_AssetData.GetAsset());
	// アセットデータチェック
	if (!_AssetData.IsValid() || _StringTable == nullptr) {
		UE_LOG(LogTextSTProcCommandlet, Display, TEXT("File Not Found."));
		return(1);
	}


	TArray<FString> Tokens;
	TArray<FString> Switches;
	ParseCommandLine(*CmdLineParams, Tokens, Switches);

	// オプションチェック
	for(const auto& _It : Switches){
		if( _It.Equals(TEXT("import"), ESearchCase::CaseSensitive)){
			_IsImport = true;
		}else
		if( _It.Equals(TEXT("export"), ESearchCase::CaseSensitive)){
			_IsExport = true;
		}
	}
	if( !_IsImport && !_IsExport){
		UE_LOG(LogTextSTProcCommandlet, Display, TEXT("NotFound SwitchOption(-Import or -Export)"));
		return(0);
	}


	//---------------------
	// インポート処理
	if(_IsImport){
		_StringTable->Modify();
		bool _Ret = _StringTable->GetMutableStringTable()->ImportStrings(_ImportCsvFiles[0]);
		
		_PackagesToSave.Add(_AssetData.GetPackage());
	}

	// アセットセーブ
	for (const auto& _It : _PackagesToSave) {
		// 保存ファイル名
		const auto _FileName = FString::Printf(TEXT("../../../app/Content/StringTable/ST_Test.uasset"));

		// パッケージ保存
		UPackage::SavePackage(_It, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *_FileName);
	}
	
	
	//---------------------
	// エクスポート処理
	if(_IsExport){
		_StringTable->GetStringTable()->ExportStrings(_ExportCsvFiles[0]);
	}
	

	return(0);

}
```


実行コマンドは以下の様になります(引数で分岐しているだけですのでエクスポートもほぼ一緒です)。
オプションでファイル名等を指定できるようにすればより扱いやすくなるかと思います。

```terminal:コマンド例
# インポート
> {エンジンパス}\Binaries\Win64\UE4Editor-Cmd.exe {プロジェクトパス}\MyProject.uproject -run=StringTableProc -import

```


# まとめ
ローカライズ文字列をストリングテーブルを使って管理する際に、コマンドレットでのインポートを使うと捗ると思います。特にローカライゼーションダッシュボードを使ったローカライズ処理ではストリングテーブル使用が必須の様です。