When we began work on our new XUL-based framework we decided that translations should be built in at a low level. Most obviously a translation could be used to switch our applications to run in French, Spanish or some other language. Less obviously – but perhaps more importantly – a translation could be used to allow our customers to use their own terminology, rather than ours. So what we call a “batch” might be a “lot” or an “order” to another company. In our framework this difference is handled as a translation.
Clearly it would be impractical for us to handle translations for our customers. Yes, we might ship with a few default languages, but individual terminology would best be handled by the customers themselves. This implies having a user interface to let them create and edit translated strings, plus a mechanism to fall back to our default text when no translation was supplied.
We decided to take a two-step approach to this problem. The translation information is stored within the database. We provide an interface that lets the customer – and our own developers – manipulate this data. They can create their own “language” – whether a real language or just a collection of local terminology – and within each language they can edit the individual text strings.
What we didn’t want to do, however, was hit the database for a load of translation data every time an application is loaded. That’s where the second step comes in: we create a file on the web server for each “language”. Whenever a change is made to the translations these files are re-generated, ensuring that we always send the latest translation data. These files are “DTD” files, containing a series of lines looking like this:
<!ENTITY menu.file "File">
<!ENTITY menu.file.open "Open">
<!ENTITY menu.file.save "Save Changes">
<!ENTITY menu.file.quit "Quit">
That is, they all map an internal reference (“menu.file.save”) to a textual string (“Save Changes”). Within our XUL we can use the internal reference directly, and it is automatically replaced with the textual string when the application is displayed. For example, this line of XUL would produce a button with the text “Save CHanges” on it:
<button label="&menu.file.save;" />
In order for our XUL to do this conversion, it needs to know about the DTD file. In an ideal world we would just dynamically inject a link to the DTD into the top of the XUL file using ASP.Net. That would let us use one DTD across all the individual pages that make up our application, and Firefox would cache it so that it only gets downloaded once. In practice, bug #22942 prevents this from working, so instead we use ASP.NET to inject the whole DTD file into the top of every XUL page that gets requested.
So, in short, when the user opens on of our applications we determine what their preferred language is, and inject the appropriate DTD file directly into the XUL page that is sent to their browser. If bug #22942 ever gets fixed we will be able to easily change the code to inject a reference to the DTD file, rather than the DTD file itself. This will let the browser’s cache do its job and hugely reduce the amount of data that we send. Until then the current approach works well enough, even if it’s not quite optimal.