Browse Source

Merge branch 'Localization'

master
chrisgregan 10 years ago
parent
commit
b9b4b779e4
  1. 3
      .gitignore
  2. 0
      Assets/Fungus/Flowchart/Editor/BlockEditor.cs
  3. 0
      Assets/Fungus/Flowchart/Editor/BlockEditor.cs.meta
  4. 0
      Assets/Fungus/Flowchart/Editor/BlockInspector.cs
  5. 0
      Assets/Fungus/Flowchart/Editor/BlockInspector.cs.meta
  6. 11
      Assets/Fungus/Flowchart/Editor/FlowchartEditor.cs
  7. 187
      Assets/Fungus/Flowchart/Editor/FountainExporter.cs
  8. 18
      Assets/Fungus/Flowchart/Scripts/Flowchart.cs
  9. 96
      Assets/Fungus/Flowchart/Scripts/StringsParser.cs
  10. 106
      Assets/Fungus/Narrative/Editor/LocalizationEditor.cs
  11. 4
      Assets/Fungus/Narrative/Editor/LocalizationEditor.cs.meta
  12. 4
      Assets/Fungus/Narrative/Editor/MenuEditor.cs
  13. 6
      Assets/Fungus/Narrative/Editor/NarrativeMenuItems.cs
  14. 6
      Assets/Fungus/Narrative/Editor/SayEditor.cs
  15. 54
      Assets/Fungus/Narrative/Resources/Localization.prefab
  16. 8
      Assets/Fungus/Narrative/Resources/Localization.prefab.meta
  17. 7
      Assets/Fungus/Narrative/Scripts/Character.cs
  18. 5
      Assets/Fungus/Narrative/Scripts/Commands/Menu.cs
  19. 7
      Assets/Fungus/Narrative/Scripts/Commands/Say.cs
  20. 35
      Assets/Fungus/Narrative/Scripts/Commands/SetLanguage.cs
  21. 6
      Assets/Fungus/Narrative/Scripts/Commands/SetLanguage.cs.meta
  22. 601
      Assets/Fungus/Narrative/Scripts/Localization.cs
  23. 12
      Assets/Fungus/Narrative/Scripts/Localization.cs.meta
  24. 9
      Assets/Fungus/Thirdparty/CSVParser.meta
  25. 48
      Assets/Fungus/Thirdparty/CSVParser/CSVSupport.cs
  26. 12
      Assets/Fungus/Thirdparty/CSVParser/CSVSupport.cs.meta
  27. 218
      Assets/Fungus/Thirdparty/CSVParser/CsvParser.cs
  28. 12
      Assets/Fungus/Thirdparty/CSVParser/CsvParser.cs.meta
  29. 281
      Assets/FungusExamples/Sherlock/TheExperiment.unity

3
.gitignore vendored

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

0
Assets/Fungus/Flowchart/Editor/SequenceEditor.cs → Assets/Fungus/Flowchart/Editor/BlockEditor.cs

0
Assets/Fungus/Flowchart/Editor/SequenceEditor.cs.meta → Assets/Fungus/Flowchart/Editor/BlockEditor.cs.meta

0
Assets/Fungus/Flowchart/Editor/SequenceInspector.cs → Assets/Fungus/Flowchart/Editor/BlockInspector.cs

0
Assets/Fungus/Flowchart/Editor/SequenceInspector.cs.meta → Assets/Fungus/Flowchart/Editor/BlockInspector.cs.meta

11
Assets/Fungus/Flowchart/Editor/FlowchartEditor.cs

