hellcat-nardo-felidal/Assets/_PoiyomiShaders/Scripts/ThryEditor/Editor/Localization.cs

565 lines
24 KiB
C#
Raw Permalink Normal View History

2023-09-10 04:16:23 +00:00
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++;
}
}
}
}
}
}