くーこのプログラマメモ

UE4などゲーム開発に関するメモ

ローカライゼーションのCommandlet紹介

使用バージョン:UE4.24.3

はじめに

UE4のローカライゼーションダッシュボードを利用してローカライズ対応を行う場合があるかと思います。
通常はテキストの追加や編集を行った際にローカライゼーションダッシュボードからテキスト収集やpoファイルのインポート、コンパイルを行うかと思いますが、これらの操作についてはCommandletが用意されているため、まとめて実行することで操作を簡略化することができます。

ローカライゼーションについては下記の資料が大変参考になります。

www.unrealengine.com

下記のコード中に出てくるExecuteCommandletの実装については以下を参照ください。

shama-coo.hatenablog.com

準備

以下のモジュールを依存関係として追加する必要があります。

  • Localization
  • UnrealEd

GatherTextCommandlet

GatherText というCommandletがあり、このCommandletから各操作を行います。
実行する際に引数として、{ProjectDir}/config/Localization以下に生成されるゲームターゲットのiniファイルを指定します。
この時に指定するiniファイル内の設定を元に内部で適したCommandletが実行されます。

iniファイル 動作
[ゲームターゲット]_Gather.ini テキスト収集
[ゲームターゲット]_Import.ini poファイルインポート
[ゲームターゲット]_Export.ini poファイルエクスポート
[ゲームターゲット]_GenerateReports.ini WordCountやStatus等のレポート生成
[ゲームターゲット]_Compile.ini コンパイル

iniファイルパスの取得

LocalizationConfigurationScript に各iniファイルを相対パスで取得する関数があります。
しかし、取得したパスをそのままCommandletの引数へ渡すと、相対パスだった場合はプロジェクトもしくはエンジンのパスが付与されてしまうため、絶対パスに変換してから渡します。

テキスト収集

#include "Misc/Paths.h"
#include "LocalizationModule.h"
#include "LocalizationTargetTypes.h"
#include "LocalizationConfigurationScript.h"
#include "Commandlets/GatherTextCommandletBase.h"

void GatherText(const FString& GameTargetName)
{
    // ゲームターゲット名からLocalizationTargetを取得.
    ILocalizationModule& LocalizationModule = ILocalizationModule::Get();
    ULocalizationTarget* LocalizationTarget = LocalizationModule.GetLocalizationTargetByName(GameTargetName, false);
    check(::IsValid(LocalizationTarget));
    // [ゲームターゲット]_GatherText.iniの相対パスを取得.
    FString ConfigPath = LocalizationConfigurationScript::GetGatherTextConfigPath(LocalizationTarget);
    // 相対パス->絶対パス.
    ConfigPath = FPaths::ConvertRelativePathToFull(ConfigPath);

    // Commandlet実行.
    ExecuteCommandlet(TEXT("GatherTextCommandlet"), FString::Format(TEXT("-config=\"{0}\""), { ConfigPath }));
}

poファイルインポート

#include "Misc/Paths.h"
#include "LocalizationModule.h"
#include "LocalizationTargetTypes.h"
#include "LocalizationConfigurationScript.h"
#include "Commandlets/GatherTextCommandletBase.h"

void ImportPO(const FString& GameTargetName, const TOptional<FString> CultureName)
{
    // ゲームターゲット名からLocalizationTargetを取得.
    ILocalizationModule& LocalizationModule = ILocalizationModule::Get();
    ULocalizationTarget* LocalizationTarget = LocalizationModule.GetLocalizationTargetByName(GameTargetName, false);
    check(::IsValid(LocalizationTarget));
    // [ゲームターゲット]_Import.iniの相対パスを取得.
    FString ConfigPath = LocalizationConfigurationScript::GetImportTextConfigPath(LocalizationTarget, CultureName);
    // 相対パス->絶対パス.
    ConfigPath = FPaths::ConvertRelativePathToFull(ConfigPath);

    // Commandlet実行.
    ExecuteCommandlet(TEXT("GatherTextCommandlet"), FString::Format(TEXT("-config=\"{0}\""), { ConfigPath }));
}

poファイルエクスポート

#include "Misc/Paths.h"
#include "LocalizationModule.h"
#include "LocalizationTargetTypes.h"
#include "LocalizationConfigurationScript.h"
#include "Commandlets/GatherTextCommandletBase.h"

