TextManager: Localization Script

I started this awhile back in order to localize a project. It reads a language file on start up so the application can reference strings by tag. It only supports the default character set.

using UnityEngine;
using System.Collections;
using System.IO;


//===============================================================================================
// TextManager
// KLL 09/04/2009
//
// Reads text files in the Assets\Resources\Languages directory into a Hashtable. The text file 
// consists of one line that has the key name while the next line has the actual
// text to display.  This is used to localize a game.
//
//	Example:
//		Assume we have a text file called English.txt in Assets\Resources\Languages
//		The file looks like this:
//	
//		HELLO
//		Hello and welcome!
//		GOODBYE
//		Goodbye and thanks for playing!
// 
//		in code we file load the language file:
//			TextManager.LoadLanguage("English");
// 
//		then we can retrieve text by calling 
//			TextManager.GetText("HELLO");
//
//		will return a string containing "Hello and welcome!"
//
//===============================================================================================

public class TextManager : MonoBehaviour {
	
	// PRIVATE DECLARATIONS
	private static TextManager instance;
	private static Hashtable textTable;
	private TextManager () {} 

	//-----------------------------------------------------------------------------------------------
	// Returns an instance of the TextManager
	//-----------------------------------------------------------------------------------------------
    private static TextManager Instance 
	{
		get 
			{
			if (instance == null) 
				{
        		// Because the TextManager is a component, we have to create a GameObject to attach it to.
       	 		GameObject notificationObject = new GameObject("Default TextManager");
       	 		
        		// Add the DynamicObjectManager component, and set it as the defaultCenter
       			instance = (TextManager) notificationObject.AddComponent(typeof(TextManager));
    			}
			return instance;
			}
	}
	
	//-----------------------------------------------------------------------------------------------
	// Returns an instance of the TextManager
	//-----------------------------------------------------------------------------------------------
	public static TextManager GetInstance()
	{
		return Instance;
	}	
	
	public static bool LoadLanguage(string filename)
	{
		GetInstance();
		
		string fullpath = "Languages/" +  filename;

		TextAsset textAsset = (TextAsset) Resources.Load(fullpath, typeof(TextAsset));
		if (textAsset == null) 
			{
			Debug.Log(fullpath + " file not found.");
			return false;
		 	}
		
		// create the text hash table if one doesn't exist
		if (textTable == null) 
			{
			textTable = new Hashtable();
			}
			
		// clear the hashtable
		textTable.Clear();
		
		StringReader reader = new StringReader(textAsset.text);
		string key;
		string val;
		while(true)
			{
    		key = reader.ReadLine();
    		val = reader.ReadLine();
    		if (key != null  val != null) 
    			{
    			// TODO: add error handling here in case of duplicate keys
    			textTable.Add(key, val);
    			} 
    		else 
    			{
    			break;
    			}
			}

		
		reader.Close();
				
		return true;
	}

	
	public static string GetText(string key)
	{
		// TODO: add error handling here in case the key doesn't exist
		return  (string)textTable[key];
	}

}

Cheers, mate, this is pretty cool. I used to work as a translator for a bit once and at work we i18n everything, so I had this pretty high on my to-look-into list. Glad to see I’ve got something to start with now :slight_smile:

One of the reasons I posted this is that I have no knowledge of what it takes to actually localize something. I was hoping someone like yourself could point out what this needs to be useful. For example, I could have German and French files now but I don’t think they are useful unless they support the proper accent characters.

From what I understand, localization is one of the things Apple looks for when deciding what apps to feature.

Hi Kenlem,

I’ve just been looking at your TextManager script and am struggling to understand how to get it to work (I’m quite new to scripting and have not done any C sharp code yet).

I have put your script into an empty project and added a text document in the correct file structure. It does not produce errors! but I am struggling with the correct way to call the TextManager, is it ok to call from a JavaScript? I created a GUIText object with a script on it to call the functions but I’m kind of getting nowhere, could you give me some pointers please?

Hi,

I’m new to Unity, but managed to get this to work with Unity 3.3.
Placed in Assets/Plugins, and with the txt files properly setup it works.

