The hangman game on a Pocket PC configured for Catalan
Introduction
This article is a basic introduction to the internationalisation (or should that be internationalization) features of the .NET Compact Framework. The .NET Compact Framework has, just like its bigger brother, extensive features that assist the developer to write applications for multiple regions. Much of this support is automatic, which makes development so much easier. However, there are some things that should be thought of in advance to help give your application international support.
For the purposes of demonstrating the features, I have based the code around a Hangman game which I hope you also find fun and entertaining in itself even if you are not interested in internationalization. The final game is available in English, French, Catalan and Spanish. Curiously, these are also the major languages spoken in Europe on the Prime Meridian.
Background
An internationalized application (also known as a globalized application) is one where the application is enabled to handle international data. The specific translations are known as "localizations".
Hangman Implementation
The Hangman game has a LocaleManager
class which is used to manage the resources and means that the game can have the language altered while it is running. The static initialiser for the class sets the current culture to be the same as the current UI Culture so that at least the game will start in the same language as is set in the Regional Settings for the Pocket PC (assuming, of course, that the game has resources in that language).
static LocaleManager()
{
_current = CultureInfo.CurrentUICulture;
Assembly asm = Assembly.GetExecutingAssembly();
_resMan = new ResourceManager("Hangman.hmlocal", asm);
}
During the game, any time a string is used that will be output to the user, then the text is retrieved from the correct set of resources by the LocalManager.GetString(string id)
method:
public static string GetString(string id)
{
return _resMan.GetString(id, _current);
}
You can see that this is a very simple method that just retrieves the string with the given identifier (I'll come to them in a moment) and for the current culture, that was retrieved in the static intialiser. If a culture is specified that is not available then the framework will use the fallback culture as a backstop so that at least something is available. The following diagram illustrates the organization of the culture information in .NET (which is derived from RFC 1766[^]).
I'll come to how this is translated into the application in a moment. The green layer at the top of the hierarchy chart is the fallback. This is what will be used if the system cannot match any other culture. The second level, with the aqua blue background, is the primary language tag. This level doesn't define things like culture specific date formatting (think of the difference between British English and American English - 1/2/03 can be the first of February or the second of January, or between using commas [in Continental Europe] or full stops [in English speaking countries] for the decimal place marker in a number). Finally, the third level, with the yellow background, adds the first sub-tag which in effect adds the region coding. The region coding helps define things like the date and number formats.
Validating User Input
For the purpose of the game, the user is permitted to type in any letter, however the same letter can have several representations. Traditionally, in English speaking countries, this type of comparison is taught along the lines of setting the letter case to be the same then comparing. For example:
if (myStringA.ToLower() == myStringB.ToLower()) {}
Certainly, this is what I was taught in university and I don't see much evidence of that changing.
In a more international context, for example, the letter "a" can be written in upper case "A", with an acute "á", a grave "à", a circumflex "â", a tilde "ã", a dieresis "ä" which is sometimes also called an umlaut, a ring above "å" and so on (we won't even go into macrons, breves, ogoneks and all manner of other modifications and their uppercase equivalents to such a simple letter).
It is possible to compare the letter "a" with all these variations, but myChar.ToLower()
just won't be sufficient in this case. The characters need to be converted to a string, and the two strings can then be compared with the InvariantCulture
. The LocaleManager
class in the game implements this as:
public static bool IsEqual(char a, char b)
{
string sa = StringInfo.GetNextTextElement(new string(a,1));
string sb = StringInfo.GetNextTextElement(new string(b,1));
int res = CultureInfo.InvariantCulture.CompareInfo.Compare(sa, sb,
CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace);
return res == 0;
}
Localized Resources
With Visual Studio, one of the easiest things to do is to create a resx file for each language (and region) that is supported. These translate into satellite assemblies when the solution is built. The resx file is simply an XML file that contains the localized strings. In the demonstration game, the fallback name of the resources files is "hmlocal", and to add extra cultures, all that needs to happen is an additional resx file created in the format fallback.language[-region].resx (the region element is optional).
Although the demonstration application doesn't show this, the resx files don't just have to contain strings, they can contain any arbitrary objects such as graphics, audio, or anything else that you can dream up.
For example (a subsection of hmlocal.resx):
<data name="play_timed_intro">
<value>You have {0} seconds to guess the word or phrase!</value>
</data>
<data name="play_intro">
<value>You have to guess the word or phrase!</value>
</data>
<data name="play_hurry">
<value>Hurry! You have {0} second left!</value>
</data>
and it's Catalan equivalent from hmlocal.ca.resx:
<data name="play_timed_intro">
<value>Tens {0} segons per endivinar la paraula o frase!</value>
</data>
<data name="play_intro">
<value>Has d'endivinar la paraula o frase!</value>
</data>
<data name="play_hurry">
<value>Vinga! Et queden nomes {0} segons!</value>
</data>
File structure of the Satellite Assemblies relative to the main assembly
When the project is built, Visual Studio places all the localized resources into satellite assemblies. The fallback resources go in the main assembly and culture dependent assemblies are created in an appropriate subfolder immediately below the main assembly.
Differences between the CF and the full .NET Framework
In the full .NET Framework, it is possible to set the CurrentCulture
on a CurrentThread
which, in turn, changes the default culture for your application. However, this is not available in the Compact .NET Framework. In normal circumstances, it would be highly unlikely that an application would want to do this and so therefore it is not supported.
Points of Interest
If you are interested in why the Options tab and the Game menu page reference to "Player 1" when there can be only one player is because at some point in the future, I intend to expand the game to allow a two player mode where players can communicate (via IR or Bluetooth) the words to each other.
References and Resources
If you want to learn more about localization and globalization then you may find the following link useful.
Thanks to
Thanks to my good friend, and fellow CPian, David Llamas[^] who did the Catalan translation and corrected my Spanish translation (apparently, I used old fashioned words that a grandmother would use). And also to Jeannette who is a friend of my mother's and did the French translation, despite never having used a computer before.
History
This is version 1.0.