Created
July 11, 2020 23:02
-
-
Save mrtrizer/9c64ed7d0f6247459bf9c2bbd7c7ae0e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEngine.Networking; | |
using System.Collections; | |
using System.Collections.Generic; | |
[CreateAssetMenu] | |
public class GoogleTranslateService : ScriptableObject | |
{ | |
public static GoogleTranslateService instance; | |
[System.Serializable] | |
public struct GoogleLanguageMeta | |
{ | |
public SystemLanguage langId; | |
public string googleLangId; | |
} | |
public List<GoogleLanguageMeta> languageMap; | |
private GoogleTranslateService() | |
{ | |
instance = this; | |
} | |
string GetRequest(string uri) | |
{ | |
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) | |
{ | |
// Request and wait for the desired page. | |
var operation = webRequest.SendWebRequest(); | |
while (!operation.isDone) | |
; | |
string[] pages = uri.Split('/'); | |
int page = pages.Length - 1; | |
if (webRequest.isNetworkError) | |
{ | |
Debug.Log(pages[page] + ": Error: " + webRequest.error); | |
} | |
else | |
{ | |
Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text); | |
} | |
return webRequest.downloadHandler.text; | |
} | |
} | |
public string trimTranslation(string source) | |
{ | |
const string msgStrPrefix = "Msgstr \\\""; | |
string result = source; | |
if (result[0] == '\\') | |
result = result.Substring(2); | |
else if (result[0] == '«') | |
result = result.Substring(1); | |
else if (result[0] == '\'') | |
result = result.Substring(1); | |
else if (result[0] == '「') | |
result = result.Substring(1); | |
else if (result[0] == '“') | |
result = result.Substring(1); | |
else if (result.IndexOf(msgStrPrefix) == 0) | |
result = result.Substring(msgStrPrefix.Length); | |
if (result[result.Length - 1] == '"') | |
result = result.Substring(0, result.Length - 2); | |
else if (result[result.Length - 1] == '\'') | |
result = result.Substring(0, result.Length - 1); | |
else if (result[result.Length - 1] == '」') | |
result = result.Substring(0, result.Length - 1); | |
else if (result[result.Length - 1] == '”') | |
result = result.Substring(0, result.Length - 1); | |
else if (result[result.Length - 1] == '»') | |
result = result.Substring(0, result.Length - 1); | |
else if (result[result.Length - 1] == '.') | |
{ | |
if (result[result.Length - 2] == '"' && result[result.Length - 3] == '\\') | |
result = result.Substring(0, result.Length - 3) + '.'; | |
else if (result[result.Length - 2] == '»') | |
result = result.Substring(0, result.Length - 2) + '.'; | |
} | |
result = result.Replace("\\n", "\n"); | |
result = result.Replace("** ", "\n"); | |
result = result.Replace("**", "\n"); | |
result = result.Replace("\\u200b", ""); | |
return result; | |
} | |
public string translate(string text, SystemLanguage sourceLanguage, SystemLanguage targetLanguage) | |
{ | |
if (sourceLanguage == targetLanguage) | |
return text; | |
string slStr = languageMap.Find(item => item.langId == sourceLanguage).googleLangId; | |
string tlStr = languageMap.Find(item => item.langId == targetLanguage).googleLangId; | |
text = text.Replace("\n", "**"); | |
text = text.Replace("\n", "**"); | |
var escapedText = System.Uri.EscapeDataString("\"" + text + "\""); | |
var url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", slStr, tlStr, escapedText); | |
Debug.Log(url); | |
string jsonText = GetRequest(url); | |
int endPos = jsonText.IndexOf("\",\""); | |
int offset = 4; | |
string translation = jsonText.Substring(offset, endPos - offset); | |
string trimmedTranslation = trimTranslation(translation); | |
return trimmedTranslation; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
[CreateAssetMenu] | |
[ExecuteInEditMode] | |
public class LanguageSettings : ScriptableObject | |
{ | |
public static LanguageSettings instance; | |
public delegate void LanguageChangedEvent(); | |
public event LanguageChangedEvent languageChangedEvent; | |
public SystemLanguage languageId; | |
public TranslationManager translationManager; | |
bool languageLoaded = false; | |
TranslationManager.LangMeta langMeta; | |
LanguageSettings() | |
{ | |
instance = this; | |
} | |
#if UNITY_EDITOR | |
private void OnEnable() | |
{ | |
updateLanguage(); | |
} | |
private void OnValidate() | |
{ | |
PlayerPrefs.SetInt("Language", (int)languageId); | |
updateLanguage(); | |
} | |
#endif | |
public void setLanguage(SystemLanguage id) { | |
var tm = ApplicationServices.translationManager; | |
languageId = tm.getSupportedLanguage(id); | |
langMeta = tm.getLanguageMeta(languageId); | |
languageLoaded = true; | |
PlayerPrefs.SetInt("Language", (int)id); | |
languageChangedEvent?.Invoke(); | |
} | |
SystemLanguage getSystemLanguage() { | |
return Application.systemLanguage; | |
} | |
void updateLanguage() | |
{ | |
languageLoaded = false; | |
getLanguage(); | |
} | |
public SystemLanguage getLanguage() { | |
if (!languageLoaded) | |
{ | |
var selectedLanguageId = getSystemLanguage(); | |
int languageIdInt = PlayerPrefs.GetInt("Language", -1); | |
if (languageIdInt > 2) | |
selectedLanguageId = (SystemLanguage)languageIdInt; | |
languageId = translationManager.getSupportedLanguage(selectedLanguageId); | |
langMeta = translationManager.getLanguageMeta(languageId); | |
languageLoaded = true; | |
} | |
return languageId; | |
} | |
public TranslationManager.LangMeta getLanguageMeta() | |
{ | |
getLanguage(); | |
return langMeta; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
[CreateAssetMenu] | |
public class Translation : ScriptableObject | |
{ | |
public string uid; | |
[TextArea] | |
public string masterText; | |
[System.Serializable] | |
public struct Localization | |
{ | |
public SystemLanguage languageId; | |
[TextArea] | |
public string text; | |
[TextArea] | |
public string autoText; | |
} | |
public List<Localization> localizations; | |
string overrideText(string oldText, string newText) | |
{ | |
if (newText == null) | |
{ | |
return oldText == null ? "" : oldText; | |
} | |
else | |
{ | |
return newText; | |
} | |
} | |
public void setAutoLocalization(SystemLanguage languageId, string text) | |
{ | |
setLocalization(languageId, null, text); | |
} | |
public void setManualLocalization(SystemLanguage languageId, string text) | |
{ | |
setLocalization(languageId, text, null); | |
} | |
public void setLocalization(SystemLanguage languageId, string text, string autoText) | |
{ | |
var index = localizations.FindIndex(item => item.languageId == languageId); | |
var newLocalization = new Localization { | |
languageId = languageId, | |
text = overrideText(index != -1 ? localizations[index].text : null, text), | |
autoText = overrideText(index != -1 ? localizations[index].autoText : null, autoText) | |
}; | |
if (index != -1) | |
{ | |
localizations[index] = newLocalization; | |
} | |
else | |
{ | |
localizations.Add(newLocalization); | |
} | |
} | |
public bool getRawLocalization(SystemLanguage languageId, ref Localization localization) | |
{ | |
var index = localizations.FindIndex(item => item.languageId == languageId); | |
if (index == -1) | |
return false; | |
localization = localizations[index]; | |
return true; | |
} | |
public string getLocalization(SystemLanguage languageId) | |
{ | |
var localization = new Localization(); | |
bool success = getRawLocalization(languageId, ref localization); | |
if (success) | |
{ | |
return localization.text != "" ? localization.text : localization.autoText; | |
} | |
else | |
{ | |
#if UNITY_EDITOR | |
Debug.LogError("Translation is not provided for " + languageId.ToString()); | |
#endif | |
if (localizations.Count > 0) | |
return masterText; | |
else | |
return "{" + this.name + "}"; | |
} | |
} | |
public string getTranslatedText() { | |
var languageSettings = LanguageSettings.instance; | |
var languageId = languageSettings.getLanguage(); | |
return getLocalization(languageId); | |
} | |
#if UNITY_EDITOR | |
[ContextMenu("AutoTranslate")] | |
public void autoTranslateForce() | |
{ | |
TranslationManager.instance.autoTranslateById(uid); | |
} | |
public bool autoTranslateLang(SystemLanguage langId, bool force = false) | |
{ | |
var localization = new Localization(); | |
bool success = getRawLocalization(langId, ref localization); | |
if (!force && success && localization.autoText != null && localization.autoText != "") | |
return false; | |
string str = GoogleTranslateService.instance.translate(masterText, SystemLanguage.English, langId); | |
setLocalization(langId, null, str); | |
return true; | |
} | |
public bool autoTranslate(int pauseMs, bool force = false) | |
{ | |
bool modified = false; | |
foreach (var meta in TranslationManager.instance.supportedLanguageMetas) | |
{ | |
if (autoTranslateLang(meta.langId, force)) | |
modified = true; | |
System.Threading.Thread.Sleep(pauseMs); | |
} | |
if (modified) | |
{ | |
EditorUtility.SetDirty(this); | |
} | |
return modified; | |
} | |
#endif | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
#if UNITY_EDITOR | |
using UnityEditor; | |
using System.IO; | |
#endif | |
[ExecuteInEditMode] | |
[CreateAssetMenu] | |
public class TranslationManager : ScriptableObject | |
{ | |
[System.Serializable] | |
public struct LangMeta | |
{ | |
public SystemLanguage langId; | |
public string nativeName; | |
public bool boldFont; | |
public bool allowBestFitMode; | |
} | |
public List<LangMeta> supportedLanguageMetas; | |
public List<Translation> initialList; | |
public GoogleTranslateService translateService; | |
Dictionary<string, Translation> translationMap = new Dictionary<string, Translation>(); | |
#if UNITY_EDITOR | |
static public TranslationManager instance; | |
static int translationIndex = 0; | |
static List<string> singleTranslationIds = new List<string>(); | |
static bool translationEnabled = false; | |
static double nextTranslationTime = 0.0f; | |
static int langIndex = 0; | |
const string autoPrefix = "Auto_"; | |
[ContextMenu("AutoTranslate")] | |
public void autoTranslate() | |
{ | |
translationEnabled = true; | |
translationIndex = 0; | |
langIndex = 0; | |
nextTranslationTime = EditorApplication.timeSinceStartup; | |
} | |
public void autoTranslateById(string id) | |
{ | |
if (singleTranslationIds.Count == 0) | |
{ | |
autoTranslate(); | |
translationIndex = initialList.FindIndex(item => item.uid == id); | |
} | |
singleTranslationIds.Add(id); | |
} | |
[ContextMenu("StopAutoTranslate")] | |
public void stopAutoTranslate() | |
{ | |
translationEnabled = false; | |
} | |
[ContextMenu("FixQuotes")] | |
public void fixQuotes() | |
{ | |
foreach (var translation in initialList) | |
{ | |
foreach (var localization in translation.localizations.ToArray()) | |
{ | |
if (localization.languageId == SystemLanguage.Japanese | |
|| localization.languageId == SystemLanguage.Dutch | |
|| localization.languageId == SystemLanguage.ChineseSimplified | |
|| localization.languageId == SystemLanguage.ChineseTraditional) | |
{ | |
string trimmed = GoogleTranslateService.instance.trimTranslation(localization.autoText); | |
if (localization.autoText != trimmed) | |
{ | |
Debug.Log(localization.autoText + " >> " + trimmed + "\n" + AssetDatabase.GetAssetPath(translation)); | |
translation.setAutoLocalization(localization.languageId, trimmed); | |
EditorUtility.SetDirty(translation); | |
} | |
} | |
} | |
} | |
} | |
#endif | |
public LangMeta getLanguageMeta(SystemLanguage langId) | |
{ | |
int index = supportedLanguageMetas.FindIndex(item => item.langId == langId); | |
if (index != -1) | |
return supportedLanguageMetas[index]; | |
else | |
return supportedLanguageMetas[0]; | |
} | |
public Translation getTranslation(string uid) | |
{ | |
Translation result; | |
if (!translationMap.TryGetValue(uid, out result)) | |
{ | |
Debug.Log("Can't find translation " + uid); | |
} | |
return result; | |
} | |
public SystemLanguage getSupportedLanguage(SystemLanguage systemLanguage) | |
{ | |
int index = supportedLanguageMetas.FindIndex(item => item.langId == systemLanguage); | |
if (index != -1) | |
return systemLanguage; | |
else | |
return supportedLanguageMetas[0].langId; | |
} | |
private void OnEnable() | |
{ | |
translationMap.Clear(); | |
foreach (var translation in initialList) | |
{ | |
translationMap.Add(translation.uid, translation); | |
} | |
} | |
public TranslationManager() | |
{ | |
#if UNITY_EDITOR | |
instance = this; | |
EditorApplication.update += onUpdate; | |
#endif | |
} | |
#if UNITY_EDITOR | |
static void onUpdate() | |
{ | |
var tm = TranslationManager.instance; | |
if (translationEnabled && nextTranslationTime < EditorApplication.timeSinceStartup) | |
{ | |
var translation = tm.initialList[translationIndex]; | |
try | |
{ | |
if (langIndex >= tm.supportedLanguageMetas.Count) | |
{ | |
if (singleTranslationIds.Count > 0) | |
{ | |
singleTranslationIds.RemoveAt(0); | |
if (singleTranslationIds.Count > 0) | |
translationIndex = tm.initialList.FindIndex(item => item.uid == singleTranslationIds[0]); | |
else | |
translationEnabled = false; | |
} | |
else | |
{ | |
translationIndex++; | |
} | |
langIndex = 0; | |
} | |
else | |
{ | |
var langId = TranslationManager.instance.supportedLanguageMetas[langIndex].langId; | |
if (translation.autoTranslateLang(langId, singleTranslationIds.Count > 0)) | |
{ | |
nextTranslationTime = EditorApplication.timeSinceStartup + 12.0; | |
EditorUtility.SetDirty(translation); | |
} | |
langIndex++; | |
} | |
} catch(System.Exception e) | |
{ | |
Debug.LogException(e); | |
Debug.LogError("Translation break"); | |
translationEnabled = false; | |
} | |
if (translationIndex >= TranslationManager.instance.initialList.Count) | |
{ | |
translationEnabled = false; | |
Debug.Log("Translation finish"); | |
} | |
Debug.Log("Translation: " + translationIndex + "/" + TranslationManager.instance.initialList.Count); | |
} | |
} | |
string generateCsv() | |
{ | |
string csvText = "id, path"; | |
foreach (var supportedLang in supportedLanguageMetas) | |
csvText += ", " + autoPrefix + supportedLang.langId.ToString() + ", " + supportedLang.langId.ToString(); | |
csvText += "\n"; | |
foreach (var translation in translationMap) | |
{ | |
var path = AssetDatabase.GetAssetPath(translation.Value); | |
if (path == "") | |
continue; | |
csvText += translation.Key + "," + path; | |
foreach (var supportedLang in supportedLanguageMetas) | |
{ | |
var localization = new Translation.Localization(); | |
bool success = translation.Value.getRawLocalization(supportedLang.langId, ref localization); | |
csvText += ",\"" + (success ? localization.autoText : "") + "\", \"" + (success ? localization.text : "") + "\""; | |
} | |
csvText += "\n"; | |
} | |
return csvText; | |
} | |
[ContextMenu("Generate CSV")] | |
public void saveCsv() | |
{ | |
string path = EditorUtility.SaveFolderPanel("Save csv to folder", "", ""); | |
if (path.Length != 0) | |
{ | |
var csvContent = generateCsv(); | |
File.WriteAllText(path + "/localization.csv", csvContent); | |
} | |
} | |
List<List<string>> splitCsv(string str) | |
{ | |
var lines = new List<List<string>>(); | |
var lineList = new List<string>(); | |
bool startQuote = false; | |
bool escape = false; | |
string accum = ""; | |
foreach (char c in str) | |
{ | |
if (!escape && startQuote && c == '\\') | |
{ | |
escape = true; | |
} | |
else if (!escape && c == '"') | |
{ | |
startQuote = !startQuote; | |
} | |
else if (!escape && !startQuote && c == ',') | |
{ | |
lineList.Add(accum); | |
accum = ""; | |
} | |
else if (!escape && !startQuote && c == '\n') | |
{ | |
lineList.Add(accum); | |
accum = ""; | |
lines.Add(lineList); | |
lineList = new List<string>(); | |
} | |
else | |
{ | |
if (c != '\r') | |
accum += c; | |
escape = false; | |
} | |
} | |
if (accum.Length > 0) | |
lineList.Add(accum); | |
if (lineList.Count > 0) | |
lines.Add(lineList); | |
return lines; | |
} | |
struct ColumnMeta | |
{ | |
public SystemLanguage langId; | |
public bool autoTranslation; | |
} | |
Dictionary<int, ColumnMeta> getIndexMap(List<string> firstRow) | |
{ | |
var map = new Dictionary<int, ColumnMeta>(); | |
for (int i = 2; i < firstRow.Count; i++) | |
{ | |
SystemLanguage langId; | |
string columnName = firstRow[i].Trim(); | |
string langIdStr = columnName; | |
bool autoTranslation = false; | |
if (columnName.IndexOf(autoPrefix) == 0) | |
{ | |
autoTranslation = true; | |
langIdStr = langIdStr.Substring(autoPrefix.Length); | |
} | |
if (System.Enum.TryParse(langIdStr, false, out langId)) | |
{ | |
map[i] = new ColumnMeta { langId = langId, autoTranslation = autoTranslation }; | |
} | |
} | |
return map; | |
} | |
void parseCsv(string csvContent) | |
{ | |
var splitByLine = splitCsv(csvContent); | |
var columnNames = splitByLine[0]; | |
if (columnNames[0] != "id") | |
throw new UnityException("Wrong file format! Expect id at first column!"); | |
splitByLine.RemoveAt(0); | |
foreach (var splitByComma in splitByLine) | |
{ | |
if (!translationMap.ContainsKey(splitByComma[0])) | |
throw new UnityException("Can't find translation with id \"" + splitByComma[0] + "\" cancel importing"); | |
} | |
var indexMap = getIndexMap(columnNames); | |
int updatedN = 0; | |
foreach (var splitByComma in splitByLine) | |
{ | |
var id = splitByComma[0].Trim(); | |
var translation = translationMap[id]; | |
bool updated = false; | |
for (int i = 2; i < splitByComma.Count; i++) | |
{ | |
var text = splitByComma[i].Trim(); | |
var columnMeta = indexMap[i]; | |
var langId = columnMeta.langId; | |
var oldLocalization = new Translation.Localization(); | |
bool auto = columnMeta.autoTranslation; | |
bool oldLocalizationExists = translation.getRawLocalization(langId, ref oldLocalization); | |
string assetPath = AssetDatabase.GetAssetPath(translation); | |
if (auto) | |
{ | |
if (!oldLocalizationExists || text != oldLocalization.autoText) | |
{ | |
translation.setAutoLocalization(langId, text); | |
Debug.Log(autoPrefix + langId.ToString() + " = " + text + "\n" + assetPath); | |
updated = true; | |
} | |
} | |
else | |
{ | |
if (!oldLocalizationExists || text != oldLocalization.text) | |
{ | |
translation.setManualLocalization(langId, text); | |
Debug.Log(langId.ToString() + " = " + text + "\n" + assetPath); | |
updated = true; | |
} | |
} | |
} | |
if (updated) | |
{ | |
updatedN++; | |
EditorUtility.SetDirty(translation); | |
} | |
} | |
Debug.Log("Imported csv. Total: " + splitByLine.Count + " Updated: " + updatedN); | |
} | |
[ContextMenu("Load CSV")] | |
public void loadCsv() | |
{ | |
string path = EditorUtility.OpenFilePanel("Load csv", "", "csv"); | |
if (path.Length != 0) | |
{ | |
var csvContent = File.ReadAllText(path); | |
parseCsv(csvContent); | |
} | |
} | |
public void updateTranslations() | |
{ | |
Debug.Log("Update translations"); | |
initialList = findAllScriptableObjects<Translation>(); | |
foreach (var translation in initialList) | |
{ | |
var newId = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(translation)); | |
if (translation.uid != newId) | |
{ | |
translation.uid = newId; | |
EditorUtility.SetDirty(translation); | |
} | |
} | |
EditorUtility.SetDirty(this); | |
} | |
List<T> findAllScriptableObjects<T>() where T : ScriptableObject | |
{ | |
List<T> initialPrefabs = new List<T>(); | |
var resources = Resources.FindObjectsOfTypeAll<T>(); | |
foreach (var resource in resources) | |
{ | |
if (AssetDatabase.Contains(resource) && !AssetDatabase.IsSubAsset(resource)) | |
{ | |
initialPrefabs.Add(resource); | |
} | |
} | |
initialPrefabs.Sort((a, b) => AssetDatabase.GetAssetPath(a).CompareTo(AssetDatabase.GetAssetPath(b))); | |
return initialPrefabs; | |
} | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment