|
|
|
@ -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; |
|
|
|
|
*/ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|