✨
UnityのLocalization Tablesの変更を保存したときに自動でCSVをexportするEditor拡張の実装
はじめに
OSSのデスクトップマスコット「uDesktopMascot」を開発しています。
こちらのプロジェクトでは、多言語対応のためCSVを使って自動翻訳を行っています。しかし、テーブルに追加するごとに以下のボタンからエクスポートするのは大変です。そのため、今回は保存したときに自動的にCSVにエクスポートするEditor拡張を作りました。
デモ
保存をすることで、以下のようにCSVノエクスポート完了のログが出ます。
また以下のようにCSVがexportされます
開発環境
- Unity 6000.0.31f1(IL2CPP)
- Localization 1.5.4
Editor拡張
コードは以下になります。こちらを Editor
フォルダ内にいれることで、Localization Tablesを保存するごとにCSVに出力されます。
using UnityEditor;
using UnityEngine;
using UnityEngine.Localization.Tables;
using UnityEditor.Localization;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
namespace uDesktopMascot.Editor
{
/// <summary>
/// ローカリゼーションテーブルをセーブ時に自動でCSVにエクスポートする処理
/// </summary>
public class LocalizationTableSaveProcessor : AssetModificationProcessor
{
/// <summary>
/// アセットが保存される直前に呼び出されるコールバック。
/// 保存されるアセットがローカリゼーションテーブルである場合、CSVエクスポートを行います。
/// </summary>
/// <param name="paths">保存されるアセットのパスの配列</param>
/// <returns>保存されるアセットのパスの配列</returns>
private static string[] OnWillSaveAssets(string[] paths)
{
// 保存されるアセットパスをループ
foreach (var path in paths)
{
// アセットがローカリゼーションテーブルかどうかを確認
var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
if (asset is StringTable stringTable)
{
// CSVエクスポートの処理を実行
ExportStringTableToCSV(stringTable);
}
}
return paths;
}
/// <summary>
/// 指定されたStringTableを含むテーブルコレクションをCSVにエクスポートします。
/// </summary>
/// <param name="stringTable">エクスポートするStringTable</param>
private static void ExportStringTableToCSV(StringTable stringTable)
{
// テーブルコレクションを取得
var tableCollection = LocalizationEditorSettings.GetCollectionFromTable(stringTable) as StringTableCollection;
if (tableCollection == null)
{
Debug.LogError($"テーブルコレクションが見つかりませんでした:{stringTable.TableCollectionName}");
return;
}
// エクスポート先のパスを設定
var exportPath = "Assets/uDesktopMascot/LocalizationTable/LocalizationTable.csv";
var exportDirectory = Path.GetDirectoryName(exportPath);
if (!Directory.Exists(exportDirectory))
{
Directory.CreateDirectory(exportDirectory);
}
// CSVデータを生成
var csvData = GenerateCSVData(tableCollection);
// CSVファイルを書き出し(既に存在する場合は上書き)
File.WriteAllText(exportPath, csvData, Encoding.UTF8);
Debug.Log($"CSVエクスポートが完了しました:{exportPath}");
}
/// <summary>
/// テーブルコレクションのデータからCSV形式の文字列を生成します。
/// </summary>
/// <param name="tableCollection">CSVデータを生成するStringTableCollection</param>
/// <returns>生成されたCSV形式の文字列</returns>
private static string GenerateCSVData(StringTableCollection tableCollection)
{
var sb = new StringBuilder();
// ヘッダーを追加(ダブルクォーテーションなし)
sb.Append("Key,Id");
// 言語カラムの順序を元のCSVと同じにする
var localeCodes = new List<string>();
foreach (var localeCode in new[] { "en", "fr", "it", "ja", "ko" })
{
var table = tableCollection.GetTable(localeCode);
if (table != null)
{
localeCodes.Add(localeCode);
var languageName = GetLanguageName(localeCode);
sb.Append($",{languageName}({localeCode})");
}
}
sb.AppendLine();
// エントリを取得
var sharedData = tableCollection.SharedData;
foreach (var sharedEntry in sharedData.Entries)
{
// Keyフィールドをダブルクォーテーションで囲む
var key = EscapeCSVField(sharedEntry.Key);
// Idフィールドはそのまま(ダブルクォーテーションなし)
var id = sharedEntry.Id.ToString();
sb.Append($"{key},{id}");
foreach (var localeCode in localeCodes)
{
var table = tableCollection.GetTable(localeCode) as StringTable;
if (table != null)
{
var entry = table.GetEntry(sharedEntry.Id);
if (entry != null)
{
// ローカライズされた値をダブルクォーテーションで囲む
var localizedValue = EscapeCSVField(entry.LocalizedValue);
sb.Append($",{localizedValue}");
}
else
{
// 空の値でもダブルクォーテーションを付ける
sb.Append($",\"\"");
}
}
else
{
// テーブルが存在しない場合もダブルクォーテーション付きの空文字を追加
sb.Append($",\"\"");
}
}
sb.AppendLine();
}
return sb.ToString();
}
/// <summary>
/// CSVフィールドをダブルクォーテーションで囲み、内部のダブルクォーテーションをエスケープします。
/// </summary>
/// <param name="field">エスケープするフィールドの文字列</param>
/// <returns>ダブルクォーテーションで囲まれた、エスケープ処理された文字列</returns>
private static string EscapeCSVField(string field)
{
if (string.IsNullOrEmpty(field))
{
return "\"\"";
}
// ダブルクォーテーションをエスケープ
if (field.Contains("\""))
{
field = field.Replace("\"", "\"\"");
}
// フィールドをダブルクォーテーションで囲む
return $"\"{field}\"";
}
/// <summary>
/// ロケールコードから言語名を取得します。
/// </summary>
/// <param name="localeCode">ロケールコード(例:"en")</param>
/// <returns>言語名(例:"English")</returns>
private static string GetLanguageName(string localeCode)
{
try
{
var culture = new CultureInfo(localeCode);
return culture.EnglishName;
}
catch
{
// ロケールコードが認識されない場合はそのまま返す
return localeCode;
}
}
}
}
関連記事
こちらは多言語に対応したプロジェクトにしていくために、unityのLocalizationを使って多言語対応を行っています。
以下の記事ではシンプルなTMPのローカル対応の方法を記載しています。
以下の記事で LocalizationデータをGitHub Actions + openaiのapiを使って自動翻訳をするフローを記事にしています。合わせてローカライズ対応のワークフローとしてご覧ください

midra-lab.notion.site/MidraLab-dd08b86fba4e4041a14e09a1d36f36ae 個人が興味を持ったこと × チームで面白いものや興味を持ったものを試していくコミュニティ
Discussion