void ExportPO(const FString& GameTargetName, const TOptional<FString> CultureName)
{
    // ゲームターゲット名からLocalizationTargetを取得.
    ILocalizationModule& LocalizationModule = ILocalizationModule::Get();
    ULocalizationTarget* LocalizationTarget = LocalizationModule.GetLocalizationTargetByName(GameTargetName, false);
    check(::IsValid(LocalizationTarget));
    // [ゲームターゲット]_Export.iniの相対パスを取得.
    FString ConfigPath = LocalizationConfigurationScript::GetExportTextConfigPath(LocalizationTarget, CultureName);
    // 相対パス->絶対パス.
    ConfigPath = FPaths::ConvertRelativePathToFull(ConfigPath);

    // Commandlet実行.
    ExecuteCommandlet(TEXT("GatherTextCommandlet"), FString::Format(TEXT("-config=\"{0}\""), { ConfigPath }));
}

コンパイル

#include "Misc/Paths.h"
#include "LocalizationModule.h"
#include "LocalizationTargetTypes.h"
#include "LocalizationConfigurationScript.h"
#include "Commandlets/GatherTextCommandletBase.h"

void Compile(const FString& GameTargetName, const TOptional<FString> CultureName)
{
    // ゲームターゲット名からLocalizationTargetを取得.
    ILocalizationModule& LocalizationModule = ILocalizationModule::Get();
    ULocalizationTarget* LocalizationTarget = LocalizationModule.GetLocalizationTargetByName(GameTargetName, false);
    check(::IsValid(LocalizationTarget));
    // [ゲームターゲット]_Compile.iniの相対パスを取得.
    FString ConfigPath = LocalizationConfigurationScript::GetCompileTextConfigPath(LocalizationTarget, CultureName);
    // 相対パス->絶対パス.
    ConfigPath = FPaths::ConvertRelativePathToFull(ConfigPath);

    // Commandlet実行.
    ExecuteCommandlet(TEXT("GatherTextCommandlet"), FString::Format(TEXT("-config=\"{0}\""), { ConfigPath }));
}

翻訳済みテキスト数を更新してWord Countに反映

#include "Misc/Paths.h"
#include "LocalizationModule.h"
#include "LocalizationTargetTypes.h"
#include "LocalizationConfigurationScript.h"
#include "Commandlets/GatherTextCommandletBase.h"

void UpdateWordCount(const FString& GameTargetName)
{
    // ゲームターゲット名からLocalizationTargetを取得.
    ILocalizationModule& LocalizationModule = ILocalizationModule::Get();
    ULocalizationTarget* LocalizationTarget = LocalizationModule.GetLocalizationTargetByName(GameTargetName, false);
    check(::IsValid(LocalizationTarget));
    // [ゲームターゲット]_GenerateReports.iniの相対パスを取得.
    FString ConfigPath = LocalizationConfigurationScript::GetWordCountReportConfigPath(LocalizationTarget);
    // 相対パス->絶対パス.
    ConfigPath = FPaths::ConvertRelativePathToFull(ConfigPath);

    // Commandlet実行.
    ExecuteCommandlet(TEXT("GatherTextCommandlet"), FString::Format(TEXT("-config=\"{0}\""), { ConfigPath }));

    // 生成したレポートを元に更新.
    LocalizationTarget->UpdateStatusFromConflictReport();
    LocalizationTarget->UpdateWordCountsFromCSV();
}

実行例

上記の処理をまとめて呼び出す例です。
※テキスト収集の前に収集対象のStringTable等を更新すると自動化が捗ります。

void Example(const FString& GameTargetName)
{
    // テキスト収集.
    GatherText(GameTargetName);
    // POファイルをインポート.
    ImportPO(GameTargetName);
    // コンパイル.
    Compile(GameTargetName);
    // WordCount更新.
    UpdateWordCount(GameTargetName);
}

おまけ

GatherTextの引数は;区切りで複数のiniファイルを指定することができるようになっているようです。
個別に実行した場合はソースコントロールに関する処理が都度呼ばれるだけのようです。
特に問題がないようなら都合のよいやり方を選択して構わないと思います。

最後に

最初にゲームターゲットの設定をしてしまえば、それ以降の追加や更新の作業については簡略化することが可能となり確認作業がしやすくなります。
また、会社によっては自社でテキスト管理ツールを作成されているところもあるかと思います。
そういった場合にも少しの作業で連携することが可能になるのではないかと思います。