Browse Source

Language object now uses a CSV text asset property

master
chrisgregan 10 years ago
parent
commit
00a4f34e00
  1. 3
      .gitignore
  2. 3
      Assets/Fungus/Flowchart/Editor/LanguageEditor.cs
  3. 365
      Assets/Fungus/Flowchart/Scripts/Language.cs
  4. 32
      Assets/strings.csv
  5. 8
      Assets/strings.csv.meta

3
.gitignore vendored

@ -8,4 +8,5 @@
*.unityproj
*.sln
*.userprefs
*.userprefs
Assets/.~lock.strings.csv#

3
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();

365
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<string> keys;
[SerializeField]
protected List<string> 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<string, string> localizedStrings = new Dictionary<string, string>();
/**
* 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<string, string> localizedStrings = new Dictionary<string, string>();
}
/**
* 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<string> languageCodes = FindLanguageCodes();
foreach (string languageCode in languageCodes)
{
csvHeader += "," + languageCode;
}
// Collect all the language items present in the scene
Dictionary<string, LanguageItem> 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<string> languageCodes = new List<string>();
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 .<LanguageCode> appended
localizedStrings[stringId + "." + languageCode] = languageEntry;
}
}
}
}
/**
* Search through the scene
*/
protected Dictionary<string, LanguageItem> FindLanguageItems()
{
Dictionary<string, LanguageItem> languageItems = new Dictionary<string, LanguageItem>();
// Export all Say and Menu commands in the scene
Flowchart[] flowcharts = GameObject.FindObjectsOfType<Flowchart>();
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<string, LanguageItem> 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<Flowchart>();
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<string> 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<string> FindLanguageCodes()
{
// Build a list of the language codes actually in use
List<string> languageCodes = new List<string>();
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 .<LanguageCode> appended
localizedStrings[stringId + "." + languageCode] = languageEntry;
}
}
}
return languageCodes;
*/
}
}

32
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!",,,
1 Key Timestamp Description Standard FR DE ES
2 SAY.Flowchart.75 10/10/2015 Note {t}(Do I really want to do this?){/t} Zero One Two Three
3 MENU.Flowchart.76 10/10/2015 Note Drink the coffee Five Six Seven
4 MENU.Flowchart.77 10/10/2015 Note Don't drink the coffee
5 SAY.Flowchart.0 10/10/2015 Note {answer}Excellent.
6 SAY.Flowchart.1 10/10/2015 Note All right. It's been 30 minutes. How do you feel?
7 SAY.Flowchart.20 10/10/2015 Note {worried}Like an idiot who should stop encouraging you.
8 SAY.Flowchart.21 10/10/2015 Note No, that's not right.
9 SAY.Flowchart.45 10/10/2015 Note {shout}No nausea? {shout}Dizziness? {shout}Feeling of sudden and impending doom?
10 SAY.Flowchart.22 10/10/2015 Note Wait, {question}{flash=0.1}what?
11 SAY.Flowchart.23 10/10/2015 Note Hmm... I'll have to revise my {clue}hypothesis…
12 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.
13 SAY.Flowchart.3 10/10/2015 Note The hallucinogen was in the {answer}gas, not the coffee.
14 SAY.Flowchart.4 10/10/2015 Note {shout}Still not ok{wp},{/wp} Sherlock!
15 SAY.Flowchart.5 10/10/2015 Note Suit yourself.
16 SAY.Flowchart.6 10/10/2015 Note THE EXPERIMENT œ˙é®√ Non Nein No
17 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
18 SAY.Flowchart.7 10/10/2015 Note {confused}I do live here, you know.
19 SAY.Flowchart.8 10/10/2015 Note {answer}{flash=0.1}Well you arrived at just the right time.
20 SAY.Flowchart.30 10/10/2015 Note Here, I need you to drink this.
21 SAY.Flowchart.9 10/10/2015 Note {worried}... Why?
22 SAY.Flowchart.10 10/10/2015 Note It's for an experiment.
23 SAY.Flowchart.35 10/10/2015 Note Don't worry. It won't kill you.
24 SAY.Flowchart.11 10/10/2015 Note {clue}Your words inspire such confidence.
25 SAY.Flowchart.12 10/10/2015 Note Why don't you test it yourself?
26 SAY.Flowchart.13 10/10/2015 Note {question}I can't observe the effects of the experiment if I'm the one participating.
27 SAY.Flowchart.74 10/10/2015 Note Of course.
28 SAY.Flowchart.80 10/10/2015 Note (What should I do now?)
29 MENU.Flowchart.78 10/10/2015 Note Talk to Sherlock.
30 MENU.Flowchart.79 10/10/2015 Note Leave
31 SAY.Flowchart.66 10/10/2015 Note Right.... Good luck with that.
32 SAY.Flowchart.70 10/10/2015 Note Your {stat-up}courage{/stat-up} has increased!

8
Assets/strings.csv.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eb17c0268d57044eb92109b71e847180
timeCreated: 1428075678
licenseType: Free
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:
Loading…
Cancel
Save