@ -23,6 +23,7 @@ namespace Fungus
protected SerializedProperty hideComponentsProp;
protected SerializedProperty runSlowDurationProp;
protected SerializedProperty saveSelectionProp;
protected SerializedProperty localizationIdProp;
protected SerializedProperty variablesProp;
protected virtual void OnEnable()
@ -32,6 +33,7 @@ namespace Fungus
hideComponentsProp = serializedObject.FindProperty("hideComponents");
runSlowDurationProp = serializedObject.FindProperty("runSlowDuration");
saveSelectionProp = serializedObject.FindProperty("saveSelection");
localizationIdProp = serializedObject.FindProperty("localizationId");
variablesProp = serializedObject.FindProperty("variables");
}
@ -48,6 +50,7 @@ namespace Fungus
EditorGUILayout.PropertyField(hideComponentsProp);
EditorGUILayout.PropertyField(runSlowDurationProp);
EditorGUILayout.PropertyField(saveSelectionProp);
EditorGUILayout.PropertyField(localizationIdProp);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
@ -55,14 +58,6 @@ namespace Fungus
{
EditorWindow.GetWindow(typeof(FlowchartWindow), false, "Flowchart");
}
if (GUILayout.Button(new GUIContent("Export Text", "Export all story text in .fountain format.")))
{
FountainExporter.ExportStrings(flowchart);
}
if (GUILayout.Button(new GUIContent("Import Text", "Import story text from a file in .fountain format.")))
{
FountainExporter.ImportStrings(flowchart);
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();

187
Assets/Fungus/Flowchart/Editor/FountainExporter.cs

@ -1,187 +0,0 @@
using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;
namespace Fungus
{
/**
* Import and export a Fungus story in the .fountain screenplay format.
* The exported file contains special tags in note blocks which map the
* story text to the corresponding commands.
*/
public class FountainExporter
{
public static void ExportStrings(Flowchart flowchart)
{
if (flowchart == null)
{
return;
}
string path = EditorUtility.SaveFilePanel("Export strings", "",
flowchart.name + ".txt", "");
if(path.Length == 0)
{
return;
}
// Write out character names
string exportText = "Title: " + flowchart.name + "\n";
exportText += "Draft date: " + System.DateTime.Today.ToString("d") + "\n";
exportText += "\n";
// In every block, write out Say & Menu text in order
Block[] blocks = flowchart.GetComponentsInChildren<Block>();
foreach (Block block in blocks)
{
// Check for any Say, Menu or Comment commands
bool hasText = false;
foreach (Command c in block.commandList)
{
System.Type t = c.GetType();
if (t == typeof(Say) ||
t == typeof(Menu) ||
t == typeof(Comment))
{
hasText = true;
}
}
if (!hasText)
{
continue;
}
exportText += "." + block.blockName.ToUpper() + "\n\n";
foreach (Command c in block.commandList)
{
if (c.GetType() == typeof(Say))
{
string idText = "";
Say say = c as Say;
if (say.character == null)
{
exportText += "NO CHARACTER\n";
}
else
{
exportText += say.character.nameText.ToUpper() + "\n";
}
idText += "[[Say," + c.itemId + "]]\n";
exportText += idText;
// Fountain requires blank dialogue lines to contain 2 spaces or else
// they will be interpreted as ACTION text.
string trimmedText = say.storyText.Trim();
string[] lines = trimmedText.Split(new [] { '\r', '\n' });
foreach (string line in lines)
{
string trimmed = line.Trim();
if (line.Length == 0)
{
exportText += " \n";
}
else
{
exportText += trimmed + "\n";
}
}
exportText += "\n";
}
else if (c.GetType() == typeof(Menu))
{
exportText += "MENU\n";
string idText = "";
Menu menu = c as Menu;
idText += "[[Menu," + c.itemId + "]]\n";
exportText += idText + menu.text.Trim() + "\n\n";
}
else if (c.GetType() == typeof(Comment))
{
string idText = "";
Comment comment = c as Comment;
idText += "[[Comment," + c.itemId + "]]\n";
exportText += idText + comment.commentText.Trim() + "\n\n";
}
}
}
File.WriteAllText(path, exportText);
}
public static void ImportStrings(Flowchart flowchart)
{
string path = EditorUtility.OpenFilePanel("Import strings", "", "");
if(path.Length == 0)
{
return;
}
string stringsFile = File.ReadAllText(path);
StringsParser parser = new StringsParser();
List<StringsParser.StringItem> items = parser.ProcessText(stringsFile);
// Build dict of commands
Dictionary<int, Command> commandDict = new Dictionary<int, Command>();
foreach (Command c in flowchart.gameObject.GetComponentsInChildren<Command>())
{
commandDict.Add (c.itemId, c);
}
foreach (StringsParser.StringItem item in items)
{
if (item.parameters.Length != 2)
{
continue;
}
string stringType = item.parameters[0];
if (stringType == "Say")
{
int itemId = int.Parse(item.parameters[1]);
Say sayCommand = commandDict[itemId] as Say;
if (sayCommand != null)
{
sayCommand.storyText = item.bodyText;
}
}
else if (stringType == "Menu")
{
int itemId = int.Parse(item.parameters[1]);
Menu menuCommand = commandDict[itemId] as Menu;
if (menuCommand != null)
{
menuCommand.text = item.bodyText;
}
}
else if (stringType == "Comment")
{
int itemId = int.Parse(item.parameters[1]);
Comment commentCommand = commandDict[itemId] as Comment;
if (commentCommand != null)
{
commentCommand.commentText = item.bodyText;
}
}
}
}
}
}

18
Assets/Fungus/Flowchart/Scripts/Flowchart.cs

@ -109,6 +109,12 @@ namespace Fungus
[Tooltip("Saves the selected block and commands when saving the scene.")]
public bool saveSelection = true;
/**
* Unique identifier for identifying this flowchart in localized string keys.
*/
[Tooltip("Unique identifier for this flowchart in localized string keys. This id must be provided for localization string export to work.")]
public string localizationId = "";
/**
* Unique id to assign to the next created item.
* Increases monotonically every time a new item is added to the Flowchart.
@ -672,15 +678,25 @@ namespace Fungus
foreach (Match match in results)
{
string key = match.Value.Substring(2, match.Value.Length - 3);
// Look for matching variable first
foreach (Variable variable in variables)
{
if (variable.key == key)
{
string value = variable.ToString();
subbedText = subbedText.Replace(match.Value, value);
break;
return subbedText;
}
}
// Next look for matching localized string
string localizedString = Localization.GetLocalizedString(key);
if (localizedString != null)
{
subbedText = subbedText.Replace(match.Value, localizedString);
return subbedText;
}
}
return subbedText;

96
Assets/Fungus/Flowchart/Scripts/StringsParser.cs

@ -1,96 +0,0 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Fungus;
namespace Fungus
{
/**
* Parses an exported strings file using the Fountain file format for screenplays
* See http://fountain.io for details.
* We only support a small subset of Fountain markup, and use note tags to embed meta data to
* bind dialogue text to the corresponding Say / Menu commands.
*/
public class StringsParser
{
public class StringItem
{
public string[] parameters;
public string bodyText;
}
public virtual List<StringItem> ProcessText(string text)
{
List<StringItem> items = new List<StringItem>();
// Split text into lines. Add a newline at end to ensure last command is always parsed
string[] lines = Regex.Split(text + "\n", "(?<=\n)");
int i = 0;
while (i < lines.Length)
{
string line = lines[i].Trim();
if (!(line.StartsWith("[[") && line.EndsWith("]]")))
{
i++;
continue;
}
string blockTag = line.Substring(2, line.Length - 4);
// Find next empty line, #, [[ or eof
int start = i + 1;
int end = lines.Length - 1;
for (int j = start; j <= end; ++j)
{
string line2 = lines[j].Trim();
if (line2.Length == 0 ||
line2.StartsWith("#") ||
line2.StartsWith("[["))
{
end = j;
break;
}
}
if (end > start)
{
string blockBuffer = "";
for (int j = start; j <= end; ++j)
{
blockBuffer += lines[j].Trim() + "\n";
}
blockBuffer = blockBuffer.Trim();
StringItem item = CreateItem(blockTag, blockBuffer);
if (item != null)
{
items.Add(item);
}
}
i = end + 1;
}
return items;
}
protected StringItem CreateItem(string commandInfo, string bodyText)
{
string[] parameters = commandInfo.Split(new char[] { ',' });
if (parameters.Length > 0)
{
StringItem item = new StringItem();
item.parameters = parameters;
item.bodyText = bodyText;
return item;
}
return null;
}
}
}

