Butterfat Internationalization (b28n)

The source can be found on svn here.

There existed no (good) GNU gettext type interface for javascript. Thus was born Butterfat Internationalization (b28n). Instead of the traditional PO file, b28n uses an xml file with a similar dtd.

The following is taken from the header in b28n.js:

This script provides i18n support via a basic replica of GNU's gettext api. To use it, pick a domain ("messages" is the traditional domain). For each language, create a file named <domain>_<lang>.xml, where lang is one of the two letter abbreviations found on the Library of Congress Codes for the Representation of Names of Languages (for instance, messages_en.xml is a good start). The script will first look for a cookie with the name "language", and if it is not set then it uses the browser langague. It should be noted that this "browser langague" is just the "navigator.language" or "navigator.browserLanguage" value, and NOT the "Accept-Language" header sent by the browser. This is something that should be set by the server, such as in a cookie. The preferable method is for the server to take the "Accept-Language" header value and store it in a permanent cookie, allowing this script to henceforth know which po file to use. The text within the msgid and msgstr should, obviously be html encoded. Your javascript does not have to be html encoded. For example, the call _("test\"test") with a message of <message msgid="test&quot;test" msgstr="test&quot;test&quot;test" /> would return "test\"test\"test".

The po file should have the following syntax:

<po creationdate ="2005-10-12 12:08-0500" translator="FULL NAME EMAIL@ADDRESS">
  <message msgid="this is english" msgstr="this is spanish" />
  <message msgid="this is %s" msgstr="another language called %s" />
</po>

The attributes for <po> are optional. The html file needs only to include the b28n.js file and then set the text domain via Butterlate.setTextDomain(<domain>,<location>). <domain> is the domain you used to create the above xml file, and <location> is the HTTP accessable directory of that xml file (so, http://<FQDN>/messages_en.xml). Whenever you are showing text, just call the function _(<text>) or Butterlate.gettext(<text>) and Butterlate will handle the rest. The following example will produce a page that says "this is spanish" (assuming you set up the setTextDomain correctly and make the above xml available).

<html>
  <head>
    <title>I18N Test</title>
    <script type="text/javascript" src="b28n.js"></script>
    <script type="text/javascript">
      Butterlate.setTextDomain("messages","http://butterfat.net/~bmuller/i18n");
      function startUp() {
        var test = document.getElementById("test");
        test.innerHTML = _("this is english");
        //test.innerHTML = __("this is %s",new Array("klingon")); //produces "another language called klingon"
      }
    </script>
  </head>
  <body onload="startUp();">
    <div id="test">
  </body>
</html>

There is also the ability to have variables in your translations. For instance, if you have a message that says "hello <username>, how are you?" and the translation ends up being "blah blah <username> blah" you need a way of handling the variable. In the xml file, just use "%s" to denote a variable's placement- for instance: <message msgid="hello %s, how are you?" msgstr="blah blah %s blah" />. Then in your javascript call either the function __(<msgid>,<array of translations>) or vgettext(<msgid>,<array of translations>). The array of translations is just an array of strings in the same order as your %s's. If there are more array strings than variables, the extras are ignored. If there are more %s's than array strings, the last string in the array is simply repeated as many times as necessary. If the array is empty, then the msgstr from the xml file will be returned as-is.

*NOTE*: If the browser is set to a language for which you do not have an xml file, the original text will just be displayed and there will be no error messages. The same goes for any text you did not define in the xml file if it does exist.