565 lines
24 KiB
C#
Executable file
565 lines
24 KiB
C#
Executable file
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Thry{
|
|
public class Localization : ScriptableObject
|
|
{
|
|
[SerializeField] Shader[] ValidateWithShaders;
|
|
[SerializeField] string DefaultLanguage = "English";
|
|
[SerializeField] string[] Languages = new string[0];
|
|
[SerializeField]int SelectedLanguage = -1;
|
|
[SerializeField]
|
|
string[] _keys = new string[0];
|
|
[SerializeField]
|
|
string[] _values = new string[0];
|
|
[SerializeField]
|
|
string[] _defaultKeys = new string[0];
|
|
[SerializeField]
|
|
string[] _defaultValues = new string[0];
|
|
|
|
Dictionary<string, string[]> _localizedStrings = new Dictionary<string, string[]>();
|
|
Dictionary<string,string> _defaultKeyValues = new Dictionary<string,string>();
|
|
string[] _allLanguages;
|
|
bool _isLoaded = false;
|
|
bool _couldNotLoad = false;
|
|
|
|
// Use
|
|
public static Localization Load(string guid)
|
|
{
|
|
string path = AssetDatabase.GUIDToAssetPath(guid);
|
|
Localization l = AssetDatabase.LoadAssetAtPath<Localization>(path);
|
|
if(l == null)
|
|
{
|
|
l = ScriptableObject.CreateInstance<Localization>();
|
|
l._couldNotLoad = true;
|
|
return l;
|
|
}
|
|
l.Load();
|
|
return l;
|
|
}
|
|
|
|
void Load()
|
|
{
|
|
_allLanguages = new string[Languages.Length + 1];
|
|
_allLanguages[0] = DefaultLanguage;
|
|
Array.Copy(Languages, 0, _allLanguages, 1, Languages.Length);
|
|
_localizedStrings = new Dictionary<string, string[]>();
|
|
for (int i = 0; i < _keys.Length; i++)
|
|
{
|
|
string[] ar = new string[Languages.Length];
|
|
Array.Copy(_values, i * Languages.Length , ar, 0, Languages.Length);
|
|
_localizedStrings[_keys[i]] = ar;
|
|
}
|
|
_isLoaded = true;
|
|
}
|
|
|
|
public static Localization Create()
|
|
{
|
|
Localization l = ScriptableObject.CreateInstance<Localization>();
|
|
l._allLanguages = new string[l.Languages.Length + 1];
|
|
l._allLanguages[0] = l.DefaultLanguage;
|
|
Array.Copy(l.Languages, 0, l._allLanguages, 1, l.Languages.Length);
|
|
l._localizedStrings = new Dictionary<string, string[]>();
|
|
return l;
|
|
}
|
|
|
|
public void DrawDropdown()
|
|
{
|
|
if(_couldNotLoad)
|
|
{
|
|
EditorGUILayout.HelpBox("Could not load localization file", MessageType.Warning);
|
|
return;
|
|
}
|
|
EditorGUI.BeginChangeCheck();
|
|
SelectedLanguage = EditorGUILayout.Popup(SelectedLanguage + 1, _allLanguages) - 1;
|
|
if(EditorGUI.EndChangeCheck())
|
|
{
|
|
ShaderEditor.Active.Reload();
|
|
EditorUtility.SetDirty(this);
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
}
|
|
|
|
public string Get(MaterialProperty prop, string defaultValue)
|
|
{
|
|
if(_localizedStrings.ContainsKey(prop.name))
|
|
{
|
|
string[] ar = _localizedStrings[prop.name];
|
|
if(ar.Length > SelectedLanguage && SelectedLanguage > -1)
|
|
{
|
|
return ar[SelectedLanguage] ?? defaultValue;
|
|
}
|
|
}
|
|
_defaultKeyValues[prop.name] = defaultValue;
|
|
return defaultValue;
|
|
}
|
|
|
|
public string Get(MaterialProperty prop, FieldInfo field, string defaultValue)
|
|
{
|
|
string id = prop.name + "." + field.DeclaringType + "." + field.Name;
|
|
return Get(id, defaultValue);
|
|
}
|
|
|
|
public string Get(string id, string defaultValue)
|
|
{
|
|
if(id == null) return defaultValue;
|
|
if (_localizedStrings.ContainsKey(id))
|
|
{
|
|
string[] ar = _localizedStrings[id];
|
|
if (ar.Length > SelectedLanguage && SelectedLanguage > -1)
|
|
{
|
|
return ar[SelectedLanguage] ?? defaultValue;
|
|
}
|
|
}
|
|
_defaultKeyValues[id] = defaultValue;
|
|
return defaultValue;
|
|
}
|
|
|
|
// Managment
|
|
|
|
void AddLanguage(string language)
|
|
{
|
|
if (System.Array.IndexOf(Languages, language) == -1)
|
|
{
|
|
System.Array.Resize(ref Languages, Languages.Length + 1);
|
|
Languages[Languages.Length - 1] = language;
|
|
string[] keys = _localizedStrings.Keys.ToArray();
|
|
foreach(string key in keys)
|
|
{
|
|
string[] ar = _localizedStrings[key];
|
|
System.Array.Resize(ref ar, ar.Length + 1);
|
|
ar[ar.Length - 1] = null;
|
|
_localizedStrings[key] = ar;
|
|
}
|
|
Save();
|
|
}
|
|
}
|
|
|
|
void RemoveLanguage(string language)
|
|
{
|
|
int index = System.Array.IndexOf(Languages, language);
|
|
if (index != -1)
|
|
{
|
|
if(Languages.Length > 1)
|
|
{
|
|
for (int i = index; i < Languages.Length - 1; i++)
|
|
{
|
|
Languages[i] = Languages[i + 1];
|
|
}
|
|
System.Array.Resize(ref Languages, Languages.Length - 1);
|
|
string[] keys = _localizedStrings.Keys.ToArray();
|
|
foreach (string key in keys)
|
|
{
|
|
string[] ar = _localizedStrings[key];
|
|
for (int i = index; i < ar.Length - 1; i++)
|
|
{
|
|
ar[i] = ar[i + 1];
|
|
}
|
|
System.Array.Resize(ref ar, ar.Length - 1);
|
|
_localizedStrings[key] = ar;
|
|
}
|
|
}else
|
|
{
|
|
Languages = new string[0];
|
|
_localizedStrings = new Dictionary<string, string[]>();
|
|
}
|
|
Save();
|
|
}
|
|
}
|
|
|
|
void Save()
|
|
{
|
|
_defaultKeys = _defaultKeyValues.Keys.ToArray();
|
|
_defaultValues = _defaultKeyValues.Values.ToArray();
|
|
_keys = _localizedStrings.Keys.ToArray();
|
|
_values = new string[_keys.Length * Languages.Length];
|
|
for (int i = 0; i < _keys.Length; i++)
|
|
{
|
|
string[] ar = _localizedStrings[_keys[i]];
|
|
Array.Copy(ar, 0, _values, i * Languages.Length, ar.Length);
|
|
}
|
|
EditorUtility.SetDirty(this);
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
_defaultKeys = new string[0];
|
|
_defaultValues = new string[0];
|
|
_keys = new string[0];
|
|
_values = new string[0];
|
|
Languages = new string[0];
|
|
}
|
|
|
|
[MenuItem("Assets/Thry/Shaders/Create Locale File", false)]
|
|
static void CreateLocale()
|
|
{
|
|
Localization locale = ScriptableObject.CreateInstance<Localization>();
|
|
Shader[] shaders = Selection.objects.Select(o => o as Shader).ToArray();
|
|
string fileName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(shaders[0]));
|
|
string folderPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(shaders[0]));
|
|
locale.ValidateWithShaders = shaders;
|
|
AssetDatabase.CreateAsset(locale, folderPath + "/" + fileName + "_Locale.asset");
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
[MenuItem("Assets/Thry/Shaders/Create Locale File", true)]
|
|
static bool ValidateCreateLocale()
|
|
{
|
|
return Selection.objects.All(o => o is Shader);
|
|
}
|
|
|
|
[MenuItem("Assets/Thry/Shaders/Locale Property", false)]
|
|
static void CreateShaderProperty()
|
|
{
|
|
Localization l = Selection.activeObject as Localization;
|
|
string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(l));
|
|
string outS = $"[HideInInspector] {ShaderEditor.PROPERTY_NAME_LOCALE} (\"{guid}\", Float) = 0";
|
|
EditorGUIUtility.systemCopyBuffer = outS;
|
|
}
|
|
|
|
[MenuItem("Assets/Thry/Shaders/Locale Property", true)]
|
|
static bool ValidateCreateShaderProperty()
|
|
{
|
|
return Selection.activeObject is Localization;
|
|
}
|
|
|
|
[CustomEditor(typeof(Localization))]
|
|
public class LocaleEditor : Editor
|
|
{
|
|
List<(string key, string defaultValue, string newValue)> _missingKeys = new List<(string key, string defaultValue, string newValue)>();
|
|
int _selectedLanguageIndex = 0;
|
|
string _searchById = "";
|
|
string _searchByTranslation = "";
|
|
string[] _searchResults = new string[0];
|
|
|
|
string _translateByValueIn = "";
|
|
string _translateByValueOut = "";
|
|
string _autoTranslateLanguageShortCode = "EN";
|
|
|
|
string ToCSVString(string s)
|
|
{
|
|
return "\"" + s.Replace("\"", "“") + "\"";
|
|
}
|
|
|
|
string FromCSVString(string s)
|
|
{
|
|
return s.Trim('"').Replace("“", "\"");
|
|
}
|
|
|
|
void ExportAsCSV(Localization locale)
|
|
{
|
|
string path = EditorUtility.SaveFilePanel("Export as CSV", "", locale.name, "csv");
|
|
if (string.IsNullOrEmpty(path) == false)
|
|
{
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
|
foreach (string language in locale.Languages)
|
|
{
|
|
sb.Append("," + ToCSVString(language));
|
|
}
|
|
sb.AppendLine();
|
|
for(int i = 0;i < locale._keys.Length; i++)
|
|
{
|
|
sb.Append(ToCSVString(locale._keys[i]));
|
|
for(int j = 0; j < locale.Languages.Length; j++)
|
|
{
|
|
sb.Append("," + ToCSVString(locale._values[i * locale.Languages.Length + j]));
|
|
}
|
|
sb.AppendLine();
|
|
}
|
|
File.WriteAllText(path, sb.ToString());
|
|
}
|
|
}
|
|
|
|
void LoadFromCSV(Localization locale)
|
|
{
|
|
string path = EditorUtility.OpenFilePanel("Load from CSV", "", "csv");
|
|
if (string.IsNullOrEmpty(path) == false)
|
|
{
|
|
string[] lines = File.ReadAllLines(path);
|
|
if (lines.Length > 0)
|
|
{
|
|
locale.Clear();
|
|
string[] languages = lines[0].Split(',');
|
|
for (int i = 1; i < languages.Length; i++)
|
|
{
|
|
locale.AddLanguage(FromCSVString(languages[i]));
|
|
}
|
|
for (int i = 1; i < lines.Length; i++)
|
|
{
|
|
string[] values = lines[i].Split(',');
|
|
if (values.Length > 0)
|
|
{
|
|
string key = FromCSVString(values[0]);
|
|
for (int j = 1; j < values.Length; j++)
|
|
{
|
|
locale._values[(i - 1) * (languages.Length - 1) + j - 1] = FromCSVString(values[j]);
|
|
}
|
|
}
|
|
}
|
|
locale.Save();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateMissing(Localization locale)
|
|
{
|
|
_missingKeys.Clear();
|
|
foreach(string key in locale._localizedStrings.Keys)
|
|
{
|
|
if (string.IsNullOrEmpty(locale._localizedStrings[key][_selectedLanguageIndex]))
|
|
{
|
|
_missingKeys.Add((key, locale._defaultKeyValues[key], locale._defaultKeyValues[key]));
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateData(Localization locale)
|
|
{
|
|
locale.Load();
|
|
// create _defaultKeyValues
|
|
if(locale._defaultKeyValues == null)
|
|
{
|
|
locale._defaultKeyValues = new Dictionary<string, string>();
|
|
}
|
|
for(int i = 0; i < locale._defaultKeys.Length; i++)
|
|
{
|
|
if(locale._defaultKeyValues.ContainsKey(locale._defaultKeys[i]) == false)
|
|
locale._defaultKeyValues.Add(locale._defaultKeys[i], locale._defaultValues[i]);
|
|
}
|
|
// add all keys from shader
|
|
foreach(var kv in locale._defaultKeyValues)
|
|
{
|
|
string key = kv.Key;
|
|
if(key.StartsWith("footer_")) continue;
|
|
if(key == ShaderEditor.PROPERTY_NAME_MASTER_LABEL) continue;
|
|
if(key == ShaderEditor.PROPERTY_NAME_LABEL_FILE) continue;
|
|
if(key == ShaderEditor.PROPERTY_NAME_LOCALE) continue;
|
|
if(key == ShaderEditor.PROPERTY_NAME_ON_SWAP_TO_ACTIONS) continue;
|
|
if(key == ShaderEditor.PROPERTY_NAME_SHADER_VERSION) continue;
|
|
if(key == ShaderEditor.PROPERTY_NAME_EDITOR_DETECT) continue;
|
|
if (string.IsNullOrEmpty(kv.Value) == false && !locale._localizedStrings.ContainsKey(kv.Key))
|
|
{
|
|
locale._localizedStrings.Add(kv.Key, new string[locale.Languages.Length]);
|
|
}
|
|
}
|
|
// make missing keys a list of all keys that have an empty string in the selected language
|
|
UpdateMissing(locale);
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
Localization locale = (Localization)target;
|
|
locale.Load();
|
|
UpdateData(locale);
|
|
}
|
|
|
|
private void Awake() {
|
|
Localization locale = (Localization)target;
|
|
locale.Load();
|
|
UpdateData(locale);
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
Localization locale = (Localization)target;
|
|
if(!locale._isLoaded)
|
|
{
|
|
UpdateData(locale);
|
|
}
|
|
|
|
if(GUILayout.Button("Save"))
|
|
{
|
|
locale.Save();
|
|
}
|
|
|
|
EditorGUILayout.PropertyField(serializedObject.FindProperty("ValidateWithShaders"));
|
|
if(GUILayout.Button("Load Properties from Shaders"))
|
|
{
|
|
// for each shader create a material & material editor so that the data is loaded into the localization object
|
|
foreach(Shader s in locale.ValidateWithShaders)
|
|
{
|
|
ShaderEditor se = new ShaderEditor();
|
|
se.FakePartialInitilizationForLocaleGathering(s);
|
|
}
|
|
}
|
|
locale.DefaultLanguage = EditorGUILayout.TextField("Default Language", locale.DefaultLanguage);
|
|
|
|
EditorGUILayout.LabelField("Languages");
|
|
for (int i = 0; i < locale.Languages.Length; i++)
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
locale.Languages[i] = EditorGUILayout.TextField(locale.Languages[i]);
|
|
if (GUILayout.Button("Remove"))
|
|
{
|
|
locale.RemoveLanguage(locale.Languages[i]);
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
EditorGUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("Add"))
|
|
{
|
|
locale.AddLanguage("New Language");
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
// popup for selecting language
|
|
EditorGUI.BeginChangeCheck();
|
|
_selectedLanguageIndex = EditorGUILayout.Popup("Language to edit", _selectedLanguageIndex, locale.Languages);
|
|
if(EditorGUI.EndChangeCheck())
|
|
{
|
|
_missingKeys.Clear();
|
|
}
|
|
|
|
if(GUILayout.Button("Update"))
|
|
{
|
|
UpdateData(locale);
|
|
}
|
|
|
|
EditorGUILayout.Space(20);
|
|
EditorGUILayout.LabelField("Import / Export", EditorStyles.boldLabel);
|
|
if(GUILayout.Button("Import from CSV"))
|
|
LoadFromCSV(locale);
|
|
|
|
if(locale.Languages.Length == 0) return;
|
|
|
|
if(GUILayout.Button("Export to CSV"))
|
|
ExportAsCSV(locale);
|
|
|
|
EditorGUILayout.Space(20);
|
|
EditorGUILayout.LabelField("Missing Entries", EditorStyles.boldLabel);
|
|
(string,string,string) kvToRemove = default;
|
|
for(int i = 0; i < _missingKeys.Count && i < 10; i++)
|
|
{
|
|
var kv = _missingKeys[i];
|
|
EditorGUILayout.BeginHorizontal();
|
|
kv.newValue = EditorGUILayout.DelayedTextField(kv.key, kv.newValue);
|
|
if(GUILayout.Button("Skip", GUILayout.Width(50)))
|
|
{
|
|
kvToRemove = kv;
|
|
}
|
|
if(GUILayout.Button("Apply", GUILayout.Width(50)))
|
|
{
|
|
if (!locale._localizedStrings.ContainsKey(kv.key))
|
|
{
|
|
locale._localizedStrings.Add(kv.key, new string[locale.Languages.Length]);
|
|
}
|
|
locale._localizedStrings[kv.key][_selectedLanguageIndex] = kv.newValue;
|
|
kvToRemove = kv;
|
|
}
|
|
_missingKeys[i] = kv;
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
if(_missingKeys.Count > 10)
|
|
{
|
|
EditorGUILayout.LabelField("...");
|
|
}
|
|
if(kvToRemove != default)
|
|
{
|
|
_missingKeys.Remove(kvToRemove);
|
|
}
|
|
|
|
EditorGUILayout.Space(20);
|
|
EditorGUILayout.LabelField("Automatic Translation using Google", EditorStyles.boldLabel);
|
|
_autoTranslateLanguageShortCode = EditorGUILayout.TextField("Language Short Code", _autoTranslateLanguageShortCode);
|
|
EditorGUILayout.HelpBox("Short code must be valid short code. See https://cloud.google.com/translate/docs/languages for a list of valid short codes.", MessageType.Info);
|
|
if(Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
|
|
{
|
|
Application.OpenURL("https://cloud.google.com/translate/docs/languages");
|
|
}
|
|
if(GUILayout.Button("Auto Translate"))
|
|
{
|
|
int _missingKeysCount = _missingKeys.Count;
|
|
int i = 0;
|
|
foreach((string key, string defaultValue, string newValue) in _missingKeys)
|
|
{
|
|
EditorUtility.DisplayProgressBar("Auto Translate", $"Translating {i}/{_missingKeysCount}", (float)i / _missingKeysCount);
|
|
try
|
|
{
|
|
if (!locale._localizedStrings.ContainsKey(key))
|
|
{
|
|
locale._localizedStrings.Add(key, new string[locale.Languages.Length]);
|
|
}
|
|
locale._localizedStrings[key][_selectedLanguageIndex] = WebHelper.Translate(defaultValue, _autoTranslateLanguageShortCode);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
}
|
|
i += 1;
|
|
}
|
|
EditorUtility.ClearProgressBar();
|
|
locale.Save();
|
|
}
|
|
|
|
EditorGUILayout.Space(20);
|
|
EditorGUILayout.LabelField("Translate entries by value", EditorStyles.boldLabel);
|
|
EditorGUILayout.HelpBox("This will search all properties and translate all that have the exact display name with the selected value. Suggested usecase: Panning, UV", MessageType.Info);
|
|
_translateByValueIn = EditorGUILayout.TextField("Search for", _translateByValueIn);
|
|
_translateByValueOut = EditorGUILayout.TextField("Translate with", _translateByValueOut);
|
|
if(GUILayout.Button("Execute"))
|
|
{
|
|
foreach(var kv in locale._defaultKeyValues)
|
|
{
|
|
if(kv.Value == _translateByValueIn)
|
|
{
|
|
locale._localizedStrings[kv.Key][_selectedLanguageIndex] = _translateByValueOut;
|
|
}
|
|
}
|
|
UpdateMissing(locale);
|
|
}
|
|
|
|
EditorGUILayout.Space(20);
|
|
EditorGUILayout.LabelField("Existing Entries", EditorStyles.boldLabel);
|
|
EditorGUI.BeginChangeCheck();
|
|
_searchById = EditorGUILayout.TextField("Search by id", _searchById);
|
|
_searchByTranslation = EditorGUILayout.TextField("Search by translation", _searchByTranslation);
|
|
if(EditorGUI.EndChangeCheck())
|
|
{
|
|
List<string> res = new List<string>();
|
|
foreach (string key in locale._localizedStrings.Keys)
|
|
{
|
|
if(locale._localizedStrings[key][_selectedLanguageIndex] == null) continue;
|
|
if(locale._localizedStrings[key][_selectedLanguageIndex].IndexOf(_searchByTranslation, StringComparison.OrdinalIgnoreCase) != -1
|
|
&& key.IndexOf(_searchById, StringComparison.OrdinalIgnoreCase) != -1)
|
|
{
|
|
res.Add(key);
|
|
}
|
|
}
|
|
_searchResults = res.ToArray();
|
|
}
|
|
EditorGUILayout.Space(5);
|
|
if(_searchById.Length > 0 || _searchByTranslation.Length > 0)
|
|
{
|
|
int count = 0;
|
|
foreach (string key in _searchResults)
|
|
{
|
|
if(count > 50)
|
|
{
|
|
EditorGUILayout.LabelField("...");
|
|
break;
|
|
}
|
|
EditorGUILayout.BeginHorizontal();
|
|
string value = EditorGUILayout.DelayedTextField(key, locale._localizedStrings[key][_selectedLanguageIndex]);
|
|
if (GUILayout.Button("Remove", GUILayout.Width(65)))
|
|
{
|
|
locale._localizedStrings[key][_selectedLanguageIndex] = "";
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
locale._localizedStrings[key][_selectedLanguageIndex] = value;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|