Though, it creates a new gameObject everytime I run the project.
It seems that
if (instance == null)
always returns true

help would be greatly appreciated. I know there are some localization prefabs available for a less than $100, but getting to understand things is always better, even if I end up buying it off the shelf.

Thanks!

I’ve translated it into a Java Script version

import System.IO;//using System.IO;

//===============================================================================================
// TextManager
// 08/06/2011
//
// Reads text files in the Assets\Resources\Languages directory into a Hashtable. The text file
// consists of one line that has the key name while the next line has the actual
// text to display. This is used to localize a game.
//
// Example:
// Assume we have a text file called English.txt in Assets\Resources\Languages
// The file looks like this:
//
// HELLO
// Hello and welcome!
// GOODBYE
// Goodbye and thanks for playing!
//
// in code we load the language file automatically at start
// LoadLanguage(“English”);
//
// then we can retrieve text by calling
// var adm_texto = TextManager(Application.systemLanguage.ToString()); //Here we load the file with our
// // Language
// textGUI.text = adm_texto.GetText(“HELP”); //Load the localized string
//
//
// will return a String containing “Hello and welcome!”
//
//===============================================================================================

class TextManager{

//DECLARATIONS
private var textTable : Hashtable;
var filename : String; // name of the text asset

function TextManager(mypath:String){
filename=mypath;
LoadLanguage(filename);
}

public function LoadLanguage( filename : String ) : boolean
{

var fullpath : String = “Languages/” + filename;

var textAsset : TextAsset = Resources.Load(fullpath, typeof(TextAsset));
if (textAsset == null)
{
Debug.Log(fullpath + " file not found.");
return false;
}

// create the text hash table if one doesn’t exist
if (textTable == null)
{
textTable = new Hashtable();
}

// clear the hashtable
textTable.Clear();

var reader : StringReader = new StringReader(textAsset.text);
var key : String;
var val : String;
while(true)
{
key = reader.ReadLine();
val = reader.ReadLine();
if (key != null val != null)
{
// TODO: add error handling here in case of duplicate keys
textTable.Add(key, val);
}
else
{
break;
}
}

reader.Close();

return true;
}

public function GetText( key : String ): String
{
// TODO: add error handling here in case the key doesn’t exist
return textTable[key];
}
}

Hey Kenlem!

I’ve used your script to come up with something that makes more sense to me, using PO and POT files.

And I’ve added it to Unify Community. I hope you don’t mind! :stuck_out_tongue:

I hope this really is your script… but it looks VERY similar to a script I wrote about 4 months ago and posted to the asset store. Even the comments and variable names and text file locations are identical.

Not a big deal to me but I just hope that peopledont steal without giving some credit.

Jake

“Posted: 10:37 AM 11-27-2009”

–Eric

Yeah I noticed that shortly after posting… There are a ton of localization scripts out there now anyway so its no wonder that there are duplicates… I was just reading the code and thinking at how amazingly similar they were… And as I was saying at the start of that post, I am not assuming there was any copying and I really wouldn’t care anyway seeing how many scripts I have handed out for free on the forums ;)…

Thanks, Eric!

Jake

Is there a way to load the file based on the user’s iphone settings? And if not, I imagine it would mean an additional screen prior to the main menu, where the user selects the language?

OR!! Do we submit separate apps to Apple, one for each language!!!

Thanks :slight_smile: :slight_smile: :stuck_out_tongue:

In the JScript Version you can use var adm_texto = TextManager( SOME_IPHONE_SETTINGS_IE_PREFERRED_LANGUAGE ) ;
However as Unity doesn’t support other charsets but ASCII, you must write them in ASCII.

Regards

Note: In the iPhone the locales are named different :wink:

Just in case UnifyCommunity goes down, here’s the code from my previous post, copied from Unify itself:

Usage

// This will reset to default POT Language
TextManager.LoadLanguage(null);
 
// This will load filename-en.po
TextManager.LoadLanguage("filename-en");
 
// Considering the PO file has a msgid "Ola Mundo" and msgstr "Hello World" this will print "Hello World"
print(TextManager.GetText("Ola Mundo"));

TextManager.cs

