#include "LocalizeTextData.h"
#include "Internationalization/StringTableRegistry.h"
#include "Internationalization/TextLocalizationManager.h"
#include "Internationalization/PolyglotTextData.h"
#if WITH_EDITOR
#include "Misc/SecureHash.h"
#include "Serialization/Csv/CsvParser.h"
#include "EditorDirectories.h"
#include "IDesktopPlatform.h"
#include "DesktopPlatformModule.h"
#endif
DEFINE_LOG_CATEGORY_STATIC(LogLocalize, Log, All);
struct FLocalizeTextDataFormat
{
static const FString Version;
};
const FString FLocalizeTextDataFormat::Version = FString(TEXT("0.1"));
FPrimaryAssetId ULocalizeTextData::GetPrimaryAssetId() const
{
return FPrimaryAssetId(GetClass()->GetFName(), GetOutermost()->GetFName());
}
#if WITH_EDITOR
void ULocalizeTextData::Reimport()
{
// ファイル名が未指定であればダイアログを表示しロードする
if(CsvPath.IsEmpty())
{
ImportCsv();
}
else
{
LoadLocalizeDataByCSVFile(CsvPath);
RegistLocalize();
}
}
void ULocalizeTextData::ImportCsv()
{
// デスクトッププラットフォームモジュールの取得
IDesktopPlatform* const DesktopPlatform = FDesktopPlatformModule::Get();
// 最後に使用したインポートディレクトリのパスを取得
const FString DefaultPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_IMPORT);
// 選択されたファイルパスを格納する配列
TArray OutFiles;
// ファイル選択ダイアログを表示
if(DesktopPlatform->OpenFileDialog(
nullptr, // 親ウィンドウハンドル
TEXT("Choose a Localize Text Data CSV file..."), // ダイアログのタイトル
DefaultPath, // 初期ディレクトリパス
TEXT(""), // デフォルトのファイル名
TEXT("String Table CSV (*.csv)|*.csv"), // ファイルフィルター
EFileDialogFlags::None, // ダイアログフラグ
OutFiles // 選択されたファイルパスの出力
))
{
// 選択されたCSVファイルからローカライズデータを読み込む
LoadLocalizeDataByCSVFile(OutFiles[0]);
RegistLocalize();
}
}
#endif
void ULocalizeTextData::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
// ファイルパスが空であれば、データがないものとする
if (CsvPath.IsEmpty())
{
return;
}
if (DiffData())
{
LoadLocalizeDataByCSVFile(CsvPath);
}
#endif
RegistLocalize();
}
void ULocalizeTextData::BeginDestroy()
{
// テーブル名で別途解放するのでここでは解放しない
Super::BeginDestroy();
}
#if WITH_EDITOR
void ULocalizeTextData::LoadLocalizeDataByCSVFile(const FString LoadCsvPath)
{
// ファイル名が空ではないかチェック
if (LoadCsvPath.IsEmpty())
{
UE_LOG(LogLocalize, Error, TEXT("Failed to load: CSV path is empty"));
return;
}
// CSVファイルをロード
FString CsvString;
if (!FFileHelper::LoadFileToString(CsvString, *LoadCsvPath))
{
UE_LOG(LogLocalize, Error, TEXT("Failed to load file: %s"), *LoadCsvPath);
return;
}
// CSVをパース
const FCsvParser CsvTable = FCsvParser(CsvString);
const int32 RowNum = CsvTable.GetRows().Num();
// 2行以上ないとデータ無しとみなす。
if (RowNum < 2)
{
UE_LOG(LogLocalize, Error, TEXT("Failed to load: CSV must have at least 2 rows (current: %d)"), RowNum);
return;
}
// 列の項目
const TArray& ColumnHeader = CsvTable.GetRows()[0];
const int32 ColumnNum = ColumnHeader.Num();
// TableId(Namespace)はアセット名を使用
const FString TableId = GetName();
// 2列以上ないとデータ無しとみなす。(Keyと文字列)
if (ColumnNum < 2)
{
UE_LOG(LogLocalize, Error, TEXT("Failed to load: CSV must have at least 2 columns (current: %d)"), ColumnNum);
return;
}
// リインポート用 ファイルパスの保持
CsvPath = LoadCsvPath;
// ハッシュ値の保存(ファイル変更チェック用)
FMD5Hash CsvFileHash = FMD5Hash::HashFile(*CsvPath);
FileHash = LexToString(CsvFileHash);
// ファイルフォーマットバージョン設定
Version = FLocalizeTextDataFormat::Version;
// ローカライズデータの登録用の配列を初期化
Languages.Empty();
LocalizeTexts.Empty();
// 言語のヘッダーを取得 0番目はKeyなので1番目から取得
for (int32 ColumnIndex = 1; ColumnIndex < ColumnNum; ++ColumnIndex)
{
Languages.Add(FString(ColumnHeader[ColumnIndex]));
}
// テキストの取得
for (int32 RowIndex = 1; RowIndex < RowNum; ++RowIndex)
{
// 行情報
const TArray& Row = CsvTable.GetRows()[RowIndex];
// 列の数が異なる場合は、その行は不正なデータとしてスキップ
if (Row.Num() != ColumnNum)
{
continue;
}
FLocalizeText LocalizeText;
// 0列目はKeyとして扱う
LocalizeText.Key = Row[0];
// 各列の文字を取り出す
for (int32 ColumnIndex = 1; ColumnIndex < ColumnNum; ++ColumnIndex)
{
const TCHAR* CellText = Row[ColumnIndex];
LocalizeText.Texts.Add(FString(CellText));
}
LocalizeTexts.Add(LocalizeText);
}
// 変更を通知
Modify(true);
}
bool ULocalizeTextData::DiffData()
{
if (Version != FLocalizeTextDataFormat::Version)
{
return true;
}
// ハッシュ値を求め比較する
FMD5Hash CsvFileHash = FMD5Hash::HashFile(*CsvPath);
FString HashString = LexToString(CsvFileHash);
if (HashString != FileHash)
{
return true;
}
return false;
}
#endif
void ULocalizeTextData::RegistLocalize()
{
// データの有無確認
if(LocalizeTexts.Num() == 0)
{
UE_LOG(LogLocalize, Warning, TEXT("No localization text data to register"));
return;
}
const FString TableNamespace = GetName();
// StringTableがなければ作成する
if (!FStringTableRegistry::Get().FindStringTable(*TableNamespace))
{
FStringTableRegistry::Get().Internal_NewLocTable(*TableNamespace, TableNamespace);
}
// 言語数
const int32 LanguageNum = Languages.Num();
// Native言語はエンジンの設定を使用
const FString NativeLanguage = FTextLocalizationManager::Get().GetNativeCultureName(Category);
int32 NativeLanguageIndex = Languages.Find(NativeLanguage);
if(NativeLanguageIndex == INDEX_NONE)
{
// Native言語が見つからない場合は、最初の言語をNativeとして設定
NativeLanguageIndex = 0;
}
// ローカライズデータの登録用配列
TArray RegistLocalizeData;
for(FLocalizeText& LocalizeText : LocalizeTexts)
{
// ローカライズデータの登録
FPolyglotTextData LocText = FPolyglotTextData(Category, TableNamespace, LocalizeText.Key, LocalizeText.Texts[NativeLanguageIndex]);
for (int32 TextIndex = 0; TextIndex < LanguageNum; ++TextIndex)
{
const FString& Text = LocalizeText.Texts[TextIndex];
const FString& Language = Languages[TextIndex];
// Native言語はNativeCultureに設定
if (TextIndex == NativeLanguageIndex)
{
// NativeCultureに設定する場合は、StringTableに登録
FStringTableRegistry::Get().Internal_SetLocTableEntry(*TableNamespace, LocalizeText.Key, Text);
LocText.SetNativeCulture(Language);
LocText.SetNativeString(Text);
}
else
{
LocText.AddLocalizedString(Language, Text);
}
}
RegistLocalizeData.Add(LocText);
}
// ローカライズマネージャに登録
FTextLocalizationManager::Get().RegisterPolyglotTextData(RegistLocalizeData);
}