106
Assets/Fungus/Narrative/Editor/LocalizationEditor.cs

@ -0,0 +1,106 @@
using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Rotorz.ReorderableList;
namespace Fungus
{
[CustomEditor(typeof(Localization))]
public class LocalizationEditor : Editor
{
protected SerializedProperty activeLanguageProp;
protected SerializedProperty localizationFileProp;
protected virtual void OnEnable()
{
activeLanguageProp = serializedObject.FindProperty("activeLanguage");
localizationFileProp = serializedObject.FindProperty("localizationFile");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
Localization localization = target as Localization;
EditorGUILayout.PropertyField(activeLanguageProp);
EditorGUILayout.PropertyField(localizationFileProp);
if (GUILayout.Button(new GUIContent("Export Localization File")))
{
ExportLocalizationFile(localization);
}
if (GUILayout.Button(new GUIContent("Export Standard Text")))
{
ExportStandardText(localization);
}
if (GUILayout.Button(new GUIContent("Import Standard Text")))
{
ImportStandardText(localization);
}
serializedObject.ApplyModifiedProperties();
}
public virtual void ExportLocalizationFile(Localization localization)
{
string path = EditorUtility.SaveFilePanel("Export Localization File", "Assets/",
"localization.csv", "");
if (path.Length == 0)
{
return;
}
string csvData = localization.GetCSVData();
File.WriteAllText(path, csvData);
AssetDatabase.Refresh();
ShowNotification(localization);
}
public virtual void ExportStandardText(Localization localization)
{
string path = EditorUtility.SaveFilePanel("Export Standard Text", "Assets/", "standard.txt", "");
if (path.Length == 0)
{
return;
}
string textData = localization.GetStandardText();
File.WriteAllText(path, textData);
AssetDatabase.Refresh();
ShowNotification(localization);
}
public virtual void ImportStandardText(Localization localization)
{
string path = EditorUtility.OpenFilePanel("Import Standard Text", "Assets/", "txt");
if (path.Length == 0)
{
return;
}
string textData = File.ReadAllText(path);
localization.SetStandardText(textData);
ShowNotification(localization);
}
protected virtual void ShowNotification(Localization localization)
{
EditorWindow window = EditorWindow.GetWindow(typeof(FlowchartWindow), false, "Flowchart");
if (window != null)
{
window.ShowNotification(new GUIContent(localization.notificationText));
localization.notificationText = "";
}
}
}
}

4
Assets/Fungus/Flowchart/Editor/FountainExporter.cs.meta → Assets/Fungus/Narrative/Editor/LocalizationEditor.cs.meta

@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: c91ad6ef6a0734046bd93dde4b0e59d1
timeCreated: 1426502899
guid: ab0caac085485491fb32dbf86efefef1
timeCreated: 1428581512
licenseType: Free
MonoImporter:
serializedVersion: 2

4
Assets/Fungus/Narrative/Editor/MenuEditor.cs

