From 00a4f34e0068b24e9d9bed2eadfa70c7c3a53323 Mon Sep 17 00:00:00 2001 From: chrisgregan Date: Fri, 3 Apr 2015 20:44:45 +0100 Subject: [PATCH] Language object now uses a CSV text asset property --- .gitignore | 3 +- .../Fungus/Flowchart/Editor/LanguageEditor.cs | 3 + Assets/Fungus/Flowchart/Scripts/Language.cs | 365 ++++++++++-------- Assets/strings.csv | 32 ++ Assets/strings.csv.meta | 8 + 5 files changed, 242 insertions(+), 169 deletions(-) create mode 100644 Assets/strings.csv create mode 100644 Assets/strings.csv.meta diff --git a/.gitignore b/.gitignore index 3ea60107..f9a51964 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ *.unityproj *.sln -*.userprefs \ No newline at end of file +*.userprefs +Assets/.~lock.strings.csv# diff --git a/Assets/Fungus/Flowchart/Editor/LanguageEditor.cs b/Assets/Fungus/Flowchart/Editor/LanguageEditor.cs index 8bf7d121..8f8ba536 100644 --- a/Assets/Fungus/Flowchart/Editor/LanguageEditor.cs +++ b/Assets/Fungus/Flowchart/Editor/LanguageEditor.cs @@ -12,10 +12,12 @@ namespace Fungus public class LanguageEditor : Editor { protected SerializedProperty activeLanguageProp; + protected SerializedProperty localizationFileProp; protected virtual void OnEnable() { activeLanguageProp = serializedObject.FindProperty("activeLanguage"); + localizationFileProp = serializedObject.FindProperty("localizationFile"); } public override void OnInspectorGUI() @@ -25,6 +27,7 @@ namespace Fungus Language t = target as Language; EditorGUILayout.PropertyField(activeLanguageProp); + EditorGUILayout.PropertyField(localizationFileProp); GUILayout.BeginHorizontal(); diff --git a/Assets/Fungus/Flowchart/Scripts/Language.cs b/Assets/Fungus/Flowchart/Scripts/Language.cs index c4b351d0..7f2bc4c6 100644 --- a/Assets/Fungus/Flowchart/Scripts/Language.cs +++ b/Assets/Fungus/Flowchart/Scripts/Language.cs @@ -22,38 +22,38 @@ namespace Fungus /** * Multi-language localization support. */ - public class Language : MonoBehaviour, ISerializationCallbackReceiver + public class Language : MonoBehaviour { /** * Currently active language, usually defined by a two letter language code (e.g DE = German) */ public string activeLanguage = ""; - [SerializeField] - protected List keys; - - [SerializeField] - protected List values; - - // We store the localized strings in a dictionary for easy lookup, but use lists for serialization - // http://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.OnBeforeSerialize.html protected Dictionary localizedStrings = new Dictionary(); /** - * Temp storage for a single item of standard text read from a scene object. + * Temp storage for a single item of standard text and its localizations */ protected class LanguageItem { public string timeStamp; public string description; public string standardText; + public Dictionary localizedStrings = new Dictionary(); } + /** + * CSV file containing localization data + */ + public TextAsset localizationFile; + public virtual void Start() { - if (activeLanguage.Length > 0) + if (activeLanguage.Length > 0 && + localizationFile != null && + localizationFile.text.Length > 0) { - SetActiveLanguage(activeLanguage); + SetActiveLanguage(activeLanguage, localizationFile.text); } } @@ -62,21 +62,32 @@ namespace Fungus */ public virtual string ExportCSV() { - // Build a list of the language codes currently in use - string csvHeader = "Key,Timestamp,Description,Standard"; - List languageCodes = FindLanguageCodes(); - foreach (string languageCode in languageCodes) - { - csvHeader += "," + languageCode; - } - // Collect all the language items present in the scene Dictionary languageItems = FindLanguageItems(); // Update language items with localization data from CSV file + if (localizationFile != null && + localizationFile.text.Length > 0) + { + AddLocalisedStrings(languageItems, localizationFile.text); + } + // Build CSV header row and a list of the language codes currently in use + string csvHeader = "Key,Timestamp,Description,Standard"; + List languageCodes = new List(); + foreach (LanguageItem languageItem in languageItems.Values) + { + foreach (string languageCode in languageItem.localizedStrings.Keys) + { + if (!languageCodes.Contains(languageCode)) + { + languageCodes.Add(languageCode); + csvHeader += "," + languageCode; + } + } + } - // Build the CSV file using collected language items and the corresponding store localized strings + // Build the CSV file using collected language items string csvData = csvHeader + "\n"; foreach (string stringId in languageItems.Keys) { @@ -89,10 +100,9 @@ namespace Fungus foreach (string languageCode in languageCodes) { - string key = stringId + "." + languageCode; - if (localizedStrings.ContainsKey(key)) + if (languageItem.localizedStrings.ContainsKey(languageCode)) { - row += "," + CSVSupport.Escape(localizedStrings[key]); + row += "," + CSVSupport.Escape(languageItem.localizedStrings[languageCode]); } else { @@ -106,69 +116,10 @@ namespace Fungus return csvData; } - /** - * Import strings from a CSV file. - * 1. Any changes to standard text items will be applied to the corresponding scene object. - * 2. Any localized strings will be added to the localization dictionary. - */ - public virtual void ImportCSV(string csvData) - { - // Split into lines - // Excel on Mac exports csv files with \r line endings, so we need to support that too. - string[] lines = csvData.Split('\n', '\r'); - - if (lines.Length == 0) - { - return; - } - - localizedStrings.Clear(); - - // Parse header row - string[] columnNames = CSVSupport.SplitCSVLine(lines[0]); - - for (int i = 1; i < lines.Length; ++i) - { - string line = lines[i]; - - string[] fields = CSVSupport.SplitCSVLine(line); - if (fields.Length < 4) - { - continue; - } - - string stringId = fields[0]; - // Ignore timestamp & notes fields - string standardText = CSVSupport.Unescape(fields[3]); - - PopulateGameString(stringId, standardText); - - // Store localized string in stringDict - for (int j = 4; j < fields.Length; ++j) - { - if (j >= columnNames.Length) - { - continue; - } - string languageCode = columnNames[j]; - string languageEntry = CSVSupport.Unescape(fields[j]); - - if (languageEntry.Length > 0) - { - // The dictionary key is the basic string id with . appended - localizedStrings[stringId + "." + languageCode] = languageEntry; - } - } - } - } - - /** - * Search through the scene - */ protected Dictionary FindLanguageItems() { Dictionary languageItems = new Dictionary(); - + // Export all Say and Menu commands in the scene Flowchart[] flowcharts = GameObject.FindObjectsOfType(); foreach (Flowchart flowchart in flowcharts) @@ -198,7 +149,7 @@ namespace Fungus { continue; } - + LanguageItem languageItem = null; if (languageItems.ContainsKey(stringID)) { @@ -209,7 +160,7 @@ namespace Fungus languageItem = new LanguageItem(); languageItems[stringID] = languageItem; } - + // Update basic properties,leaving localised strings intact languageItem.timeStamp = "10/10/2015"; languageItem.description = "Note"; @@ -217,10 +168,124 @@ namespace Fungus } } } - + return languageItems; } + protected virtual void AddLocalisedStrings(Dictionary languageItems, string csvData) + { + // Split into lines + // Excel on Mac exports csv files with \r line endings, so we need to support that too. + string[] lines = csvData.Split('\n', '\r'); + + if (lines.Length == 0) + { + // Early out if no data in file + return; + } + + // Parse header row + string[] columnNames = CSVSupport.SplitCSVLine(lines[0]); + + for (int i = 1; i < lines.Length; ++i) + { + string line = lines[i]; + + string[] fields = CSVSupport.SplitCSVLine(line); + if (fields.Length < 4) + { + continue; + } + + string stringId = fields[0]; + + // Store localized strings for this string id + for (int j = 4; j < fields.Length; ++j) + { + if (j >= columnNames.Length) + { + continue; + } + string languageCode = columnNames[j]; + string languageEntry = CSVSupport.Unescape(fields[j]); + + if (languageEntry.Length > 0) + { + if (languageItems.ContainsKey(stringId)) + { + languageItems[stringId].localizedStrings[languageCode] = languageEntry; + } + } + } + } + } + + public virtual void SetActiveLanguage(string languageCode, string csvData) + { + if (!Application.isPlaying) + { + // This function should only ever be called when the game is playing (not in editor). + return; + } + + localizedStrings.Clear(); + + // Split into lines + // Excel on Mac exports csv files with \r line endings, so we need to support that too. + string[] lines = csvData.Split('\n', '\r'); + + if (lines.Length == 0) + { + // No data rows in file + return; + } + + // Parse header row + string[] columnNames = CSVSupport.SplitCSVLine(lines[0]); + + if (columnNames.Length < 5) + { + // No languages defined in CSV file + return; + } + + int languageIndex = -1; + for (int i = 4; i < columnNames.Length; ++i) + { + if (columnNames[i] == languageCode) + { + languageIndex = i; + break; + } + } + + if (languageIndex == -1) + { + // Language not found + return; + } + + for (int i = 1; i < lines.Length; ++i) + { + string line = lines[i]; + + string[] fields = CSVSupport.SplitCSVLine(line); + if (fields.Length < languageIndex + 1) + { + continue; + } + + string stringId = fields[0]; + string languageEntry = CSVSupport.Unescape(fields[languageIndex]); + + if (languageEntry.Length > 0) + { + localizedStrings[stringId] = languageEntry; + PopulateGameString(stringId, languageEntry); + } + } + } + public virtual void PopulateGameString(string stringId, string text) { string[] idParts = stringId.Split('.'); @@ -228,7 +293,7 @@ namespace Fungus { return; } - + string stringType = idParts[0]; if (stringType == "SAY") { @@ -236,10 +301,10 @@ namespace Fungus { return; } - + string flowchartName = idParts[1]; int itemId = int.Parse(idParts[2]); - + GameObject go = GameObject.Find(flowchartName); Flowchart flowchart = go.GetComponentInChildren(); if (flowchart != null) @@ -259,7 +324,7 @@ namespace Fungus { return; } - + string flowchartName = idParts[1]; int itemId = int.Parse(idParts[2]); @@ -278,98 +343,62 @@ namespace Fungus } } - public virtual void SetActiveLanguage(string languageCode) + /** + * Import strings from a CSV file. + * 1. Any changes to standard text items will be applied to the corresponding scene object. + * 2. Any localized strings will be added to the localization dictionary. + */ + public virtual void ImportCSV(string csvData) { - // This function should only ever be called when the game is playing (not in editor). - // If it was called in the editor it would permanently modify the text properties in the scene objects. - if (!Application.isPlaying) - { - return; - } + /* + // Split into lines + // Excel on Mac exports csv files with \r line endings, so we need to support that too. + string[] lines = csvData.Split('\n', '\r'); - List languageCodes = FindLanguageCodes(); - if (!languageCodes.Contains(languageCode)) + if (lines.Length == 0) { - Debug.LogWarning("Language code " + languageCode + " not found."); + return; } - // Find all string keys that match the language code and populate the corresponding game object - foreach (string key in localizedStrings.Keys) - { - if (GetLanguageId(key) == languageCode) - { - PopulateGameString(GetStringId(key), localizedStrings[key]); - } - } - } + localizedStrings.Clear(); - public void OnBeforeSerialize() - { - keys.Clear(); - values.Clear(); - foreach (string key in localizedStrings.Keys) - { - string value = localizedStrings[key]; - keys.Add(key); - values.Add(value); - } - } - - public void OnAfterDeserialize() - { - // Both arrays should be the same length, but use the min length just in case - int minCount = Math.Min(keys.Count, values.Count); + // Parse header row + string[] columnNames = CSVSupport.SplitCSVLine(lines[0]); - // Populate the string dict - localizedStrings.Clear(); - for (int i = 0; i < minCount; ++i) + for (int i = 1; i < lines.Length; ++i) { - string key = keys[i]; - string value = values[i]; - localizedStrings[key] = value; - } - } + string line = lines[i]; - protected virtual string GetStringId(string key) - { - int lastDotIndex = key.LastIndexOf("."); - if (lastDotIndex <= 0 || - lastDotIndex == key.Length - 1) - { - // Malformed key - return ""; - } - - return key.Substring(0, lastDotIndex); - } + string[] fields = CSVSupport.SplitCSVLine(line); + if (fields.Length < 4) + { + continue; + } - protected virtual string GetLanguageId(string key) - { - int lastDotIndex = key.LastIndexOf("."); - if (lastDotIndex <= 0 || - lastDotIndex == key.Length - 1) - { - // Malformed key - return ""; - } + string stringId = fields[0]; + // Ignore timestamp & notes fields + string standardText = CSVSupport.Unescape(fields[3]); - return key.Substring(lastDotIndex + 1, key.Length - lastDotIndex - 1); - } + PopulateGameString(stringId, standardText); - protected virtual List FindLanguageCodes() - { - // Build a list of the language codes actually in use - List languageCodes = new List(); - foreach (string key in keys) - { - string languageId = GetLanguageId(key); - if (!languageCodes.Contains(languageId)) + // Store localized string in stringDict + for (int j = 4; j < fields.Length; ++j) { - languageCodes.Add(languageId); + if (j >= columnNames.Length) + { + continue; + } + string languageCode = columnNames[j]; + string languageEntry = CSVSupport.Unescape(fields[j]); + + if (languageEntry.Length > 0) + { + // The dictionary key is the basic string id with . appended + localizedStrings[stringId + "." + languageCode] = languageEntry; + } } } - - return languageCodes; + */ } } diff --git a/Assets/strings.csv b/Assets/strings.csv new file mode 100644 index 00000000..4d3c1a21 --- /dev/null +++ b/Assets/strings.csv @@ -0,0 +1,32 @@ +"Key","Timestamp","Description","Standard","FR","DE","ES" +"SAY.Flowchart.75","10/10/2015","Note","{t}(Do I really want to do this?){/t} Zero","One","Two","Three" +"MENU.Flowchart.76","10/10/2015","Note","Drink the coffee","Five","Six","Seven" +"MENU.Flowchart.77","10/10/2015","Note","Don't drink the coffee",,, +"SAY.Flowchart.0","10/10/2015","Note","{answer}Excellent.",,, +"SAY.Flowchart.1","10/10/2015","Note","All right. It's been 30 minutes. How do you feel?",,, +"SAY.Flowchart.20","10/10/2015","Note","{worried}Like an idiot who should stop encouraging you.",,, +"SAY.Flowchart.21","10/10/2015","Note","No, that's not right.",,, +"SAY.Flowchart.45","10/10/2015","Note","{shout}No nausea? {shout}Dizziness? {shout}Feeling of sudden and impending doom?",,, +"SAY.Flowchart.22","10/10/2015","Note","Wait, {question}{flash=0.1}what?",,, +"SAY.Flowchart.23","10/10/2015","Note","Hmm... I'll have to revise my {clue}hypothesis…",,, +"SAY.Flowchart.2","10/10/2015","Note","No thanks. The last time I drank your coffee, I spent the day running from an imaginary dog.",,, +"SAY.Flowchart.3","10/10/2015","Note","The hallucinogen was in the {answer}gas, not the coffee.",,, +"SAY.Flowchart.4","10/10/2015","Note","{shout}Still not ok{wp},{/wp} Sherlock!",,, +"SAY.Flowchart.5","10/10/2015","Note","Suit yourself.",,, +"SAY.Flowchart.6","10/10/2015","Note","THE EXPERIMENT œ˙é®√","Non","Nein","No" +"SAY.Flowchart.58","10/10/2015","Note","Ah John,\n {pleased}there you are.\nI've been looking everywhere for you!","Mon frere","Mein frere","Bonjo" +"SAY.Flowchart.7","10/10/2015","Note","{confused}I do live here, you know.",,, +"SAY.Flowchart.8","10/10/2015","Note","{answer}{flash=0.1}Well you arrived at just the right time.",,, +"SAY.Flowchart.30","10/10/2015","Note"," Here, I need you to drink this.",,, +"SAY.Flowchart.9","10/10/2015","Note","{worried}... Why?",,, +"SAY.Flowchart.10","10/10/2015","Note","It's for an experiment.",,, +"SAY.Flowchart.35","10/10/2015","Note"," Don't worry. It won't kill you.",,, +"SAY.Flowchart.11","10/10/2015","Note","{clue}Your words inspire such confidence.",,, +"SAY.Flowchart.12","10/10/2015","Note","Why don't you test it yourself?",,, +"SAY.Flowchart.13","10/10/2015","Note","{question}I can't observe the effects of the experiment if I'm the one participating.",,, +"SAY.Flowchart.74","10/10/2015","Note","Of course.",,, +"SAY.Flowchart.80","10/10/2015","Note","(What should I do now?)",,, +"MENU.Flowchart.78","10/10/2015","Note","Talk to Sherlock.",,, +"MENU.Flowchart.79","10/10/2015","Note","Leave",,, +"SAY.Flowchart.66","10/10/2015","Note","Right.... Good luck with that.",,, +"SAY.Flowchart.70","10/10/2015","Note","Your {stat-up}courage{/stat-up} has increased!",,, diff --git a/Assets/strings.csv.meta b/Assets/strings.csv.meta new file mode 100644 index 00000000..3e1be37a --- /dev/null +++ b/Assets/strings.csv.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eb17c0268d57044eb92109b71e847180 +timeCreated: 1428075678 +licenseType: Free +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: