<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Sgt. Conker &#187; Localization</title>
	<atom:link href="http://www.sgtconker.com/tag/localization/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.sgtconker.com</link>
	<description>We are &#34;absolutely fine&#34;</description>
	<lastBuildDate>Wed, 06 Jul 2011 13:29:44 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Article: Using XNA Content pipeline extensions for localization. (Part 2)</title>
		<link>http://www.sgtconker.com/2010/02/article-using-xna-content-pipeline-extensions-for-localization-part-2/</link>
		<comments>http://www.sgtconker.com/2010/02/article-using-xna-content-pipeline-extensions-for-localization-part-2/#comments</comments>
		<pubDate>Sat, 20 Feb 2010 10:53:43 +0000</pubDate>
		<dc:creator>Sgt. Conker</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[Input]]></category>
		<category><![CDATA[XNA]]></category>
		<category><![CDATA[Content Pipeline]]></category>
		<category><![CDATA[Language]]></category>
		<category><![CDATA[Localization]]></category>

		<guid isPermaLink="false">http://www.sgtconker.com/?p=1116</guid>
		<description><![CDATA[After publishing part one and having a good week to think about part two, I found that some of my design choices, which seemed nice, where actually at bit cumbersome.]]></description>
			<content:encoded><![CDATA[<h4 style="text-align: center;">by <a href="http://roy-t.nl">Roy Triesscheijn</a></h4>
<p><a href="http://www.sgtconker.com/wp-content/uploads/2010/02/flag.png"><img class="alignright" title="flag" src="http://www.sgtconker.com/wp-content/uploads/2010/02/flag-300x194.png" alt="" width="240" height="155" /></a></p>
<h4>Recap</h4>
<p>So… last time we made our strings localizable and added-in a new content manager. Today we are going to make all our content localizable, but first we should revisit and refactor “yesterday’s code”.</p>
<p>After publishing part one and having a good week to think about part two, I found that some of my design choices, which seemed nice, where actually at bit cumbersome.<br />
Also the xml parsing (in BABProcessor) wasn’t as robust as I wanted it to be because I forgot about localized formats for dates and numbers.<br />
Fixing our xml loading is easy, so let’s first fix that!</p>
<p><span id="more-1116"></span></p>
<h4>Refactoring BABProcessor</h4>
<p>Load up your previous code, or the source code from last week and go to the BABProcessor.cs file. In the switch in our method Process we parse our version number without caring for localized number formats.<br />
Because of this the version number “1.0” might be parsed as 10 instead of 1 on some computers. So change the code in case version to this:</p>
<pre class="brush: csharp; title: ;">Double.TryParse(node.InnerText, NumberStyles.Any, CultureInfo.InvariantCulture,out output.version);</pre>
<p>Here we tell Double.TryParse to parse the number with an InvariantCulture, so 1.0 will always be interpreted as 1.<br />
The next troubling line is where we parse dates. In Europe we usually write dates as 31-01-2010 (dd-mm-yyyy) but in the U.S. 01-31-2010 (mm-dd-yyyy) is more common, and in Japan they use 2010-01-31 (yyyy-mm-dd). Based on the computers local settings dates will be parsed differently,<br />
so let’s drop down to the revision date case and change the code to this:</p>
<pre class="brush: csharp; title: ;">DateTime.TryParseExact(node.InnerText, &quot;dd-mm-yyyy&quot;, CultureInfo.InvariantCulture, DateTimeStyles.None, out output.revision);  </pre>
<p>Here we explicitly specify the format used for parsing the revision date in our xml file. (Of course you can change this to something of your liking).<br />
One last thing, I forgot to delete the following line from the package case:</p>
<pre class="brush: csharp; title: ;">Dictionary&lt;string, string&gt; package = new Dictionary&lt;string, string&gt;();</pre>
<p>You can remove this as it’s not used at all, sorry about that, I totally missed that line when doing my final checkup.<br />
Alright so that’s it for cleaing up our BABProcessor.</p>
<h4>Refactoring BabylonContent</h4>
<p>We’re also going to refactor our new content manager. As I was tinkering with the code for localizing other content/assets I noticed that the code for strings was doing some extra unneeded caching.<br />
I forgot that all content we load using our Content Manager is cached for us, so we don’t need the extra caching. (This is not <a href="http://roy-t.nl/index.php/2008/08/27/9/">the first time</a> I forgot this).<br />
But remembering this again allows us to make our content manger much simpler.<br />
Remove the following lines:</p>
<pre class="brush: csharp; title: ;">
private Dictionary&lt;string, Dictionary&lt;string, string&gt;&gt; packages;
language = ProperString(value);
</pre>
<p>Now replace the entire GetString method with this method:</p>
<pre class="brush: csharp; title: ;">
private string GetString(string path, string package, string key, string packageLanguage)
{
	try
	{
		BABFile localization = base.Load&lt;BABFile&gt;(path + package + &quot;.&quot; + packageLanguage);
        if (localization.keys.Keys.Contains(key))
        {
			return localization.keys[key];
        }
    }
	catch (ContentLoadException e){}
	//Now try to load the content using the default language
    //but only if the current language isn't the default language
    //else we would get stuck in ãn infinite loop.
    if (!packageLanguage.Equals(DEFAULTLANGUAGE))
    {
		return GetString(path, package, key, DEFAULTLANGUAGE);
    }
    else
    {
		//The string searched for couldnt be found in the
        //translated file or the default file.
        //Replace this by an exception if you want to test your game better
        return &quot;&quot;;
    }
}
</pre>
<p>As you can see this method is a lot shorter and no longer an exception is thrown if the key doesn’t exist. It also fixes another problem I had with the GetString method, you couldn’t do this:</p>
<pre class="brush: csharp; title: ;">Content.Load&lt;String&gt;(@”SomePath\Package.key”);</pre>
<p>So I fixed that in this version, nothing really new here so I’m moving on to the next subject. Add a new Load method.</p>
<pre class="brush: csharp; title: ;">
public T Load&lt;T&gt;(string assetName, string specificLanguage)
{
	if (typeof(T) == typeof(String))
    {
		string folder = assetName.Substring(0, assetName.LastIndexOf('\\') + 1);
        string file = assetName.Substring(assetName.LastIndexOf('\\') + 1);
        string[] packageKey = file.Split('.');
        if (packageKey.Length &gt; 1)
        {
			//we convert to object and then T because we can't convert a valuetype to a (T) directly
            return (T)(object)GetString(folder, packageKey[0], packageKey[1], specificLanguage);
        }
        else
        {
			return base.Load&lt;T&gt;(assetName);
		}
	}
</pre>
<p>This parses the path for us so our new GetString method works correct. Now replace the method with the signature Load(string assetName) with this one:</p>
<pre class="brush: csharp; title: ;">
public override T Load&lt;T&gt;(string assetName)
{
	return Load&lt;T&gt;(assetName, language);
}
</pre>
<p>Well this means we are all done refactoring. These last changes allow us to load strings (and later on other content) in a specific language, other than the language currently set. While still having our old behaviour when someone just calls the normal Load method.</p>
<h4>The XML file,  BACFile, Importer and Processor</h4>
<p>Just as last time we are going to define an xml format, load that xml file and process it into a special class, this time called BACFile (for Babylon Content).<br />
Remember the general idea is that you can add in new languages fairly easy. We are going to do this by adding an xml file in every folder that contains content. We call this xml file “LocalizedContent.Language.bac”. In this xml file we specify what files are localized versions of other files. Now every time our new content manager is asked to load content it first opens the “LocalizedContent.Lanuage.bac” file (where Language is the current language, note that  the content manager automatically caches these files for us). If the file can’t be loaded or if there exists no key for the given asset, we just load the asset as normal. If there is a file with a key referencing the file we want to load, we get a new filename from the key, and load that one instead.<br />
Simple huh?  Let’s first define a suitable xml-file:</p>
<pre class="brush: plain; title: ;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;XNABabylonContentFile&gt;
  &lt;version&gt;1.0&lt;/version&gt;
  &lt;revisionDate&gt;3-2-2010&lt;/revisionDate&gt;
  &lt;author&gt;Roy Triesscheijn&lt;/author&gt;
  &lt;language&gt;English&lt;/language&gt;
  &lt;!--Files that are already in the correct language can just reference themselves--&gt;
  &lt;!--Or can be left out--&gt;
  &lt;localization&gt;
    &lt;key con=&quot;Texture01&quot; loc=&quot;Texture01&quot;/&gt;
  &lt;/localization&gt;
&lt;/XNABabylonContentFile&gt;
</pre>
<p>As you can see this file defines that for the content (con) “Texture01” the localized english version is “Texture01”.<br />
Now because parsing and processing these .bac files is gonna be almost the same as processing the bab files from part1, I’m just going to put down the code here, with little comments. (You could also just download the sourcecode and copy BACFile.cs, BACImporter.cs and BACProcessor.cs to your project).</p>
<p><strong>BACFile.cs</strong></p>
<p>First we need a container, create a new class, mark it serializable and write code simmilar to this in the class body:</p>
<pre class="brush: csharp; title: ;">
public string language;
public double version;
public string author;
public DateTime revision;
//Other than in the BABFile the BACFile
//doesnt store a direct translation but a string
//naming the translated file.
public Dictionary&lt;string, string&gt; keys;

public BACFile()
{
	keys = new Dictionary&lt;string, string&gt;();
}
</pre>
<p><strong>BACImporter.cs</strong></p>
<p>Now we need an importer, create a new class called BACImporter, and just copy the contents of BABImporter into the new class file. All you need to change is the class name (obviously to BACImporter). And the ContentImporter directives (register extension “.bac” instead of “.bab”, change the display name, and set defaultprocessor to “BACProcessor”.)<br />
Keep the method Import the same, but change the error message so we know it was a babylon content file, and not a babylon string file, that was invalid.</p>
<p><strong>BACProcessor.cs</strong><br />
Again just copy the content of BABProcessor into a new class called BACProcessor. Now change</p>
<pre class="brush: csharp; title: ;">using TOutput = XNABabylon.Importers.BABFile;</pre>
<p>To</p>
<pre class="brush: csharp; title: ;">using TOutput = XNABabylon.Importers.BACFile;</pre>
<p>Change the display name to “XNA Babylon .bac Processor” .  Now in the method Process change the line:</p>
<pre class="brush: csharp; title: ;">TOutput output = new BABFile();</pre>
<p>To:</p>
<pre class="brush: csharp; title: ;">
TOutput output = new BACFile();
</pre>
<p>And change the lines:</p>
<pre class="brush: csharp; title: ;">
if(input.GetElementsByTagName(&quot;XNABabylonStringFile&quot;).Count &gt; 0)
{
	foreach (XmlNode node in input.GetElementsByTagName(&quot;XNABabylonStringFile&quot;)[0].ChildNodes)
</pre>
<p>To:</p>
<pre class="brush: csharp; title: ;">
if (input.GetElementsByTagName(&quot;XNABabylonContentFile&quot;).Count &gt; 0)
{
	foreach (XmlNode node in input.GetElementsByTagName(&quot;XNABabylonContentFile&quot;)[0].ChildNodes)
</pre>
<p>So almost there. Locate the case package, remove it and add  this new case instead</p>
<pre class="brush: csharp; title: ;">
case &quot;localization&quot;:
//Loop trough all the keys and store the name of the original content (con)
//and name of the translated file (loc) into a dictionary
foreach (XmlNode key in node.ChildNodes)
{
    output.keys.Add(key.Attributes[&quot;con&quot;].InnerText, key.Attributes[&quot;loc&quot;].InnerText);
}
break;
</pre>
<p>Test our new additions to the content pipeline extension by creating the .bac file provided a bit above and adding it to your test project, make sure to set the Build Action to compile and to set the bac importer and processor. If everything is well, you should be able to compile without problems.</p>
<h4>Adding new methods to our content manager</h4>
<p>With all this new data available, we should update our content manager to use it. Go to BabylonContent.cs and let’s create a new method with the following signature:</p>
<pre class="brush: csharp; title: ;">private T GetContent&lt;T&gt;(string path, string file, string packageLanguage)</pre>
<p>This method will function just like the GetString method. And will be very short tbh. All the code in the method body is this:</p>
<pre class="brush: csharp; title: ;">
try
{
    BACFile localization = base.Load&lt;BACFile&gt;(path + &quot;LocalizedContent.&quot; + packageLanguage);
    if (localization.keys.Keys.Contains(file))
    {
        return base.Load&lt;T&gt;(path + localization.keys[file]);
    }
}
catch(ContentLoadException e){}

//If we got to here we couldnt load the localization file or localized content
//So try to load the unlocalized/default content.
return base.Load&lt;T&gt;(path + file);
</pre>
<p>As you can see we do nothing more than loading the LocalizedContent.Language.bac file in the requested folder, checking if there is a key for the requested asset, if so load that one, else, or on an exception, just load the normal one.<br />
We can use this for all content types, but that means that all content needs to take the extra steps trough localization, which would be silly if we know in advance that some piece of content doesn’t need to be localized.<br />
We can control it ourselves by adding this method (which we do):</p>
<pre class="brush: csharp; title: ;">
public T Load&lt;T&gt;(string assetName, bool skipLocalization)
{
	if (skipLocalization)
    {
		return base.Load&lt;T&gt;(assetName);
    }
    else
    {
		return Load&lt;T&gt;(assetName);
    }
}
</pre>
<p>But there might be some types of files that we never want to be localized (spritefonts for example). Therefor we are going to make a list of types we do want localized.<br />
Todo this, add the folowing list and helper functions:</p>
<pre class="brush: csharp; title: ;">
//Stores all the types we want our processor to handle
//Types not in this list will just be directly forwarded to the base class
private List&lt;Type&gt; processable;

//'Properties' for the processable list

/// &lt;summary&gt;
/// Add a new type to the processable list.
/// Assets of this type will now be localized
/// when loaded (if possible).
/// &lt;/summary&gt;
public void AddProcessableType(Type t)
{
    processable.Add(t);
}

/// &lt;summary&gt;
/// Removes a type from the processable list
/// Assets of thys type will no longer be
/// localized when loaded.
/// &lt;/summary&gt;
public void RemoveProcessableType(Type t)
{
    processable.Remove(t);
}

/// &lt;summary&gt;
/// Clears all the types from the processable
/// list. No more content will be localized
/// when loaded from now on. Until new
/// types are added via AddProcessableType(..)
/// &lt;/summary&gt;
public void ClearProcessableTypes()
{
    processable.Clear();
}

/// &lt;summary&gt;
/// Returns an array of all types currently
/// in the processable list. Assets of these
/// types will be localized (if possible).
/// &lt;/summary&gt;
public Type[] GetProcessableTypes()
{
    return processable.ToArray();
}
</pre>
<p>This way we can adjust, at runtime, what types of files should not be localized.<br />
In the constructor add the following lines:</p>
<pre class="brush: csharp; title: ;">
processable = new List&lt;Type&gt;();
//Add some default types that we process
processable.Add(typeof(String));
processable.Add(typeof(Texture));
processable.Add(typeof(Texture2D));
processable.Add(typeof(SoundEffect));
</pre>
<p>This way we have some sensible default types that will be localized (unless they are removed from our processable list).<br />
All we need to do now, is adjust our Load&lt;&gt; function to incorporate these changes, and ofcourse we still need to add a call to our GetContent method!<br />
Add these lines in the method with signature</p>
<pre class="brush: csharp; title: ;">public T Load&lt;T&gt;(string assetName, string specificLanguage)</pre>
<p>In between the if and the else (so we get an if, else if, else).</p>
<pre class="brush: csharp; title: ;">
else if (processable.Contains(typeof(T)))
{
string folder = assetName.Substring(0, assetName.LastIndexOf('\\') + 1);
string file = assetName.Substring(assetName.LastIndexOf('\\') + 1);
return (T)GetContent&lt;T&gt;(folder, file, specificLanguage);
}
</pre>
<p>Ah now it should all start coming togheter. Still a few checkups though. The normal (overriden) load method should look like this:</p>
<pre class="brush: csharp; title: ;">
public override T Load&lt;T&gt;(string assetName)
{
	return Load&lt;T&gt;(assetName, language);
}
</pre>
<p>Okay now we have 3 Load methods, let’s recap a bit what they are for.</p>
<ul>
<li>1.	Load(string assetName)</li>
<li>2.	Load(string assetName, bool skipLocalization)</li>
<li>3.	Load(string assetName, string specificLanguage)</li>
</ul>
<p>Okay 1 is just the normal load function. This should normally be used.<br />
Now 2 allows us to bypass all the fancy stuff and just load the file we want, without the localization overhead (for example, for stuff that doesn’t have to be localized).<br />
And 3 is the workhorse, it is called by 1, with as specificLanguage argument, the language currently set to the application. We can aslo call it ourselves with a language we want translation to. (For example for the title screen where we want to display instructions in multiple languages).<br />
(note that only 1 is visible if you don’t cast to BabylonContent).</p>
<h4>Sample project and conclusions</h4>
<p>This time I will not go into more details in how to make a test project. Please download the source code, a test project is acompanied there. It will show you two flags, and based on the flag chosen it will show the rest of the ‘game’ in that language. It will use all 3 different Load methods to demonstrate there uses.</p>
<p><a href="http://www.sgtconker.com/wp-content/uploads/2010/02/flag.png"><img class="alignnone size-medium wp-image-1127" title="flag" src="http://www.sgtconker.com/wp-content/uploads/2010/02/flag-300x194.png" alt="" width="300" height="194" /></a></p>
<p>I think it’s fair to conclude that this way of localization is fairly easy, the amount of code we need is pretty slim. And it’s hot pluggable into any existing game. Once you h ave created new localized content it’s easy to update your game to use it. (Just update the xml file). I think this method  of localization has never been applied before, but I do think it’s a valid, and efficient way.</p>
<p>Please download the sourcode <a href="http://www.sgtconker.com/Downloads/articles/XNABabylonpt2.zip">HERE</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgtconker.com/2010/02/article-using-xna-content-pipeline-extensions-for-localization-part-2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Article: Using XNA Content pipeline extensions for localization.</title>
		<link>http://www.sgtconker.com/2010/01/article-using-xna-content-pipeline-extensions-for-localization/</link>
		<comments>http://www.sgtconker.com/2010/01/article-using-xna-content-pipeline-extensions-for-localization/#comments</comments>
		<pubDate>Sat, 30 Jan 2010 00:24:07 +0000</pubDate>
		<dc:creator>Sgt. Conker</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[XNA]]></category>
		<category><![CDATA[Content Pipeline]]></category>
		<category><![CDATA[Localization]]></category>

		<guid isPermaLink="false">http://www.sgtconker.com/?p=979</guid>
		<description><![CDATA[by Roy Triesscheijn
Introduction
In this tutorial series I will show how to setup a simple Content Pipeline extension. At first it will be used to parse XML files containing text and their translations, in a part 2 we are going to extend our processor to also parse other xml files which contain data about localized textures [...]]]></description>
			<content:encoded><![CDATA[<h4 style="text-align: center;">by <a href="http://roy-t.nl">Roy Triesscheijn</a></h4>
<h4>Introduction</h4>
<p>In this tutorial series I will show how to setup a simple Content Pipeline extension. At first it will be used to parse XML files containing text and their translations, in a part 2 we are going to extend our processor to also parse other xml files which contain data about localized textures and sounds. We extend XNA’s content manager to make use of all this extra data.<br />
<span id="more-979"></span></p>
<p>Doing all this work in a content processor and writing a custom content manager means that we don’t have to change every line of code where we load content to factor in localization. All we need to do is add new content (like a texture with text on it in a different language) and write an xml file that explains our new content manager what files are alternatives to the original file and what language they are in. This approach works on all platforms XNA runs on, because the localization data is compiled into XNB files.</p>
<h4>Part 1: Setup, and localizing strings.</h4>
<p>As this is part 1 of this tutorial we are only going to focus on setting up the content pipeline extension and using it to display strings in different languages<em>. (Note: that hardcoded strings are never loaded via the ContentManager, so you will have to change all hardcoded strings in your code with a call to our new content manager, there isn’t a way around this unfortunately).</em></p>
<h5>The XML File</h5>
<p>Before we fire up Visual Studio we are going to have to think about the XML format for our string translation data. This is what I came up with, but of course you’re free to develop your own format suitable for your needs.</p>
<p><em>(Note: I’ve called my implementation XNABabylon, you’ll probably see that name pop-up now and then).</em></p>
<p>This is the XML format I decided on and will use for the rest of this tutorial.</p>
<pre>
<pre class="brush: plain; title: ;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;XNABabylonStringFile&gt;
  &lt;version&gt;1.0&lt;/version&gt;
  &lt;revisionDate&gt;25-01-2010&lt;/revisionDate&gt;
  &lt;author&gt;Roy Triesscheijn&lt;/author&gt;
  &lt;language&gt;English&lt;/language&gt;
  &lt;!--The package id for a file named PACKAGE.LANGUAGE.bab should be PACKAGE--&gt;
  &lt;package id=&quot;MainMenu&quot;&gt;
    &lt;key id=&quot;start&quot; val=&quot;Start Game&quot;/&gt;
    &lt;key id=&quot;quit&quot; val=&quot;Quit game by pressing escape&quot;/&gt;
    &lt;key id=&quot;motd&quot; val=&quot;Press space to change language&quot;/&gt;
  &lt;/package&gt;
&lt;/XNABabylonStringFile&gt;
</pre>
</pre>
<p>We will use the package id, which is also the first part of the filename, to load the correct file. (The second part of the filename is the language). The package id we will also be used for <a href="http://en.wikipedia.org/wiki/Namespace">namespacing</a>. Each file should contain one package.</p>
<h5>Setting up the solution</h5>
<p>Now we’ve got the formalities out of the way, let’s start coding!</p>
<p>Open up the solution in which you want to add localization (or create a new XNA Game Project). Right click the solution in the solution explorer and choose “Add new project”</p>
<p><a href="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0024.gif"><img style="display: inline; border-width: 0px;" title="clip_image002[4]" src="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0024_thumb.gif" border="0" alt="clip_image002[4]" width="244" height="157" /></a></p>
<p>Add a new ContentPipeLineExtension to your solution. Now we need reference this extension in our game project. To do this you have to add a reference to your extension project in both your Game project <strong>AND </strong>in the games content project.</p>
<p><a href="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0044.gif"><img style="display: inline; border-width: 0px;" title="clip_image004[4]" src="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0044_thumb.gif" border="0" alt="clip_image004[4]" width="201" height="221" /></a><a href="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0064.gif"><img style="display: inline; border-width: 0px;" title="clip_image006[4]" src="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0064_thumb.gif" border="0" alt="clip_image006[4]" width="198" height="204" /></a></p>
<h5>The Importer</h5>
<p>Now we are going to create the classes for our ContentPipeLineExtension project. Create a new class and call it BABImporter, this class is going to Import our ‘xml’ files and convert them to instances of System.Xml.XmlDocument.<br />
As you’ll see this is pretty trivial to do, however we need to make sure XNA understands what our class is for.</p>
<p>Right under the using statements, before the namespace add the following line:</p>
<pre class="brush: csharp; title: ;">using TImport = System.Xml.XmlDocument;</pre>
<p>Here we define that our output type for this importer is going to be an XmlDocument. Now make sure your class inherits from ContentImporter by modifying your class defintion to:</p>
<pre class="brush: csharp; title: ;">public class BABImporter : ContentImporter</pre>
<p>(If needed add the necesairy using statements by right clicking on the text and selecting resolve) Now right above your class definition add the following line:</p>
<pre class="brush: csharp; title: ;">[ContentImporter(&quot;.bab&quot;, DisplayName = &quot;XNA Babylon .bab importer&quot;, DefaultProcessor = &quot;BABProcessor&quot;)]</pre>
<p>This data tells Visual Studio that this class has a special meaning in the editor. If we compile our class, all projects that have a reference to this class’ project will add this importer to the list of importers for content, also if we add new .bab files to a project the editor will automatically choose this importer. You see that we already have a DefaultProcessor named. Here you should fill in the processor that you want assigned to .bab files by default. (Fill in the class name for the processor that we are going to make shortly).</p>
<p>Now all we need to do for the importer is overriding the method Import. So create a new method in this class looking like this:</p>
<pre class="brush: csharp; title: ;">
public override TImport Import(string filename, ContentImporterContext context)
        {
            XmlDocument stringFile = new XmlDocument();
            try
            {
                stringFile.Load(filename);
            }
            catch (Exception e)
            {
                context.Logger.LogImportantMessage(&quot;The file &quot;
                    + filename + &quot; is not valid: &quot; + e.Message);
                throw e;
            }
            return stringFile;
        }
</pre>
<p>As you can see here, all we do is creating a new XmlDocument from the file inputted. Looks simple doesn’t it?</p>
<h5>The BABFile class</h5>
<p>Before we can process this data we need to define a class that can hold all the relevant information after processing. Create a simple class called BABFile like this one that can store all the data from your xml file, make sure to mark it [Serializable].</p>
<pre class="brush: csharp; title: ;">
[Serializable]
    public class BABFile
    {
        public string language;
        public double version;
        public string author;
        public string package;
        public DateTime revision;
        public Dictionary&lt;string string ,&gt; keys;

        public BABFile()
        {
            keys = new Dictionary&lt;string string ,&gt;();
        }
    }
</pre>
<h5>The processor</h5>
<p>Add another class to your extension project and call it BABProcessor, this processor is going to receive the output of the importer as input, and processes it into an instance of the BABFile class. The processor is just a simple XML reader, however we do have to mark the class with a few special attributes so that Visual Studio knows what it’s for.<br />
First add the following lines under the using statements above the namespace declaration:</p>
<pre class="brush: csharp; title: ;">
using TInput = System.Xml.XmlDocument;
using TOutput = XNABabylon.Importers.BABFile;
</pre>
<p>These lines define our input and output types. Now make sure that your class inherits from ContentProcessor (resolve the using statements if needed).<br />
And add this line above your class declaration:</p>
<pre class="brush: csharp; title: ;">[ContentProcessor(DisplayName = &quot;XNA Babylon .bab Processor&quot;)] </pre>
<p>This tells visual studio how to display this content processor in the dropdown menus of content in projects that reference this project. All we need to do now is overide the method Process and add some basic XML reading, I’ll just put the code down right here because I don’t think it’s hard to understand, but feel free to ask questions.</p>
<pre class="brush: csharp; title: ;">
public override TOutput Process(TInput input, ContentProcessorContext context)
        {
            TOutput output = new BABFile();

            //We traverse the XML file as usual and store certain elements into the
            //output object;
            if(input.GetElementsByTagName(&quot;XNABabylonStringFile&quot;).Count &gt; 0)
            {
                foreach (XmlNode node in input.GetElementsByTagName(&quot;XNABabylonStringFile&quot;)[0].ChildNodes)
                {
                    switch (node.Name)
                    {
                        case &quot;version&quot;:
                            Double.TryParse(node.InnerText, out output.version);
                            break;
                        case &quot;revisionDate&quot;:
                            DateTime.TryParse(node.InnerText, out output.revision);
                            break;
                        case &quot;author&quot;:
                            output.author = node.InnerText;
                            break;
                        case &quot;language&quot;:
                            output.language = node.InnerText;
                            break;

                        case &quot;package&quot;:
                            //Loop trough all the keys and store the key id and translated value into a dictionary
                            Dictionary&lt;string string ,&gt; package = new Dictionary&lt;string string ,&gt;();
                            foreach (XmlNode key in node.ChildNodes)
                            {
                                output.keys.Add(key.Attributes[&quot;id&quot;].InnerText, key.Attributes[&quot;val&quot;].InnerText);
                            }
                            break;
                    }
                }
            }

            return output;
        }
</pre>
<p>Now compile and resolve any errors you might have (there should be none). Testing the first files Go to your GameProject and add a new xml file to the Content project in there. (Right click it and choose add new). Rename the xml file to “MainMenu.English.bab” and select it in visual studio. Now in this file’s properties make sure to set build action to “Compile” and set the correct importer and processor as show below.</p>
<p><a href="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image00210.gif"><img style="display: inline; border-width: 0px;" title="clip_image002[10]" src="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image00210_thumb.gif" border="0" alt="clip_image002[10]" width="244" height="187" /></a></p>
<p>(If the importer and processor don’t show up, make sure that the reference to your extension project is both in the Content project’s references and in the encapsulating game project’s reference the latter we don’t need yet, but why not add it already).</p>
<p>Open up MainMenu.English.bab and just paste in the content from the sample xml file, located at the beginning of this tutorial. Then create another file called MainMenu.Dutch.bab set the same properties and paste in the following xml data:</p>
<pre>
<pre class="brush: plain; title: ;">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;XNABabylonStringFile&gt;
  &lt;version&gt;1.0&lt;/version&gt;
  &lt;revisionDate&gt;25-01-2010&lt;/revisionDate&gt;
  &lt;author&gt;Roy Triesscheijn&lt;/author&gt;
  &lt;language&gt;Dutch&lt;/language&gt;
  &lt;!--The package id for a file named PACKAGE.LANGUAGE.bab should be PACKAGE--&gt;
  &lt;package id=&quot;MainMenu&quot;&gt;
    &lt;key id=&quot;start&quot; val=&quot;Start Spel&quot;/&gt;
    &lt;key id=&quot;quit&quot; val=&quot;Stop het spel door te drukken op escape&quot;/&gt;
    &lt;key id=&quot;motd&quot; val=&quot;Druk op spatie om van taal te veranderen&quot;/&gt;
  &lt;/package&gt;
&lt;/XNABabylonStringFile&gt;
</pre>
</pre>
<p>So we now have English and Dutch language files. Test if everything still compiles (it should).</p>
<p>The Babylon content manager<br />
We can now import our files and we can load them using the original content manager with something like:</p>
<pre class="brush: csharp; title: ;">Content.Load(“MainMenu.English”); </pre>
<p>And manually use the BABFile’s dictionary to find our translation. However since in part two we are going to write a new ContentManager, why not start a bit earlier. Go to your Extension project again and create a new class called BabylonContent and make it look like below. I think the comments sum up nicely what the methods do but let’s walk trough them quickly. The most important method, GetString, tries to find a string by looking in a package and it’s keys. First it tries to load the file associated with the current language. If that fails it loads the file for the default language and tries to find the string there. If this fails aswell we simply return an empty string. (For debugging purposes you might want to throw a hard error here, so you don’t miss missing strings).</p>
<p>You see that in the overridden Load method we now have a special section for strings. You can now do Load(“MainMenu.Start”); and get the translation for “Start Game” in the current language. The other methods are just helpers for creating ‘proper’ strings and some bookkeeping. Nothing that should really blow your mind.</p>
<pre class="brush: csharp; title: ;">
public class BabylonContent : ContentManager
    {
        public BabylonContent(IServiceProvider serviceProvider, string rootDirectory)
            : base(serviceProvider, rootDirectory)
        {
            packages = new Dictionary&lt;string , string Dictionary&gt;&lt;string ,&gt;&gt;();
            Language = DEFAULTLANGUAGE;
        }

        /// &lt;summary&gt;
        /// Load strings using &amp;quot;PackageName.Key&amp;quot;, load other files normally.
        /// &lt;/summary&gt;
        public override T Load&lt;t&gt;(string assetName)
        {

            if (typeof(T) == typeof(String))
            {
                string[] packageKey = assetName.Split('.');
                if (packageKey.Length &gt; 1)
                {
                    return (T)(object)GetString(packageKey[0], packageKey[1]);
                }
                else
                {
                    throw new Exception(assetName + &amp;quot; is not of the format 'PackageName.Key'&amp;quot;);
                }
            }
            else
            {
                return base.Load&lt;t&gt;(assetName);
            }
        }

        /// &lt;summary&gt;
        /// Given a package and a key finds the corresponding string in the
        /// language currently set in .Language
        /// &lt;/summary&gt;
        public string GetString(string package, string key)
        {
            return GetString(package, key, language);
        }

        /// &lt;summary&gt;
        /// Given a package and a key finds the corresponding string in the
        /// language requested
        /// &lt;/summary&gt;
        public string GetString(string package, string key, string packageLanguage)
        {
            package = package.ToLower();
            key = key.ToLower();
            packageLanguage = ProperString(packageLanguage);

            if (packages.Keys.Contains(package))
            {
                return (packages[package])[key];
            }

            try
            {
                BABFile bab = base.Load&lt;babfile&gt;(package + &amp;quot;.&amp;quot; + packageLanguage);
                packages.Add(package, bab.keys);
            }
            catch (ContentLoadException e)
            {
                //Now try to load the content using the default language
                //but only if the current language isn't the default language
                //else we would get stuck in infinite exception recursion.
                if (!packageLanguage.Equals(DEFAULTLANGUAGE))
                {
                    return GetString(package, key, DEFAULTLANGUAGE);
                }
            }

            if (packages.Keys.Contains(package))
            {
                return (packages[package])[key];
            }

            //The string searched for couldnt be found in the
            //translated file or the default file.
            return &amp;quot;&amp;quot;;

        }

        /// &lt;summary&gt;
        /// Converts LaNGuAGe to Language
        /// &lt;/summary&gt;
        private string ProperString(string value)
        {
            if (value.Length &gt; 0)
            {
                value = value.ToLower();
                value = value.ToUpper()[0] + value.Substring(1); ;
            }
            return value;
        }

        private Dictionary&lt;string , string Dictionary&gt;&lt;string ,&gt;&gt; packages;

        private string language;
        public string Language
        {
            get
            {
                return language;
            }
            set
            {
                //Set the new language and clear the translation dictionary
                if (language == null || !language.Equals(ProperString(value)))
                {
                    language = ProperString(value);
                    packages.Clear();
                }
            }
        }

        //Change this variable to the default language your content files are in
        //This value will be used as a fallback for untranslated content
        private static string DEFAULTLANGUAGE = &amp;quot;English&amp;quot;;
    }
</pre>
<p>(In the next tutorial we are going to override the the Load&lt;&gt; method for all types to add ‘automatic’ support for localized textures and sounds).</p>
<h5>A usage example</h5>
<p>Now we’ve got all this code and nothing to show for. I’m now going to show you a couple of lines that should show you how to use this new content manager to load localized strings from files. (if you don’t want to do this yourself feel free to just download the sourcecode and run the sample code) Go to your game project and open up your game’s class (Usually Game1.cs).<br />
Define the following two members:</p>
<pre class="brush: csharp; title: ;">
SpriteFont font;
KeyboardState oldState;
</pre>
<p>Now add the following line to the end of the constructor.</p>
<pre class="brush: csharp; title: ;">Content = new BabylonContent(Content.ServiceProvider, Content.RootDirectory); </pre>
<p>This line overwrites the default ContentManager with our newly created instance which is capable of loading strings and doing some localization. (And which will do much more in part 2).<br />
Add the following line to LoadContent:</p>
<pre class="brush: csharp; title: ;">font = Content.Load(&quot;Arial&quot;);</pre>
<p>And change the update method to look like this:</p>
<pre class="brush: csharp; title: ;">
           KeyboardState newState = Keyboard.GetState();

            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                newState.IsKeyDown(Keys.Escape))
                this.Exit();

            if(newState.IsKeyDown(Keys.Space) &amp;amp;&amp;amp; !oldState.IsKeyDown(Keys.Space))
            {
                //We can cast Content to type BabylonCOntent for extra
                //functionality
                BabylonContent BContent = (BabylonContent)Content;
                if (BContent.Language == &amp;quot;English&amp;quot;)
                {
                    BContent.Language = &amp;quot;Dutch&amp;quot;;
                }
                else
                {
                    BContent.Language = &amp;quot;English&amp;quot;;
                }
            }
            oldState = newState;

            base.Update(gameTime);
</pre>
<p>And finally the draw method to look like this:</p>
<pre class="brush: csharp; title: ;">
GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            spriteBatch.DrawString(font, Content.Load&lt;string&gt;(&amp;quot;MainMenu.start&amp;quot;), Vector2.Zero, Color.White);
            spriteBatch.DrawString(font, Content.Load&lt;string&gt;(&amp;quot;MainMenu.motd&amp;quot;), new Vector2(0, 150), Color.White);
            spriteBatch.DrawString(font, Content.Load&lt;string&gt;(&amp;quot;MainMenu.quit&amp;quot;), new Vector2(0, 250), Color.White);
            spriteBatch.DrawString(font, ((BabylonContent)Content).Language, new Vector2(0, 350), Color.Gray);

            spriteBatch.End();

            base.Draw(gameTime);
</pre>
<p>Now as you can see we can just use Content.Load to load our localized strings. Let’s fire up our demo, press space to change between English and Dutch.</p>
<p><a href="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image00212.gif"><img style="display: inline; border: 0px;" title="clip_image002[12]" src="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image00212_thumb.gif" border="0" alt="clip_image002[12]" width="193" height="244" /></a><a href="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0048.gif"><img style="display: inline; border: 0px;" title="clip_image004[8]" src="http://www.sgtconker.com/wp-content/uploads/2010/01/clip_image0048_thumb.gif" border="0" alt="clip_image004[8]" width="243" height="244" /></a></p>
<h4>Conclusion</h4>
<p>With a relatively little amount of code we made feature full string localization that can be plugged into games easily. If you have a lot of hardcoded strings in your game it might take a while to put them all into xml files and change them to Content.Load&lt;&gt; instructions. In the next part we are going to make our content manager localize textures and sound files. Because there we already use Content.Load&lt;&gt; no code has to be changed after plugging in the new content manager. All we need to do is make some xml files.</p>
<p>This approach to localization is flexible and enforces a couple of good practices (like not using hardcoded strings). I think it’s easy to built in to an almost completed game. And games that are available in many languages might be more enjoyable for people who are not native speakers in the language your game uses. Using xml files in a package like structure makes it easy for multiple translators to work on different localizations of the strings at the same time, without having to look at source code or using complex tools.</p>
<p>Only loading the languages that are requested make this code pretty efficient, however the code might need a few iterations of ‘clean up’ which is always the case with “first release” code.</p>
<p><a href="http://www.sgtconker.com/Downloads/articles/XNABabylon.zip">Download Sample Project</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgtconker.com/2010/01/article-using-xna-content-pipeline-extensions-for-localization/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

