Created
October 11, 2023 20:41
-
-
Save leventeren/c55ea11338ea8c4d676d71004f67d4ff to your computer and use it in GitHub Desktop.
Translator
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 System.Linq; | |
using UnityEngine; | |
[System.Serializable] | |
public struct SerializedDictionary<T, U> { | |
public List<T> Keys; | |
public List<U> Values; | |
public Dictionary<T, U> getDictionary() | |
{ | |
Dictionary<T, U> res = new Dictionary<T, U>(); | |
for (int i = 0; i < Keys.Count; i++) | |
{ | |
res[Keys[i]] = Values[i]; | |
} | |
return res; | |
} | |
public SerializedDictionary(Dictionary<T, U> dict) | |
{ | |
Keys = dict.Keys.ToList(); | |
Values = dict.Values.ToList(); | |
} | |
public static implicit operator Dictionary<T, U>(SerializedDictionary<T, U> test) | |
{ | |
return test.getDictionary(); | |
} | |
public static implicit operator SerializedDictionary<T, U>(Dictionary<T, U> test) | |
{ | |
return new SerializedDictionary<T,U>(test); | |
} | |
} | |
[System.Serializable] | |
public struct SerializedVector | |
{ | |
public float x, y, z; | |
public Vector3 GetPos() | |
{ | |
return new Vector3(x, y, z); | |
} | |
public SerializedVector(Vector3 v) | |
{ | |
x = v.x; | |
y = v.y; | |
z = v.z; | |
} | |
public static implicit operator Vector3(SerializedVector test) | |
{ | |
return test.GetPos(); | |
} | |
public static implicit operator SerializedVector(Vector3 test) | |
{ | |
return new SerializedVector(test); | |
} | |
} | |
[System.Serializable] | |
public class CSSerializedObject | |
{ | |
public float serializationTime; | |
public int version = 0; | |
public bool isValid = false; | |
} | |
public class SerializableObject : MonoBehaviour | |
{ | |
public virtual void Save(SerializedGame save) | |
{ | |
} | |
public virtual void Load(SerializedGame save) | |
{ | |
} | |
} |
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; | |
/// <summary> | |
/// Inherit from this base class to create a singleton. | |
/// e.g. public class MyClassName : Singleton<MyClassName> {} | |
/// </summary> | |
public class Singleton<T> : SerializableObject where T : MonoBehaviour | |
{ | |
// Check to see if we're about to be destroyed. | |
private static bool m_ShuttingDown = false; | |
private static object m_Lock = new object(); | |
private static T m_Instance; | |
private void Awake() | |
{ | |
if (Instance != null && Instance != this) | |
{ | |
Destroy(this.gameObject); | |
} | |
//else | |
//{ | |
// m_Instance = this; | |
//} | |
} | |
/// <summary> | |
/// Access singleton instance through this propriety. | |
/// </summary> | |
public static T Instance | |
{ | |
get | |
{ | |
//if (m_ShuttingDown) | |
//{ | |
// Debug.LogWarning("[Singleton] Instance '" + typeof(T) + | |
// "' already destroyed. Returning null."); | |
// return null; | |
//} | |
lock (m_Lock) | |
{ | |
if (m_Instance == null) | |
{ | |
// Search for existing instance. | |
m_Instance = (T)FindObjectOfType(typeof(T)); | |
// Create new instance if one doesn't already exist. | |
if (m_Instance == null) | |
{ | |
// Need to create a new GameObject to attach the singleton to. | |
var singletonObject = new GameObject(); | |
m_Instance = singletonObject.AddComponent<T>(); | |
singletonObject.name = typeof(T).ToString() + " (Singleton)"; | |
// Make instance persistent. | |
// DontDestroyOnLoad(singletonObject); | |
} | |
} | |
return m_Instance; | |
} | |
} | |
} | |
private void OnApplicationQuit() | |
{ | |
m_ShuttingDown = true; | |
} | |
private void OnDestroy() | |
{ | |
m_ShuttingDown = true; | |
m_Instance = null; | |
} | |
} |
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
// TermData.cs | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
// Obtains the ( audio / picture / translation ) that corresponds to a given term | |
public static class TermData | |
{ | |
public class Terms | |
{ | |
// < language, index of a non-English language's term within a array-value of termTranslations > | |
// English is paired to -1 to convey that it is the key and not within termTranslations's array-value | |
public Dictionary<string, int> languageIndicies; | |
// < englishText, [frenchText, italianText, ...] > | |
// Example: < "Nope.", ["Nan.", "No.", "Nee."..] > | |
public Dictionary<string, string[]> termTranslations; | |
public Terms() | |
{ | |
languageIndicies = new Dictionary<string, int>(); | |
termTranslations = new Dictionary<string, string[]>(); | |
} | |
} | |
} |
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
// TranslateText.cs | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.UI; | |
using TMPro; | |
[DisallowMultipleComponent] | |
//[RequireComponent(typeof(Text),typeof(TMP_Text))] | |
// Place on all t:TextMeshProUGUI or t:Text | |
public class TranslateText : MonoBehaviour | |
{ | |
bool init = false; | |
string englishText; | |
int lastLanguageUpdated = -1; | |
Text txtDisplay; | |
TMP_Text txtDisplay_TMP; | |
void Start() | |
{ | |
Init(); | |
UpdateTextDisplay(); | |
} | |
private void OnEnable() | |
{ | |
if (init) | |
UpdateTextDisplay(); | |
} | |
public void SetEnglishText(string newText) | |
{ | |
Init(); | |
englishText = newText; | |
if (txtDisplay_TMP) | |
{ | |
txtDisplay_TMP.text = englishText; | |
} | |
else | |
{ | |
txtDisplay.text = englishText; | |
} | |
lastLanguageUpdated = -1; | |
UpdateTextDisplay(); | |
} | |
void Init() | |
{ | |
if (init) | |
return; | |
txtDisplay = GetComponent<Text>(); | |
txtDisplay_TMP = GetComponent<TMP_Text>(); | |
if (!txtDisplay && !txtDisplay_TMP) | |
{ | |
Debug.LogError(" no text! "+name); | |
} | |
// UnityEngine.Assertions.Assert.IsNotNull(txtDisplay); | |
if (txtDisplay_TMP) | |
{ | |
englishText = txtDisplay_TMP.text; | |
} | |
else | |
{ | |
englishText = txtDisplay.text; | |
} | |
lastLanguageUpdated = -1; | |
init = true; | |
Translator.Instance.DisplayLanguageChanged += UpdateTextDisplay; | |
} | |
internal void ManuallySetTextToLoading(int displayLanguage) | |
{ | |
Init(); | |
string[] loadingTranslations = {"Loading...", "Chargement...", "Caricamento in corso...", "Wird geladen...", | |
"Cargando...", "载入中...", "Loading...", "Carregando..."}; | |
int langIndex = displayLanguage + 1; | |
UnityEngine.Assertions.Assert.IsTrue(langIndex >= 0 && langIndex < loadingTranslations.Length); | |
if (txtDisplay_TMP) | |
{ | |
txtDisplay_TMP.text = loadingTranslations[langIndex]; | |
} | |
else | |
{ | |
txtDisplay.text = loadingTranslations[langIndex]; | |
} | |
} | |
void UpdateTextDisplay() | |
{ | |
if (this == null) | |
return; | |
Init(); | |
Translator translator = Translator.Instance; | |
int displayLanguage = translator.GetUserDisplayLanguageIndex(); | |
if (displayLanguage == lastLanguageUpdated) | |
return; | |
lastLanguageUpdated = displayLanguage; | |
if (translator.IsDisplayingEnglish()) | |
{ | |
if (txtDisplay_TMP) | |
{ | |
txtDisplay_TMP.text = englishText; | |
} | |
else | |
{ | |
txtDisplay.text = englishText; | |
} | |
} | |
else | |
{ | |
string translatedText = translator.Translate(englishText.Trim(), displayLanguage); | |
UnityEngine.Assertions.Assert.IsNotNull(translatedText); | |
if (txtDisplay_TMP) | |
{ | |
UnityEngine.Assertions.Assert.IsNotNull(txtDisplay_TMP, gameObject.name); | |
txtDisplay_TMP.text = translatedText; | |
} | |
else | |
{ | |
UnityEngine.Assertions.Assert.IsNotNull(txtDisplay, gameObject.name); | |
txtDisplay.text = translatedText; | |
} | |
} | |
} | |
} |
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
English | 中文 | |
---|---|---|
en | zh | |
Continue | 继续 | |
New Game | 新游戏 | |
Support Us | 支持我们 | |
Settings | 设置 | |
Credits | 制作人员 |
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
// TranslationLoader.cs | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using UnityEngine; | |
using UnityEngine.Assertions; | |
public class TranslationLoader : Singleton<TranslationLoader> | |
{ | |
private int progress = 0; | |
List<string> languages = new List<string>(); | |
System.Action<string> callbackOnCompleted; | |
private void Awake() | |
{ | |
System.Action<string> localCallback = new System.Action<string>(delegate (string error) | |
{ | |
Debug.Log("loaded translation"); | |
//if (error == null) | |
//{ | |
// ServiceLocator.instance.GetService<Translator>().SetDisplayLanguage(index); | |
// if (ServiceLocator.instance.GetService<Settings>().collectAllDialogueTermData) | |
// ServiceLocator.instance.GetService<TranslationLoader>().DebugCollectAllDialogueTermData(); | |
//} | |
//else | |
//{ | |
// Debug.LogError("Langauge set failed: " + error); | |
//} | |
//if (globalCallback != null) | |
// globalCallback(error); | |
}); | |
Load(localCallback); | |
} | |
// Returns an error string (if any) | |
public void Load(System.Action<string> callback) | |
{ | |
callbackOnCompleted = callback; | |
AfterDownload(Resources.Load<TextAsset>("translation").text); | |
// StartCoroutine(CSVDownloader.DownloadData(AfterDownload)); | |
} | |
public void AfterDownload(string data) | |
{ | |
if (string.IsNullOrEmpty(data)) | |
{ | |
Debug.LogError("Was not able to download data or retrieve stale data."); | |
// TODO: Display a notification that this is likely due to poor internet connectivity | |
} | |
else | |
{ | |
ProcessData(data, AfterProcessData); | |
} | |
} | |
private void AfterProcessData(string errorMessage) | |
{ | |
if (null != errorMessage) | |
{ | |
Debug.LogError("Was not able to process data: " + errorMessage); | |
} | |
UnityEngine.Assertions.Assert.IsNotNull(callbackOnCompleted); | |
callbackOnCompleted(errorMessage); | |
} | |
public void ProcessData(string data, System.Action<string> onCompleted) | |
{ | |
Translator.Instance.termData = new TermData.Terms(); | |
// Line level | |
int currLineIndex = 0; | |
bool inQuote = false; | |
int linesSinceUpdate = 0; | |
int kLinesBetweenUpdate = 15; | |
// Entry level | |
string currEntry = ""; | |
int currCharIndex = 0; | |
bool currEntryContainedQuote = false; | |
List<string> currLineEntries = new List<string>(); | |
// "\r\n" means end of line and should be only occurence of '\r' (unless on macOS/iOS in which case lines ends with just \n) | |
char lineEnding = '\r';// Utils.IsIOS() ? '\n' : '\r'; | |
int lineEndingLength = 2; //Utils.IsIOS() ? 1 : 2; | |
while (currCharIndex < data.Length) | |
{ | |
if (!inQuote && (data[currCharIndex] == lineEnding)) | |
{ | |
// Skip the line ending | |
currCharIndex += lineEndingLength; | |
// Wrap up the last entry | |
// If we were in a quote, trim bordering quotation marks | |
if (currEntryContainedQuote) | |
{ | |
currEntry = currEntry.Substring(1, currEntry.Length - 2); | |
} | |
currLineEntries.Add(currEntry); | |
currEntry = ""; | |
currEntryContainedQuote = false; | |
// Line ended | |
ProcessLineFromCSV(currLineEntries, currLineIndex); | |
currLineIndex++; | |
currLineEntries = new List<string>(); | |
linesSinceUpdate++; | |
if (linesSinceUpdate > kLinesBetweenUpdate) | |
{ | |
linesSinceUpdate = 0; | |
} | |
} | |
else | |
{ | |
if (data[currCharIndex] == '"') | |
{ | |
inQuote = !inQuote; | |
currEntryContainedQuote = true; | |
} | |
// Entry level stuff | |
{ | |
if (data[currCharIndex] == ',') | |
{ | |
if (inQuote) | |
{ | |
currEntry += data[currCharIndex]; | |
} | |
else | |
{ | |
// If we were in a quote, trim bordering quotation marks | |
if (currEntryContainedQuote) | |
{ | |
currEntry = currEntry.Substring(1, currEntry.Length - 2); | |
} | |
currLineEntries.Add(currEntry); | |
currEntry = ""; | |
currEntryContainedQuote = false; | |
} | |
} | |
else if(data[currCharIndex] == '\\') | |
{ | |
currEntry += '\n'; | |
currCharIndex++; | |
} | |
else | |
{ | |
currEntry += data[currCharIndex]; | |
} | |
} | |
currCharIndex++; | |
} | |
progress = (int)((float)currCharIndex / data.Length * 100.0f); | |
} | |
onCompleted(null); | |
} | |
// Extracts translation data from a CSV line into Manager.instance.translator.termData.termTranslations | |
private void ProcessLineFromCSV(List<string> currLineElements, int currLineIndex) | |
{ | |
Translator translator = Translator.Instance; | |
// This line contains the column headers, telling us which languages are in which column | |
if (currLineIndex == 0) | |
{ | |
languages = new List<string>(); | |
for (int columnIndex = 0; columnIndex < currLineElements.Count; columnIndex++) | |
{ | |
string currLanguage = currLineElements[columnIndex]; | |
Assert.IsTrue((columnIndex != 0 || currLanguage == "English"), "First column first row was:" + currLanguage); | |
Assert.IsFalse(translator.termData.languageIndicies.ContainsKey(currLanguage)); | |
languages.Add(currLanguage); | |
// English is given a -1 index to convey it is the key of termTranslations rather than within its array-value | |
translator.termData.languageIndicies.Add(currLanguage, columnIndex - 1); | |
} | |
UnityEngine.Assertions.Assert.IsFalse(languages.Count == 0); | |
} | |
// This is a normal node | |
else if (currLineElements.Count > 1) | |
{ | |
string englishSpelling = null; | |
string[] nonEnglishSpellings = new string[languages.Count - 1]; | |
for (int columnIndex = 0; columnIndex < currLineElements.Count; columnIndex++) | |
{ | |
string currentTerm = currLineElements[columnIndex]; | |
if (columnIndex == 0) | |
{ | |
Assert.IsFalse(translator.termData.termTranslations.ContainsKey(currentTerm), "Saw this term twice: " + currentTerm); | |
// Note: optionally compare with case insensitivity | |
englishSpelling = currentTerm; | |
} | |
else | |
{ | |
nonEnglishSpellings[columnIndex - 1] = currentTerm; | |
} | |
} | |
translator.termData.termTranslations[englishSpelling] = nonEnglishSpellings; | |
} | |
else | |
{ | |
Debug.LogError("Database line did not fall into one of the expected categories."); | |
} | |
} | |
//public void DebugCollectAllDialogueTermData() | |
//{ | |
// List<string> dialogueNames = DebugGetAllDialogueNames(); | |
// foreach (string dialogue in dialogueNames) | |
// { | |
// var twineText = Resources.Load<TextAsset>("Conversations/" + dialogue); | |
// UnityEngine.Assertions.Assert.IsNotNull(twineText); | |
// var curDialogue = new DialogueObject.Dialogue(twineText.text); | |
// curDialogue.DebugTranslateAllNodes(); | |
// } | |
//} | |
//private List<string> DebugGetAllDialogueNames() | |
//{ | |
// List<string> names = new List<string>(); | |
// string assetsFolder = Application.dataPath; | |
// DirectoryInfo dir = new DirectoryInfo(assetsFolder + "/Resources/Conversations/"); | |
// FileInfo[] info = dir.GetFiles("*.txt"); | |
// foreach (FileInfo f in info) | |
// { | |
// names.Add(f.Name.Substring(0, f.Name.IndexOf("."))); | |
// } | |
// return names; | |
//} | |
} |
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
// Translator.cs | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using UnityEngine; | |
public class Translator : Singleton<Translator> | |
{ | |
public static readonly string kUnTranslatedTermsListPath = "unTranslatedTermsList.txt"; | |
public delegate void OnDisplayLanguageChangedHandler(); | |
public event OnDisplayLanguageChangedHandler DisplayLanguageChanged = delegate { }; | |
public TermData.Terms termData; | |
private const int kEnglishIndex = -1; | |
private int currentLanguageIndex = -1;// kEnglishIndex; | |
private string unTranslatedTerms = ""; | |
public int GetUserDisplayLanguageIndex() | |
{ | |
return currentLanguageIndex; | |
} | |
public bool IsDisplayingEnglish() | |
{ | |
return currentLanguageIndex == kEnglishIndex; | |
} | |
public void InvokeDisplayLanguageChanged() | |
{ | |
DisplayLanguageChanged(); | |
} | |
//public void EnsureLoadThenSetDisplayLanguage(int index, Action<string> globalCallback) | |
//{ | |
// System.Action<string> localCallback = new System.Action<string>(delegate (string error) | |
// { | |
// if (error == null) | |
// { | |
// ServiceLocator.instance.GetService<Translator>().SetDisplayLanguage(index); | |
// if (ServiceLocator.instance.GetService<Settings>().collectAllDialogueTermData) | |
// ServiceLocator.instance.GetService<TranslationLoader>().DebugCollectAllDialogueTermData(); | |
// } | |
// else | |
// { | |
// Debug.LogError("Langauge set failed: " + error); | |
// } | |
// if (globalCallback != null) | |
// globalCallback(error); | |
// }); | |
// ServiceLocator.instance.GetService<Translator>().EnsureTranslationsLoaded(localCallback); | |
//} | |
private void SetDisplayLanguage(int index) | |
{ | |
PlayerPrefs.SetInt("displayLanguage", index); | |
UnityEngine.Assertions.Assert.IsTrue(termData != null); | |
currentLanguageIndex = index; | |
DisplayLanguageChanged(); | |
} | |
public bool NeedsLoad() | |
{ | |
return termData == null; | |
} | |
// Returns an error string (if any) | |
//public void EnsureTranslationsLoaded(Action<string> callback) | |
//{ | |
// if (termData == null) | |
// ServiceLocator.instance.GetService<TranslationLoader>().Load(callback); | |
// else | |
// callback(null); | |
//} | |
// Deprecated | |
private void SetDisplayLanguage(string selectedLanguage) | |
{ | |
UnityEngine.Assertions.Assert.IsTrue(termData.languageIndicies.ContainsKey(selectedLanguage)); | |
int chosenIndex = termData.languageIndicies[selectedLanguage]; | |
SetDisplayLanguage(chosenIndex); | |
} | |
internal string Translate(string englishText, int targetLanguageIndex) | |
{ | |
if (string.IsNullOrEmpty(englishText)) | |
return ""; | |
if (englishText == "\"\"") | |
return englishText; | |
if (englishText.Length < 2) | |
return englishText; | |
if (englishText.StartsWith("<")) | |
{ | |
//Debug.LogWarning("Ignoring translation request for: " + englishText); | |
return englishText; | |
} | |
//if (englishText.Contains("\n")) | |
//UnityEngine.Assertions.Assert.IsTrue(true, "Term included line breaks: '" + englishText + "'"); | |
// Ignore translation requests that come before the termData has loaded | |
if (termData == null) | |
return englishText; | |
// Note: optionally compare with case insensitivity | |
string englishTextLower = englishText; | |
if (termData.termTranslations.ContainsKey(englishTextLower)) | |
{ | |
if (targetLanguageIndex == kEnglishIndex) | |
return englishText; | |
bool inBounds = termData.termTranslations[englishTextLower].Length > targetLanguageIndex && targetLanguageIndex >= 0; | |
UnityEngine.Assertions.Assert.IsTrue(inBounds); | |
return termData.termTranslations[englishTextLower][targetLanguageIndex]; | |
} | |
else | |
{ | |
//remove later | |
Debug.LogWarning("No translation for: '" + englishTextLower + "'"); | |
AddStringToListOfTermsToTranslate(englishTextLower); | |
//englishTextLower = "<<" + englishTextLower + ">>"; | |
return englishTextLower; | |
} | |
} | |
internal string Translate(string englishText) | |
{ | |
return Translate(englishText, currentLanguageIndex); | |
} | |
internal string GetDisplayLanguage() | |
{ | |
foreach (var lang in termData.languageIndicies.Keys) | |
{ | |
if (termData.languageIndicies[lang] == currentLanguageIndex) | |
return lang; | |
} | |
Debug.LogError("Display language not found"); | |
return ""; | |
} | |
void AddStringToListOfTermsToTranslate(string term) | |
{ | |
if (term.Length < 2) | |
return; | |
UnityEngine.Assertions.Assert.IsFalse(termData.termTranslations.ContainsKey(term)); | |
string unTranslatedTermsListPath = Path.Combine(Application.persistentDataPath, kUnTranslatedTermsListPath); | |
try | |
{ | |
if (!File.Exists(unTranslatedTermsListPath)) | |
File.Create(unTranslatedTermsListPath); | |
if (string.IsNullOrEmpty(unTranslatedTerms)) | |
unTranslatedTerms = File.ReadAllText(unTranslatedTermsListPath); | |
if (!unTranslatedTerms.Contains(term)) | |
{ | |
unTranslatedTerms += term; | |
File.AppendAllText(unTranslatedTermsListPath, term + System.Environment.NewLine); | |
} | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError("Could not write untranslated term:" + e.Message); | |
} | |
} | |
internal string[] SupportedLanguages() | |
{ | |
string[] keys = new string[termData.languageIndicies.Keys.Count]; | |
termData.languageIndicies.Keys.CopyTo(keys, 0); | |
return keys; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment