Since I already played around with XML in C#, this part of the project was easier to do than before.
What is it supposed to do?
Basically, I could simply hard-code most of my strings used in the game directly into the code – no one would notice the difference anyway. But obviously, this is not a very good idea, both because editing strings and later translating them becomes a pain.
Creating some data that would allow me to get strings out of an XML file would solve this problem – and, if the code is good enough, be reusable in later games.
It would allow me to edit text independently of the game code and add translations on a later date.
Preliminary format of the XML
The XML file has the following (as of now experimental) format:
In order to allow for different distribution formats, parts of this file can change, as reflected within the <meta>
tag.
The file above could contain all strings in all languages for all levels and screens. The format allows for splitting these files up, i. e. into single level files (one file for each level) or single language files (one file for each available language) or a combination thereof.
A a mono-grouped, monolingual file would then look like this:
Comparing both examples, you can see certain features:
- The XML format does no refer to “levels”, but to “groups”, in order to take into account that also menu and option screens can have their own groups.
- Whenever either
language
orgrouping
have been defined within themeta
tag, the attributes aren’t necessary anymore later on. - All
id
s are strings, in order to allow for more understandable coding and to reduce human error while matching the ID and the actual string in the code. - The format should also allow for audio files to be matched with the strings. This function is not yet implemented, though.
The format has yet to be formalised in a RelaxNG schema or a DTD.
How to get the strings out of the file
Again, first I thought I could do stuff with a XMLReader – before I realised that I didn’t want to program a glorified typewriter. An anonymous commenter pointed me to LINQ, which sounded great, but isn’t available in Unity, since it uses an older version of the framework.
What’s available is XmlDocument, though – and how comfortable it is. Using simple XPath queries, the document can be easily traversed and the necessary strings can be pulled from it.
The following code prepares the necessary variables:
public class XMLStringReader : MonoBehaviour { public string localizedStringsFile; string language; string grouping; XmlDocument root;
void Start ()
{
// Get the file into the document.
root = new XmlDocument ();
root.Load (localizedStringsFile);
language = root.SelectSingleNode ("localizableStrings/meta/language").InnerText;
grouping = root.SelectSingleNode ("localizableStrings/meta/grouping").InnerText;
}
The following code is used to retrieve the strings.
} catch (NullReferenceException ex) {
string s = "Missing string (" + ex.ToString () + ")";
return s;
}
}
/**
*
string n = root.SelectSingleNode (“localizableStrings” + “/group[@id=’” + level + “’]” + “/string[@id=’” + id + “’]” + “/text”).InnerText;
return n;
} catch (NullReferenceException exa) {
// Apparently, there was nothing there ... well, so
// we simply try and see whether we can use the last string as
// a language code.
try {
string n = root.SelectSingleNode ("localizableStrings" + "/group/string[@id='" + id + "']" + "/text[@lang='" + level + "']").InnerText;
return n;
} catch (NullReferenceException exb) {
string s = "Missing string (" + exb.ToString () + ")";
return s;
}
}
}
/**
*
The code may not be really beautiful right now and could be written more clearly and compact. It was more an exercise in overloading methods and using try/catch structures …
The method overloading allows for coding, since only the necessary parameters have to be given (at least, that’s the idea).
Ideas that got trashed along the way
For a brief moment I considered changing that all to a Drupal-style translation system, in which the actual strings in the original language would be string IDs, in order to allow coding along the lines of Button(rect, t(Load Game))
; where the ID string could be used as a fallback when the language wasn’t available.
I decided against it, because of the following reasons:
- Text is, again, distributed over actual game code and an XML file: whenever the wording of the original has to be changed, it has to be changed in the code. This defeats the original purpose of the code.
- Whenever the original wording is changed in the game code, the translations break (this is also a problem in Drupal, even though strings are handled in a database).
- Unsanitised input – depending on the text, it could wreak havoc both in the code as well as the XML.
What still has to be done
- The strings aren’t sanitised – bad things could happen if someone tried to sneak in some strings on his own, I guess.
- The method using two parameters has to be rewritten – it seems to be too clumsy right now.