@ -11,6 +11,7 @@ namespace Fungus
public class MenuEditor : CommandEditor
{
protected SerializedProperty textProp;
protected SerializedProperty descriptionProp;
protected SerializedProperty targetBlockProp;
protected SerializedProperty hideIfVisitedProp;
protected SerializedProperty setMenuDialogProp;
@ -18,6 +19,7 @@ namespace Fungus
protected virtual void OnEnable()
{
textProp = serializedObject.FindProperty("text");
descriptionProp = serializedObject.FindProperty("description");
targetBlockProp = serializedObject.FindProperty("targetBlock");
hideIfVisitedProp = serializedObject.FindProperty("hideIfVisited");
setMenuDialogProp = serializedObject.FindProperty("setMenuDialog");
@ -34,6 +36,8 @@ namespace Fungus
serializedObject.Update();
EditorGUILayout.PropertyField(textProp);
EditorGUILayout.PropertyField(descriptionProp);
BlockEditor.BlockField(targetBlockProp,
new GUIContent("Target Block", "Block to call when option is selected"),

6
Assets/Fungus/Narrative/Editor/NarrativeMenuItems.cs

@ -50,6 +50,12 @@ namespace Fungus
{
FlowchartMenuItems.SpawnPrefab("StagePosition");
}
[MenuItem("Tools/Fungus/Create/Localization", false, 57)]
static void CreateLocalization()
{
FlowchartMenuItems.SpawnPrefab("Localization");
}
}
}

6
Assets/Fungus/Narrative/Editor/SayEditor.cs

@ -100,6 +100,7 @@ namespace Fungus
protected SerializedProperty characterProp;
protected SerializedProperty portraitProp;
protected SerializedProperty storyTextProp;
protected SerializedProperty descriptionProp;
protected SerializedProperty voiceOverClipProp;
protected SerializedProperty showAlwaysProp;
protected SerializedProperty showCountProp;
@ -114,6 +115,7 @@ namespace Fungus
characterProp = serializedObject.FindProperty("character");
portraitProp = serializedObject.FindProperty("portrait");
storyTextProp = serializedObject.FindProperty("storyText");
descriptionProp = serializedObject.FindProperty("description");
voiceOverClipProp = serializedObject.FindProperty("voiceOverClip");
showAlwaysProp = serializedObject.FindProperty("showAlways");
showCountProp = serializedObject.FindProperty("showCount");
@ -170,7 +172,9 @@ namespace Fungus
}
EditorGUILayout.PropertyField(storyTextProp);
EditorGUILayout.PropertyField(descriptionProp);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(extendPreviousProp);

54
Assets/Fungus/Narrative/Resources/Localization.prefab

@ -0,0 +1,54 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &149266
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
serializedVersion: 4
m_Component:
- 4: {fileID: 480768}
- 114: {fileID: 11438504}
m_Layer: 0
m_Name: Localization
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &480768
Transform:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 149266}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 2.05546069, y: -3.16485739, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
--- !u!114 &11438504
MonoBehaviour:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 149266}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5724422a635e425bae0af9ffe2615d6, type: 3}
m_Name:
m_EditorClassIdentifier:
activeLanguage:
localizationFile: {fileID: 0}
--- !u!1001 &100100000
Prefab:
m_ObjectHideFlags: 1
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications: []
m_RemovedComponents: []
m_ParentPrefab: {fileID: 0}
m_RootGameObject: {fileID: 149266}
m_IsPrefabParent: 1

8
Assets/Fungus/Narrative/Resources/Localization.prefab.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ffbd0831d997545eab75c364da082c1b
timeCreated: 1428580452
licenseType: Free
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

7
Assets/Fungus/Narrative/Scripts/Character.cs

