Skip to content

Instantly share code, notes, and snippets.

@mrtrizer
Created July 11, 2020 23:02
Show Gist options
  • Save mrtrizer/9c64ed7d0f6247459bf9c2bbd7c7ae0e to your computer and use it in GitHub Desktop.
Save mrtrizer/9c64ed7d0f6247459bf9c2bbd7c7ae0e to your computer and use it in GitHub Desktop.
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;
}
}
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;
}
}
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
}
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