using UnityEngine;
using System.Collections;
using System.IO;
 
// originally found in: http://forum.unity3d.com/threads/35617-TextManager-Localization-Script
 
/// <summary>
/// TextManager
/// 
/// Reads PO files in the Assets\Resources\Languages directory into a Hashtable.
/// Look for "PO editors" as that's a standard for translating software.
/// 
/// Example:
/// 
/// load the language file:
///   TextManager.LoadLanguage("helptext-pt-br");
/// 
/// which has to contain at least two lines as such:
///   msgid "HELLO WORLD"
///   msgstr "OLA MUNDO"
/// 
/// then we can retrieve the text by calling:
///   TextManager.GetText("HELLO WORLD");
/// </summary>
public class TextManager : MonoBehaviour {
 
	private static TextManager instance;
	private static Hashtable textTable;
	private TextManager () {} 
 
	private static TextManager Instance 
	{
		get 
		{
			if (instance == null) 
			{
        		// Because the TextManager is a component, we have to create a GameObject to attach it to.
       	 		GameObject notificationObject = new GameObject("Default TextManager");
 
        		// Add the DynamicObjectManager component, and set it as the defaultCenter
       			instance = (TextManager) notificationObject.AddComponent(typeof(TextManager));
    		}
			return instance;
		}
	}
 
	public static TextManager GetInstance ()
	{
		return Instance;
	}	
 
	public static bool LoadLanguage (string filename)
	{
		GetInstance();
 
		if (filename == null)
		{
			DebugConsole.Log("[TextManager] loading default language.");
			textTable = null; // reset to default
			return false; // this means: call LoadLanguage with null to reset to default
		}
 
		string fullpath = "Languages/" +  filename + ".po"; // the file is actually ".txt" in the end
 
		TextAsset textAsset = (TextAsset) Resources.Load(fullpath, typeof(TextAsset));
		if (textAsset == null) 
		{
			DebugConsole.LogError("[TextManager] "+ fullpath +" file not found.");
			return false;
		}
 
		DebugConsole.Log("[TextManager] loading: "+ fullpath);
 
		if (textTable == null) 
		{
			textTable = new Hashtable();
		}
 
		textTable.Clear();
 
		StringReader reader = new StringReader(textAsset.text);
		string key = null;
		string val = null;
		string line;
		while ( (line = reader.ReadLine()) != null)
		{
			if (line.StartsWith("msgid \""))
			{
    			key = line.Substring(7, line.Length - 8);
			}
			else if (line.StartsWith("msgstr \""))
			{
    			val = line.Substring(8, line.Length - 9);
			}
			else
			{
	    		if (key != null  val != null) 
	    		{
	    			// TODO: add error handling here in case of duplicate keys
	    			textTable.Add(key, val);
					key = val = null;
	    		} 
    		}
		}
 
		reader.Close();
 
		return true;
	}
 
 
	public static string GetText (string key)
	{
		if (key != null  textTable != null)
		{
			if (textTable.ContainsKey(key))
			{
				string result = (string)textTable[key];
				if (result.Length > 0)
				{
					key = result;
				}
			}
		}
		return key;
	}
}

I’m curious as to why you chose to have this inherit from MonoBehaviour.

kelso I think every class which is to be applied to an object has to be inherited from monobehavior. Javascript code without classes is inherited automatically. This is, unless I am missing something from your question?

You can inherit from “ScriptableObject” for classes that don’t require a GameObject

Check out this example on this thread:
http://forum.unity3d.com/threads/76889-Does-iOS-support-PlayerPrefs-class

@Cawas
is DebugConsole a runtime console works in web player? care to share that, too

Hi,

I was wondering if you guys managed to sort out issues with translated text that overflows?

Some words in other languages have more characters than English words, so did you manage to scale the TextMesh/GuiText text to fit to the area it should be displayed on?

Thanks

Yes, it is! I completely forgot that. It’s also in UnifyCommunity. But it’s too big to post here and it can be safely replaced by regular [Debug](http://docs.unity3d.com/Documentation/ScriptReference/Debug.Log.html): http://wiki.unity3d.com/index.php/DebugConsole