07 November 2007

Discovering the wonders of stringBundle.getFormattedString()

Localization in an XUL-based app or extension is accomplished by putting all UI strings into DTD or .properties files and bundling them into a Mozilla XPI. The latter type of file is used for storing UI strings that will be programmatically assigned to XUL elements like <label> and <description>, etc. during run-time. In this situation, one would need to define a <stringbundle> element in the XUL document and set its src attribute to the chrome URL of the .properties file containing the localized UI strings. Then to get the UI string, one would do the following in the JavaScript source:
// Get the <stringbundle> element in the XUL document
var strBundle = document.getElementsByTagName("stringbundle").item(0);

// Get the UI string
var theString = strBundle.getString("foobar");
The variable theString will then contain the localized string defined in the XUL document.

A problem for which I've discovered a solution only recently is how to embed another string within a localized string. This typically happens when the UI string contains one or more substrings that are only determined at run-time, such as file I/O error messages that gives the name of the file or folder.

Somehow I overlooked a method of the <stringbundle> object that can accomplish what I needed: getFormattedString(). The idea is that the UI string contains placeholders that will be substituted at run-time with another string that is provided in an array parameter. Each nth element in the array parameter corresponds to the nth placeholder in the string -- basically, the same idea in the C library function printf().

After reading the documentation on XULPlanet, I tried it out. I modified the UI string that defines the error message that appears if the user attempts to import an invalid Clippings file:
alertImportFailed = Cannot read file: \"%s\"\nThe selected file may not be a valid Clippings file.
Then in clippings.js, which contains Clippings Manager's UI logic, I modified the code that gets the error message as follows:
// gStrBundle is a global variable referencing the <stringbundle> element defined in clippings.xul
// path contains the full path of the file the user attempted to import
var err = gStrBundle.getFormattedString("alertImportFailed", [path]);
But it didn't work: only the first character in path was returned. So I did some searching on Google and came across the documentation on Devmo that indicated that an uppercase "S" is needed in the placeholder.
alertImportFailed = Cannot read file: \"%S\"\nThe selected file may not be a valid Clippings file.
Now it works. Armed with this knowledge, I even made the effort to point this out to a developer who had written a "bug fix" to work around the lowercase "s" problem.

And one other hint on the subject of localizing strings: to embed a non-breaking space in a UI string in a .properties file, use the hexadecimal Unicode escape character \u00A0.

No comments: