792 lines
25 KiB
C#
792 lines
25 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Text;
|
|
using Newtonsoft.Json;
|
|
using UnityEditor;
|
|
using UnityEditor.Localization;
|
|
using UnityEngine;
|
|
using UnityEngine.Localization;
|
|
using UnityEngine.Localization.Settings;
|
|
using UnityEngine.Localization.Tables;
|
|
using UnityEditor.SceneManagement;
|
|
|
|
public static class UnityLocalizationProjectSync
|
|
{
|
|
private const string SessionKey = "Bansonic.Localization.Sync.Completed";
|
|
private const string RootDirectory = "Assets/Localization";
|
|
private const string SettingsDirectory = RootDirectory + "/Settings";
|
|
private const string LocalesDirectory = RootDirectory + "/Locales";
|
|
private const string TablesDirectory = RootDirectory + "/Tables";
|
|
private const string SettingsAssetPath = SettingsDirectory + "/ProjectLocalizationSettings.asset";
|
|
private const string ZhJsonPath = "Assets/StreamingAssets/localization/zh-CN.json";
|
|
private const string EnJsonPath = "Assets/StreamingAssets/localization/en-US.json";
|
|
|
|
private static readonly string[] LiteralAssetExtensions = { ".prefab", ".unity" };
|
|
private static readonly string[] SkippedPathPrefixes =
|
|
{
|
|
"Assets/Airy UI/_Demo Scenes/",
|
|
"Assets/Cartoon UI/",
|
|
"Assets/Spine Examples/",
|
|
"Assets/TextMesh Pro/Examples & Extras/",
|
|
"Assets/Localization/"
|
|
};
|
|
|
|
private static readonly Regex LocalizedTextRegex =
|
|
new Regex(@"LocalizationService\.(Get|GetFormat)\(\s*""(?<key>[^""]+)""\s*,\s*""(?<fallback>(?:\\.|[^""])*)""", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex LiteralRegex =
|
|
new Regex(@"LocalizationService\.LocalizeLiteral\(\s*""(?<literal>(?:\\.|[^""])*)""\s*\)", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex NoticeLiteralRegex =
|
|
new Regex(@"gNotice\.\w+\.display\(\s*""(?<literal>(?:\\.|[^""])*)""", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex YamlTextRegex =
|
|
new Regex(@"^\s*m_(Text|text):\s*(?<value>.*)$", RegexOptions.Compiled);
|
|
|
|
private static readonly HashSet<string> ScriptableObjectStringExclusionPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"songName"
|
|
};
|
|
|
|
[InitializeOnLoadMethod]
|
|
private static void ScheduleAutoSync()
|
|
{
|
|
EditorApplication.delayCall += AutoSyncIfNeeded;
|
|
}
|
|
|
|
[MenuItem("Tools/Localization/Rebuild Official Localization Assets")]
|
|
public static void RebuildOfficialLocalizationAssets()
|
|
{
|
|
try
|
|
{
|
|
EnsureLocalizationAssets(forceRebuild: true);
|
|
Debug.Log("[Localization] Official localization assets rebuilt successfully.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[Localization] Rebuild failed: {ex}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
[MenuItem("Tools/Localization/Import JSON To Official Tables")]
|
|
public static void ImportJsonToOfficialTables()
|
|
{
|
|
try
|
|
{
|
|
EnsureLocalizationAssets(forceRebuild: true);
|
|
Debug.Log("[Localization] Imported JSON packs into official localization tables.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[Localization] Import JSON failed: {ex}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
[MenuItem("Tools/Localization/Export Official Tables To JSON")]
|
|
public static void ExportOfficialTablesToJson()
|
|
{
|
|
try
|
|
{
|
|
EnsureDirectories();
|
|
|
|
LocalizationSettings settings = EnsureSettingsAsset();
|
|
LocalizationEditorSettings.ActiveLocalizationSettings = settings;
|
|
|
|
Locale zhLocale = EnsureLocale("zh-CN", "Chinese (Simplified)", $"{LocalesDirectory}/zh-CN.asset");
|
|
Locale enLocale = EnsureLocale("en-US", "English (United States)", $"{LocalesDirectory}/en-US.asset");
|
|
|
|
StringTableCollection gameTextCollection = LocalizationEditorSettings.GetStringTableCollection(LocalizationService.GameTextTableCollectionName);
|
|
StringTableCollection literalCollection = LocalizationEditorSettings.GetStringTableCollection(LocalizationService.LiteralTextTableCollectionName);
|
|
|
|
Dictionary<string, string> zhEntries = ExtractEntries(gameTextCollection, zhLocale);
|
|
Dictionary<string, string> enEntries = ExtractEntries(gameTextCollection, enLocale);
|
|
Dictionary<string, string> zhLiterals = ExtractEntries(literalCollection, zhLocale);
|
|
Dictionary<string, string> enLiterals = ExtractEntries(literalCollection, enLocale);
|
|
|
|
SavePack(ZhJsonPath, "zh-CN", zhEntries, zhLiterals);
|
|
SavePack(EnJsonPath, "en-US", enEntries, enLiterals);
|
|
|
|
AssetDatabase.Refresh();
|
|
Debug.Log("[Localization] Exported official localization tables to JSON.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[Localization] Export JSON failed: {ex}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static void AutoSyncIfNeeded()
|
|
{
|
|
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (SessionState.GetBool(SessionKey, false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SessionState.SetBool(SessionKey, true);
|
|
|
|
try
|
|
{
|
|
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (HasCoreLocalizationAssets())
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnsureLocalizationShellAssets();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[Localization] Auto sync failed: {ex}");
|
|
}
|
|
}
|
|
|
|
private static void EnsureLocalizationAssets(bool forceRebuild)
|
|
{
|
|
EnsureDirectories();
|
|
|
|
LocalizationSettings settings = EnsureSettingsAsset();
|
|
LocalizationEditorSettings.ActiveLocalizationSettings = settings;
|
|
|
|
Locale zhLocale = EnsureLocale("zh-CN", "Chinese (Simplified)", $"{LocalesDirectory}/zh-CN.asset");
|
|
Locale enLocale = EnsureLocale("en-US", "English (United States)", $"{LocalesDirectory}/en-US.asset");
|
|
var locales = new List<Locale> { zhLocale, enLocale };
|
|
|
|
StringTableCollection gameTextCollection = EnsureStringCollection(LocalizationService.GameTextTableCollectionName, locales);
|
|
StringTableCollection literalCollection = EnsureStringCollection(LocalizationService.LiteralTextTableCollectionName, locales);
|
|
|
|
LocalizationLanguagePack zhPack = LoadPack(ZhJsonPath);
|
|
LocalizationLanguagePack enPack = LoadPack(EnJsonPath);
|
|
|
|
Dictionary<string, string> zhEntries = BuildEntryMap(zhPack?.entries, scanCodeFallbacks: true, preferJsonValues: true);
|
|
Dictionary<string, string> enEntries = BuildEntryMap(enPack?.entries, scanCodeFallbacks: false, preferJsonValues: true);
|
|
BackfillMissingEntries(zhEntries, enEntries);
|
|
|
|
Dictionary<string, string> zhLiterals = BuildLiteralMap(zhPack?.literals, enPack?.literals, english: false);
|
|
Dictionary<string, string> enLiterals = BuildLiteralMap(zhPack?.literals, enPack?.literals, english: true);
|
|
|
|
SavePack(ZhJsonPath, "zh-CN", zhEntries, zhLiterals);
|
|
SavePack(EnJsonPath, "en-US", enEntries, enLiterals);
|
|
|
|
ApplyEntries(gameTextCollection, zhLocale, zhEntries);
|
|
ApplyEntries(gameTextCollection, enLocale, enEntries);
|
|
ApplyEntries(literalCollection, zhLocale, zhLiterals);
|
|
ApplyEntries(literalCollection, enLocale, enLiterals);
|
|
|
|
SetPreloadFlags(gameTextCollection);
|
|
SetPreloadFlags(literalCollection);
|
|
|
|
EditorUtility.SetDirty(settings);
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
}
|
|
|
|
private static bool HasCoreLocalizationAssets()
|
|
{
|
|
if (!File.Exists(SettingsAssetPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LocalizationSettings settings = AssetDatabase.LoadAssetAtPath<LocalizationSettings>(SettingsAssetPath);
|
|
if (settings == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (LocalizationEditorSettings.GetLocale("zh-CN") == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (LocalizationEditorSettings.GetLocale("en-US") == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (LocalizationEditorSettings.GetStringTableCollection(LocalizationService.GameTextTableCollectionName) == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (LocalizationEditorSettings.GetStringTableCollection(LocalizationService.LiteralTextTableCollectionName) == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void EnsureLocalizationShellAssets()
|
|
{
|
|
EnsureDirectories();
|
|
|
|
LocalizationSettings settings = EnsureSettingsAsset();
|
|
LocalizationEditorSettings.ActiveLocalizationSettings = settings;
|
|
|
|
Locale zhLocale = EnsureLocale("zh-CN", "Chinese (Simplified)", $"{LocalesDirectory}/zh-CN.asset");
|
|
Locale enLocale = EnsureLocale("en-US", "English (United States)", $"{LocalesDirectory}/en-US.asset");
|
|
var locales = new List<Locale> { zhLocale, enLocale };
|
|
|
|
EnsureStringCollection(LocalizationService.GameTextTableCollectionName, locales);
|
|
EnsureStringCollection(LocalizationService.LiteralTextTableCollectionName, locales);
|
|
|
|
EditorUtility.SetDirty(settings);
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
private static void EnsureDirectories()
|
|
{
|
|
Directory.CreateDirectory(SettingsDirectory);
|
|
Directory.CreateDirectory(LocalesDirectory);
|
|
Directory.CreateDirectory(TablesDirectory);
|
|
}
|
|
|
|
private static LocalizationSettings EnsureSettingsAsset()
|
|
{
|
|
var settings = AssetDatabase.LoadAssetAtPath<LocalizationSettings>(SettingsAssetPath);
|
|
if (settings != null)
|
|
{
|
|
return settings;
|
|
}
|
|
|
|
settings = ScriptableObject.CreateInstance<LocalizationSettings>();
|
|
AssetDatabase.CreateAsset(settings, SettingsAssetPath);
|
|
return settings;
|
|
}
|
|
|
|
private static Locale EnsureLocale(string code, string localeName, string assetPath)
|
|
{
|
|
Locale locale = LocalizationEditorSettings.GetLocale(code);
|
|
if (locale == null)
|
|
{
|
|
locale = AssetDatabase.LoadAssetAtPath<Locale>(assetPath);
|
|
}
|
|
|
|
if (locale == null)
|
|
{
|
|
locale = Locale.CreateLocale(new LocaleIdentifier(code));
|
|
locale.name = localeName;
|
|
locale.LocaleName = localeName;
|
|
AssetDatabase.CreateAsset(locale, assetPath);
|
|
}
|
|
|
|
if (LocalizationEditorSettings.GetLocale(code) == null)
|
|
{
|
|
LocalizationEditorSettings.AddLocale(locale);
|
|
}
|
|
|
|
EditorUtility.SetDirty(locale);
|
|
return locale;
|
|
}
|
|
|
|
private static StringTableCollection EnsureStringCollection(string tableName, IList<Locale> locales)
|
|
{
|
|
StringTableCollection collection = LocalizationEditorSettings.GetStringTableCollection(tableName);
|
|
if (collection == null)
|
|
{
|
|
collection = LocalizationEditorSettings.CreateStringTableCollection(tableName, TablesDirectory, locales);
|
|
}
|
|
|
|
for (int i = 0; i < locales.Count; i++)
|
|
{
|
|
if (collection.GetTable(locales[i].Identifier) == null)
|
|
{
|
|
collection.AddNewTable(locales[i].Identifier);
|
|
}
|
|
}
|
|
|
|
EditorUtility.SetDirty(collection);
|
|
return collection;
|
|
}
|
|
|
|
private static LocalizationLanguagePack LoadPack(string assetPath)
|
|
{
|
|
if (!File.Exists(assetPath))
|
|
{
|
|
return new LocalizationLanguagePack();
|
|
}
|
|
|
|
string json = File.ReadAllText(assetPath);
|
|
LocalizationLanguagePack pack = JsonConvert.DeserializeObject<LocalizationLanguagePack>(json);
|
|
return pack ?? new LocalizationLanguagePack();
|
|
}
|
|
|
|
private static Dictionary<string, string> BuildEntryMap(Dictionary<string, string> seedEntries, bool scanCodeFallbacks, bool preferJsonValues)
|
|
{
|
|
var result = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
|
|
if (seedEntries != null)
|
|
{
|
|
foreach (var pair in seedEntries)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(pair.Key) && !string.IsNullOrWhiteSpace(pair.Value))
|
|
{
|
|
result[pair.Key] = pair.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!scanCodeFallbacks)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
foreach (string sourceFile in EnumerateSourceFiles("Assets"))
|
|
{
|
|
string content = File.ReadAllText(sourceFile);
|
|
foreach (Match match in LocalizedTextRegex.Matches(content))
|
|
{
|
|
string key = Regex.Unescape(match.Groups["key"].Value);
|
|
string fallback = Regex.Unescape(match.Groups["fallback"].Value);
|
|
if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(fallback))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!result.ContainsKey(key) || !preferJsonValues)
|
|
{
|
|
result[key] = fallback;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static Dictionary<string, string> BuildLiteralMap(
|
|
Dictionary<string, string> zhSeed,
|
|
Dictionary<string, string> enSeed,
|
|
bool english)
|
|
{
|
|
var result = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
Dictionary<string, string> primary = english ? enSeed : zhSeed;
|
|
|
|
if (primary != null)
|
|
{
|
|
foreach (var pair in primary)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(pair.Key) && !string.IsNullOrWhiteSpace(pair.Value))
|
|
{
|
|
result[pair.Key] = pair.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (string sourceFile in EnumerateSourceFiles("Assets"))
|
|
{
|
|
string content = File.ReadAllText(sourceFile);
|
|
MergeLiteralMatches(content, LiteralRegex, result, english);
|
|
MergeLiteralMatches(content, NoticeLiteralRegex, result, english);
|
|
}
|
|
|
|
foreach (string assetPath in EnumerateLiteralAssetPaths())
|
|
{
|
|
foreach (string literal in ReadLiteralStringsFromYaml(assetPath))
|
|
{
|
|
if (!result.ContainsKey(literal))
|
|
{
|
|
result[literal] = literal;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (string literal in EnumerateScriptableObjectLiterals())
|
|
{
|
|
if (!result.ContainsKey(literal))
|
|
{
|
|
result[literal] = literal;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void MergeLiteralMatches(string content, Regex regex, Dictionary<string, string> target, bool english)
|
|
{
|
|
foreach (Match match in regex.Matches(content))
|
|
{
|
|
string literal = Regex.Unescape(match.Groups["literal"].Value);
|
|
if (!ShouldCaptureLiteral(literal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!target.ContainsKey(literal))
|
|
{
|
|
target[literal] = literal;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<string> EnumerateSourceFiles(string rootDirectory)
|
|
{
|
|
return Directory.EnumerateFiles(rootDirectory, "*.cs", SearchOption.AllDirectories)
|
|
.Where(path => path.IndexOf("\\Editor\\", StringComparison.OrdinalIgnoreCase) < 0);
|
|
}
|
|
|
|
private static IEnumerable<string> EnumerateLiteralAssetPaths()
|
|
{
|
|
return Directory.EnumerateFiles("Assets", "*.*", SearchOption.AllDirectories)
|
|
.Where(path => LiteralAssetExtensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase))
|
|
.Where(path => !ShouldSkipAssetPath(path));
|
|
}
|
|
|
|
private static IEnumerable<string> ReadLiteralStringsFromYaml(string assetPath)
|
|
{
|
|
foreach (string rawLine in File.ReadLines(assetPath))
|
|
{
|
|
Match match = YamlTextRegex.Match(rawLine);
|
|
if (!match.Success)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string value;
|
|
try
|
|
{
|
|
value = DecodeYamlText(match.Groups["value"].Value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[Localization] Skipped malformed YAML text in {assetPath}: {ex.Message}");
|
|
continue;
|
|
}
|
|
|
|
if (ShouldCaptureLiteral(value))
|
|
{
|
|
yield return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<string> EnumerateScriptableObjectLiterals()
|
|
{
|
|
string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { "Assets" });
|
|
for (int i = 0; i < guids.Length; i++)
|
|
{
|
|
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
|
|
if (string.IsNullOrWhiteSpace(assetPath) || ShouldSkipAssetPath(assetPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ScriptableObject asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(assetPath);
|
|
if (asset == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SerializedObject serializedObject = new SerializedObject(asset);
|
|
SerializedProperty iterator = serializedObject.GetIterator();
|
|
bool enterChildren = true;
|
|
while (iterator.NextVisible(enterChildren))
|
|
{
|
|
enterChildren = true;
|
|
if (iterator.propertyType != SerializedPropertyType.String)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ShouldSkipScriptableObjectString(asset, iterator))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string literal = iterator.stringValue;
|
|
if (ShouldCaptureLiteral(literal))
|
|
{
|
|
yield return literal.Trim();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool ShouldSkipScriptableObjectString(ScriptableObject asset, SerializedProperty property)
|
|
{
|
|
if (asset == null || property == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (string.Equals(property.propertyPath, "m_Script", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (asset is SongData && ScriptableObjectStringExclusionPaths.Contains(property.propertyPath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static string DecodeYamlText(string rawValue)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(rawValue))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
string value = rawValue.Trim();
|
|
if (value == "\"\"" || value == "''")
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
bool isDoubleQuoted = value.Length >= 2 &&
|
|
value.StartsWith("\"", StringComparison.Ordinal) &&
|
|
value.EndsWith("\"", StringComparison.Ordinal);
|
|
bool isSingleQuoted = value.Length >= 2 &&
|
|
value.StartsWith("'", StringComparison.Ordinal) &&
|
|
value.EndsWith("'", StringComparison.Ordinal);
|
|
|
|
if (isDoubleQuoted || isSingleQuoted)
|
|
{
|
|
if (isDoubleQuoted)
|
|
{
|
|
try
|
|
{
|
|
value = JsonConvert.DeserializeObject<string>(value);
|
|
}
|
|
catch
|
|
{
|
|
value = value.Substring(1, value.Length - 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
value = value.Substring(1, value.Length - 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
value = DecodeYamlEscapes(value);
|
|
}
|
|
|
|
return value.Trim();
|
|
}
|
|
|
|
private static string DecodeYamlEscapes(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value) || value.IndexOf('\\') < 0)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
var builder = new StringBuilder(value.Length);
|
|
for (int i = 0; i < value.Length; i++)
|
|
{
|
|
char current = value[i];
|
|
if (current != '\\' || i + 1 >= value.Length)
|
|
{
|
|
builder.Append(current);
|
|
continue;
|
|
}
|
|
|
|
char next = value[i + 1];
|
|
switch (next)
|
|
{
|
|
case 'n':
|
|
builder.Append('\n');
|
|
i++;
|
|
break;
|
|
case 'r':
|
|
builder.Append('\r');
|
|
i++;
|
|
break;
|
|
case 't':
|
|
builder.Append('\t');
|
|
i++;
|
|
break;
|
|
case '"':
|
|
case '\'':
|
|
case '\\':
|
|
builder.Append(next);
|
|
i++;
|
|
break;
|
|
case 'u' when i + 5 < value.Length:
|
|
string hex = value.Substring(i + 2, 4);
|
|
if (ushort.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out ushort code))
|
|
{
|
|
builder.Append((char)code);
|
|
i += 5;
|
|
}
|
|
else
|
|
{
|
|
builder.Append(current);
|
|
}
|
|
break;
|
|
case 'x':
|
|
builder.Append(next);
|
|
i++;
|
|
break;
|
|
default:
|
|
builder.Append(current);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static bool ShouldCaptureLiteral(string literal)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(literal))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string trimmed = literal.Trim();
|
|
if (trimmed.Length == 0 || trimmed.Length > 512)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (trimmed.StartsWith("guid:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool hasLetterLikeContent = trimmed.Any(ch => char.IsLetter(ch) || IsCjk(ch));
|
|
return hasLetterLikeContent;
|
|
}
|
|
|
|
private static bool IsCjk(char ch)
|
|
{
|
|
return ch >= 0x4E00 && ch <= 0x9FFF;
|
|
}
|
|
|
|
private static bool ShouldSkipAssetPath(string path)
|
|
{
|
|
string normalized = path.Replace('\\', '/');
|
|
for (int i = 0; i < SkippedPathPrefixes.Length; i++)
|
|
{
|
|
if (normalized.StartsWith(SkippedPathPrefixes[i], StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static void BackfillMissingEntries(Dictionary<string, string> zhEntries, Dictionary<string, string> enEntries)
|
|
{
|
|
foreach (string key in zhEntries.Keys.ToArray())
|
|
{
|
|
if (!enEntries.ContainsKey(key))
|
|
{
|
|
enEntries[key] = zhEntries[key];
|
|
}
|
|
}
|
|
|
|
foreach (string key in enEntries.Keys.ToArray())
|
|
{
|
|
if (!zhEntries.ContainsKey(key))
|
|
{
|
|
zhEntries[key] = enEntries[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ApplyEntries(StringTableCollection collection, Locale locale, Dictionary<string, string> entries)
|
|
{
|
|
if (collection == null || locale == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StringTable table = collection.GetTable(locale.Identifier) as StringTable;
|
|
if (table == null)
|
|
{
|
|
table = collection.AddNewTable(locale.Identifier) as StringTable;
|
|
}
|
|
|
|
foreach (var pair in entries)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(pair.Key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
StringTableEntry entry = table.GetEntry(pair.Key);
|
|
if (entry == null)
|
|
{
|
|
table.AddEntry(pair.Key, pair.Value ?? string.Empty);
|
|
}
|
|
else if (entry.LocalizedValue != (pair.Value ?? string.Empty))
|
|
{
|
|
entry.Value = pair.Value ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
EditorUtility.SetDirty(table);
|
|
EditorUtility.SetDirty(table.SharedData);
|
|
}
|
|
|
|
private static Dictionary<string, string> ExtractEntries(StringTableCollection collection, Locale locale)
|
|
{
|
|
var result = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
if (collection == null || locale == null)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
StringTable table = collection.GetTable(locale.Identifier) as StringTable;
|
|
if (table == null)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
foreach (StringTableEntry entry in table.Values)
|
|
{
|
|
if (entry == null || string.IsNullOrWhiteSpace(entry.Key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result[entry.Key] = entry.LocalizedValue ?? string.Empty;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void SavePack(string assetPath, string languageCode, Dictionary<string, string> entries, Dictionary<string, string> literals)
|
|
{
|
|
var pack = new LocalizationLanguagePack
|
|
{
|
|
languageCode = languageCode,
|
|
entries = new Dictionary<string, string>(entries, StringComparer.Ordinal),
|
|
literals = new Dictionary<string, string>(literals, StringComparer.Ordinal)
|
|
};
|
|
|
|
string json = JsonConvert.SerializeObject(pack, Formatting.Indented);
|
|
File.WriteAllText(assetPath, json);
|
|
}
|
|
|
|
private static void SetPreloadFlags(StringTableCollection collection)
|
|
{
|
|
foreach (var table in collection.StringTables)
|
|
{
|
|
LocalizationEditorSettings.SetPreloadTableFlag(table, true);
|
|
EditorUtility.SetDirty(table);
|
|
}
|
|
}
|
|
}
|