@ -1,12 +1,14 @@
using UnityEngine;
using UnityEngine.Serialization;
using System.Collections;
using System.Collections.Generic;
using System;
namespace Fungus
{
[ExecuteInEditMode]
public class Character : MonoBehaviour
public class Character : MonoBehaviour
{
public string nameText; // We need a separate name as the object name is used for character variations (e.g. "Smurf Happy", "Smurf Sad")
public Color nameColor = Color.white;
@ -16,8 +18,9 @@ namespace Fungus
public FacingDirection portraitsFace;
public PortraitState state;
[FormerlySerializedAs("notes")]
[TextArea(5,10)]
public string notes;
public string description;
static public List<Character> activeCharacters = new List<Character>();

5
Assets/Fungus/Narrative/Scripts/Commands/Menu.cs

@ -11,7 +11,7 @@ namespace Fungus
"Menu",
"Displays a multiple choice menu")]
[AddComponentMenu("")]
public class Menu : Command
public class Menu : Command
{
// Menu displays a menu button which will execute the target block when clicked
@ -22,6 +22,9 @@ namespace Fungus
public string text = "Option Text";
[Tooltip("Notes about the option text for other authors, localization, etc.")]
public string description = "";
[FormerlySerializedAs("targetSequence")]
public Block targetBlock;

7
Assets/Fungus/Narrative/Scripts/Commands/Say.cs

@ -9,10 +9,13 @@ namespace Fungus
"Say",
"Writes text in a dialog box.")]
[AddComponentMenu("")]
public class Say : Command
public class Say : Command
{
[TextArea(5,10)]
public string storyText;
public string storyText = "";
[Tooltip("Notes about this story text for other authors, localization, etc.")]
public string description = "";
[Tooltip("Character that is speaking")]
public Character character;

35
Assets/Fungus/Narrative/Scripts/Commands/SetLanguage.cs

@ -0,0 +1,35 @@
using UnityEngine;
using System.Collections;
namespace Fungus
{
[CommandInfo("Narrative",
"Set Language",
"Set the active language for the scene. A Localization object with a localization file must be present in the scene.")]
[AddComponentMenu("")]
public class SetLanguage : Command
{
public string languageCode;
public override void OnEnter()
{
Localization localization = GameObject.FindObjectOfType<Localization>();
if (localization != null)
{
localization.SetActiveLanguage(languageCode);
}
Continue();
}
public override string GetSummary()
{
return languageCode;
}
public override Color GetButtonColor()
{
return new Color32(184, 210, 235, 255);
}
}
}

6
Assets/Fungus/Flowchart/Scripts/StringsParser.cs.meta → Assets/Fungus/Narrative/Scripts/Commands/SetLanguage.cs.meta

@ -1,8 +1,12 @@
fileFormatVersion: 2
guid: 0f02aedc631824200a4abe95774a44f5
guid: 3fc625e237d6048bf86f34835d8266d9
timeCreated: 1428591017
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

601
Assets/Fungus/Narrative/Scripts/Localization.cs

@ -0,0 +1,601 @@
/**
* CSVParser by Ideafixxxer. http://www.codeproject.com/Tips/741941/CSV-Parser-Csharp
* This code is licensed under the CPOL open source license.
* http://www.codeproject.com/info/cpol10.aspx
*/
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Ideafixxxer.CsvParser;
namespace Fungus
{
/**
* Multi-language localization support.
*/
public class Localization : MonoBehaviour
{
/**
* Currently active language, usually defined by a two letter language code (e.g DE = German)
*/
public string activeLanguage = "";
protected static Dictionary<string, string> localizedStrings = new Dictionary<string, string>();
/**
* Temp storage for a single item of standard text and its localizations
*/
protected class TextItem
{
public string description = "";
public string standardText = "";
public Dictionary<string, string> localizedStrings = new Dictionary<string, string>();
}
/**
* CSV file containing localization data
*/
public TextAsset localizationFile;
/**
* Stores any notification message from export / import methods.
*/
[NonSerialized]
public string notificationText = "";
public virtual void Start()
{
if (localizationFile != null &&
localizationFile.text.Length > 0)
{
SetActiveLanguage(activeLanguage);
}
}
/**
* Looks up the specified string in the localized strings table.
* For this to work, a localization file and active language must have been set previously.
* Return null if the string is not found.
*/
public static string GetLocalizedString(string stringId)
{
if (localizedStrings == null)
{
return null;
}
if (localizedStrings.ContainsKey(stringId))
{
return localizedStrings[stringId];
}
return null;
}
/**
* Convert all text items and localized strings to an easy to edit CSV format.
*/
public virtual string GetCSVData()
{
// Collect all the text items present in the scene
Dictionary<string, TextItem> textItems = FindTextItems();
// Update text items with localization data from CSV file
if (localizationFile != null &&
localizationFile.text.Length > 0)
{
AddCSVDataItems(textItems, localizationFile.text);
}
// Build CSV header row and a list of the language codes currently in use
string csvHeader = "Key,Description,Standard";
List<string> languageCodes = new List<string>();
foreach (TextItem textItem in textItems.Values)
{
foreach (string languageCode in textItem.localizedStrings.Keys)
{
if (!languageCodes.Contains(languageCode))
{
languageCodes.Add(languageCode);
csvHeader += "," + languageCode;
}
}
}
// Build the CSV file using collected text items
int rowCount = 0;
string csvData = csvHeader + "\n";
foreach (string stringId in textItems.Keys)
{
TextItem textItem = textItems[stringId];
string row = CSVSupport.Escape(stringId);
row += "," + CSVSupport.Escape(textItem.description);
row += "," + CSVSupport.Escape(textItem.standardText);
foreach (string languageCode in languageCodes)
{
if (textItem.localizedStrings.ContainsKey(languageCode))
{
row += "," + CSVSupport.Escape(textItem.localizedStrings[languageCode]);
}
else
{
row += ","; // Empty field
}
}
csvData += row + "\n";
rowCount++;
}
notificationText = "Exported " + rowCount + " localization text items.";
return csvData;
}
/**
* Buidls a dictionary of localizable text items in the scene.
*/
protected Dictionary<string, TextItem> FindTextItems()
{
Dictionary<string, TextItem> textItems = new Dictionary<string, TextItem>();
// Export all character names
foreach (Character character in GameObject.FindObjectsOfType<Character>())
{
// String id for character names is CHARACTER.<Character Name>
TextItem textItem = new TextItem();
textItem.standardText = character.nameText;
textItem.description = character.description;
string stringId = "CHARACTER." + character.nameText;
textItems[stringId] = textItem;
}
// Export all Say and Menu commands in the scene
// To make it easier to localize, we preserve the command order in each exported block.
Flowchart[] flowcharts = GameObject.FindObjectsOfType<Flowchart>();
foreach (Flowchart flowchart in flowcharts)
{
// Have to set a unique localization id to export strings
if (flowchart.localizationId.Length == 0)
{
continue;
}
Block[] blocks = flowchart.GetComponentsInChildren<Block>();
foreach (Block block in blocks)
{
foreach (Command command in block.commandList)
{
string stringId = "";
string standardText = "";
string description = "";
System.Type type = command.GetType();
if (type == typeof(Say))
{
// String id for Say commands is SAY.<Flowchart id>.<Command id>.<Character Name>
Say sayCommand = command as Say;
standardText = sayCommand.storyText;
description = sayCommand.description;
stringId = "SAY." + flowchart.localizationId + "." + sayCommand.itemId + ".";
if (sayCommand.character != null)
{
stringId += sayCommand.character.nameText;
}
}
else if (type == typeof(Menu))
{
// String id for Menu commands is MENU.<Flowchart id>.<Command id>
Menu menuCommand = command as Menu;
standardText = menuCommand.text;
description = menuCommand.description;
stringId = "MENU." + flowchart.localizationId + "." + menuCommand.itemId;
}
else
{
continue;
}
TextItem textItem = null;
if (textItems.ContainsKey(stringId))
{
textItem = textItems[stringId];
}
else
{
textItem = new TextItem();
textItems[stringId] = textItem;
}
// Update basic properties,leaving localised strings intact
textItem.standardText = standardText;
textItem.description = description;
}
}
}
return textItems;
}
/**
* Adds localized strings from CSV file data to a dictionary of text items in the scene.
*/
protected virtual void AddCSVDataItems(Dictionary<string, TextItem> textItems, string csvData)
{
CsvParser csvParser = new CsvParser();
string[][] csvTable = csvParser.Parse(csvData);
if (csvTable.Length <= 1)
{
// No data rows in file
return;
}
// Parse header row
string[] columnNames = csvTable[0];
for (int i = 1; i < csvTable.Length; ++i)
{
string[] fields = csvTable[i];
if (fields.Length < 3)
{
// No standard text or localized string fields present
continue;
}
string stringId = fields[0];
if (!textItems.ContainsKey(stringId))
{
if (stringId.StartsWith("CHARACTER.") ||
stringId.StartsWith("SAY.") ||
stringId.StartsWith("MENU."))
{
// If it's a 'built-in' type this probably means that item has been deleted from its flowchart,
// so there's no need to add a text item for it.
continue;
}
// Key not found. Assume it's a custom string that we want to retain, so add a text item for it.
TextItem newTextItem = new TextItem();
newTextItem.description = CSVSupport.Unescape(fields[1]);
newTextItem.standardText = CSVSupport.Unescape(fields[2]);
textItems[stringId] = newTextItem;
}
TextItem textItem = textItems[stringId];
for (int j = 3; j < fields.Length; ++j)
{
if (j >= columnNames.Length)
{
continue;
}
string languageCode = columnNames[j];
string languageEntry = CSVSupport.Unescape(fields[j]);
if (languageEntry.Length > 0)
{
textItem.localizedStrings[languageCode] = languageEntry;
}
}
}
}
/**
* Scan a localization CSV file and copies the strings for the specified language code
* into the text properties of the appropriate scene objects.
*/
public virtual void SetActiveLanguage(string languageCode)
{
if (!Application.isPlaying)
{
// This function should only ever be called when the game is playing (not in editor).
return;
}
if (localizationFile == null)
{
// No localization file set
return;
}
localizedStrings.Clear();
CsvParser csvParser = new CsvParser();
string[][] csvTable = csvParser.Parse(localizationFile.text);
if (csvTable.Length <= 1)
{
// No data rows in file
return;
}
// Parse header row
string[] columnNames = csvTable[0];
if (columnNames.Length < 3)
{
// No languages defined in CSV file
return;
}
// First assume standard text column and then look for a matching language column
int languageIndex = 2;
for (int i = 3; i < columnNames.Length; ++i)
{
if (columnNames[i] == languageCode)
{
languageIndex = i;
break;
}
}
if (languageIndex == 2)
{
// Using standard text column
// Add all strings to the localized strings dict, but don't replace standard text in the scene.
// This allows string substitution to work for both standard and localized text strings.
for (int i = 1; i < csvTable.Length; ++i)
{
string[] fields = csvTable[i];
if (fields.Length < 3)
{
continue;
}
localizedStrings[fields[0]] = fields[languageIndex];
}
return;
}
// Using a localized language text column
// 1. Add all localized text to the localized strings dict
// 2. Update all scene text properties with localized versions
// Cache a lookup table of characters in the scene
Dictionary<string, Character> characterDict = new Dictionary<string, Character>();
foreach (Character character in GameObject.FindObjectsOfType<Character>())
{
characterDict[character.nameText] = character;
}
// Cache a lookup table of flowcharts in the scene
Dictionary<string, Flowchart> flowchartDict = new Dictionary<string, Flowchart>();
foreach (Flowchart flowChart in GameObject.FindObjectsOfType<Flowchart>())
{
flowchartDict[flowChart.localizationId] = flowChart;
}
for (int i = 1; i < csvTable.Length; ++i)
{
string[] fields = csvTable[i];
if (fields.Length < languageIndex + 1)
{
continue;
}
string stringId = fields[0];
string languageEntry = CSVSupport.Unescape(fields[languageIndex]);
if (languageEntry.Length > 0)
{
localizedStrings[stringId] = languageEntry;
PopulateTextProperty(stringId, languageEntry, flowchartDict, characterDict);
}
}
}
/**
* Populates the text property of a single scene object with a new text value.
*/
public virtual bool PopulateTextProperty(string stringId,
string newText,
Dictionary<string, Flowchart> flowchartDict,
Dictionary<string, Character> characterDict)
{
string[] idParts = stringId.Split('.');
if (idParts.Length == 0)
{
return false;
}
string stringType = idParts[0];
if (stringType == "SAY")
{
if (idParts.Length != 4)
{
return false;
}
string flowchartId = idParts[1];
if (!flowchartDict.ContainsKey(flowchartId))
{
return false;
}
Flowchart flowchart = flowchartDict[flowchartId];
int itemId = int.Parse(idParts[2]);
if (flowchart != null)
{
foreach (Say say in flowchart.GetComponentsInChildren<Say>())
{
if (say.itemId == itemId &&
say.storyText != newText)
{
#if UNITY_EDITOR
Undo.RecordObject(say, "Set Text");
#endif
say.storyText = newText;
return true;
}
}
}
}
else if (stringType == "MENU")
{
if (idParts.Length != 3)
{
return false;
}
string flowchartId = idParts[1];
if (!flowchartDict.ContainsKey(flowchartId))
{
return false;
}
Flowchart flowchart = flowchartDict[flowchartId];
int itemId = int.Parse(idParts[2]);
if (flowchart != null)
{
foreach (Menu menu in flowchart.GetComponentsInChildren<Menu>())
{
if (menu.itemId == itemId &&
menu.text != newText)
{
#if UNITY_EDITOR
Undo.RecordObject(menu, "Set Text");
#endif
menu.text = newText;
return true;
}
}
}
}
else if (stringType == "CHARACTER")
{
if (idParts.Length != 2)
{
return false;
}
string characterName = idParts[1];
if (!characterDict.ContainsKey(characterName))
{
return false;
}
Character character = characterDict[characterName];
if (character != null &&
character.nameText != newText)
{
#if UNITY_EDITOR
Undo.RecordObject(character, "Set Text");
#endif
character.nameText = newText;
return true;
}
}
return false;
}
/**
* Returns all standard text for SAY & MENU commands in the scene using an
* easy to edit custom text format.
*/
public virtual string GetStandardText()
{
// Collect all the text items present in the scene
Dictionary<string, TextItem> textItems = FindTextItems();
string textData = "";
int rowCount = 0;
foreach (string stringId in textItems.Keys)
{
if (!stringId.StartsWith("SAY.") && !(stringId.StartsWith("MENU.")))
{
continue;
}
TextItem languageItem = textItems[stringId];
textData += "#" + stringId + "\n";
textData += languageItem.standardText.Trim() + "\n\n";
rowCount++;
}
notificationText = "Exported " + rowCount + " standard text items.";
return textData;
}
/**
* Sets standard text on scene objects by parsing a text data file.
*/
public virtual void SetStandardText(string textData)
{
// Cache a lookup table of characters in the scene
Dictionary<string, Character> characterDict = new Dictionary<string, Character>();
foreach (Character character in GameObject.FindObjectsOfType<Character>())
{
characterDict[character.nameText] = character;
}
// Cache a lookup table of flowcharts in the scene
Dictionary<string, Flowchart> flowchartDict = new Dictionary<string, Flowchart>();
foreach (Flowchart flowChart in GameObject.FindObjectsOfType<Flowchart>())
{
flowchartDict[flowChart.localizationId] = flowChart;
}
string[] lines = textData.Split('\n');
int updatedCount = 0;
string stringId = "";
string buffer = "";
foreach (string line in lines)
{
// Check for string id line
if (line.StartsWith("#"))
{
if (stringId.Length > 0)
{
// Write buffered text to the appropriate text property
if (PopulateTextProperty(stringId, buffer.Trim(), flowchartDict, characterDict))
{
updatedCount++;
}
}
// Set the string id for the follow text lines
stringId = line.Substring(1, line.Length - 1);
buffer = "";
}
else
{
buffer += line;
}
}
// Handle last buffered entry
if (stringId.Length > 0)
{
if (PopulateTextProperty(stringId, buffer.Trim(), flowchartDict, characterDict))
{
updatedCount++;
}
}
notificationText = "Updated " + updatedCount + " standard text items.";
}
}
}

12
Assets/Fungus/Narrative/Scripts/Localization.cs.meta

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e5724422a635e425bae0af9ffe2615d6
timeCreated: 1427886378
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

9
Assets/Fungus/Thirdparty/CSVParser.meta vendored

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 3469ab31c1c9d4c2da1ae42edc001ded
folderAsset: yes
timeCreated: 1428523768
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

48
Assets/Fungus/Thirdparty/CSVParser/CSVSupport.cs vendored

@ -0,0 +1,48 @@
using UnityEngine;
using System.Collections;
using System.Text.RegularExpressions;
using System.Linq;
using System;
namespace Fungus
{
// Some CSV utilities cobbled together from stack overflow answers
// CSV escape & unescape from http://stackoverflow.com/questions/769621/dealing-with-commas-in-a-csv-file
// http://answers.unity3d.com/questions/144200/are-there-any-csv-reader-for-unity3d-without-needi.html
public static class CSVSupport
{
public static string Escape( string s )
{
s = s.Replace("\n", "\\n");
if ( s.Contains( QUOTE ) )
s = s.Replace( QUOTE, ESCAPED_QUOTE );
if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
s = QUOTE + s + QUOTE;
return s;
}
public static string Unescape( string s )
{
s = s.Replace("\\n", "\n");
if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
{
s = s.Substring( 1, s.Length - 2 );
if ( s.Contains( ESCAPED_QUOTE ) )
s = s.Replace( ESCAPED_QUOTE, QUOTE );
}
return s;
}
private const string QUOTE = "\"";
private const string ESCAPED_QUOTE = "\"\"";
private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}
}

12
Assets/Fungus/Thirdparty/CSVParser/CSVSupport.cs.meta vendored

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4468f4dcfdfbf46b088949ea57ed6135
timeCreated: 1427897861
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

218
Assets/Fungus/Thirdparty/CSVParser/CsvParser.cs vendored

@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Ideafixxxer.CsvParser
{
public class CsvParser
{
private const char CommaCharacter = ',';
private const char QuoteCharacter = '"';
#region Nested types
private abstract class ParserState
{
public static readonly LineStartState LineStartState = new LineStartState();
public static readonly ValueStartState ValueStartState = new ValueStartState();
public static readonly ValueState ValueState = new ValueState();
public static readonly QuotedValueState QuotedValueState = new QuotedValueState();
public static readonly QuoteState QuoteState = new QuoteState();
public abstract ParserState AnyChar(char ch, ParserContext context);
public abstract ParserState Comma(ParserContext context);
public abstract ParserState Quote(ParserContext context);
public abstract ParserState EndOfLine(ParserContext context);
}
private class LineStartState : ParserState
{
public override ParserState AnyChar(char ch, ParserContext context)
{
context.AddChar(ch);
return ValueState;
}
public override ParserState Comma(ParserContext context)
{
context.AddValue();
return ValueStartState;
}
public override ParserState Quote(ParserContext context)
{
return QuotedValueState;
}
public override ParserState EndOfLine(ParserContext context)
{
context.AddLine();
return LineStartState;
}
}
private class ValueStartState : LineStartState
{
public override ParserState EndOfLine(ParserContext context)
{
context.AddValue();
context.AddLine();
return LineStartState;
}
}
private class ValueState : ParserState
{
public override ParserState AnyChar(char ch, ParserContext context)
{
context.AddChar(ch);
return ValueState;
}
public override ParserState Comma(ParserContext context)
{
context.AddValue();
return ValueStartState;
}
public override ParserState Quote(ParserContext context)
{
context.AddChar(QuoteCharacter);
return ValueState;
}
public override ParserState EndOfLine(ParserContext context)
{
context.AddValue();
context.AddLine();
return LineStartState;
}
}
private class QuotedValueState : ParserState
{
public override ParserState AnyChar(char ch, ParserContext context)
{
context.AddChar(ch);
return QuotedValueState;
}
public override ParserState Comma(ParserContext context)
{
context.AddChar(CommaCharacter);
return QuotedValueState;
}
public override ParserState Quote(ParserContext context)
{
return QuoteState;
}
public override ParserState EndOfLine(ParserContext context)
{
context.AddChar('\r');
context.AddChar('\n');
return QuotedValueState;
}
}
private class QuoteState : ParserState
{
public override ParserState AnyChar(char ch, ParserContext context)
{
//undefined, ignore "
context.AddChar(ch);
return QuotedValueState;
}
public override ParserState Comma(ParserContext context)
{
context.AddValue();
return ValueStartState;
}
public override ParserState Quote(ParserContext context)
{
context.AddChar(QuoteCharacter);
return QuotedValueState;
}
public override ParserState EndOfLine(ParserContext context)
{
context.AddValue();
context.AddLine();
return LineStartState;
}
}
private class ParserContext
{
private readonly StringBuilder _currentValue = new StringBuilder();
private readonly List<string[]> _lines = new List<string[]>();
private readonly List<string> _currentLine = new List<string>();
public void AddChar(char ch)
{
_currentValue.Append(ch);
}
public void AddValue()
{
_currentLine.Add(_currentValue.ToString());
_currentValue.Remove(0, _currentValue.Length);
}
public void AddLine()
{
_lines.Add(_currentLine.ToArray());
_currentLine.Clear();
}
public List<string[]> GetAllLines()
{
if (_currentValue.Length > 0)
{
AddValue();
}
if (_currentLine.Count > 0)
{
AddLine();
}
return _lines;
}
}
#endregion
public string[][] Parse(string csvData)
{
var context = new ParserContext();
string[] lines = csvData.Split('\n');
ParserState currentState = ParserState.LineStartState;
foreach (string next in lines)
{
foreach (char ch in next)
{
switch (ch)
{
case CommaCharacter:
currentState = currentState.Comma(context);
break;
case QuoteCharacter:
currentState = currentState.Quote(context);
break;
default:
currentState = currentState.AnyChar(ch, context);
break;
}
}
currentState = currentState.EndOfLine(context);
}
List<string[]> allLines = context.GetAllLines();
return allLines.ToArray();
}
}
}

12
Assets/Fungus/Thirdparty/CSVParser/CsvParser.cs.meta vendored

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 803c0d4d8bc9447d1b20e1f4fb86120f
timeCreated: 1428171346
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

281
Assets/FungusExamples/Sherlock/TheExperiment.unity

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save