Localization

XUL and XML provide entities which are a convenient way of allowing localization.

Entities

Many applications are built such that translating the interface into a different language is as simple as possible. Usually, a table of strings is created for each language. Instead of hard-coding text directly into an application, each piece of text is only a reference into the string table. XML provides entities which can be used for a similar purpose.

You should already be familiar with entities if you have written HTML. The codes < and > are examples of entities which can be used to place less than and greater than signs into the text. XML has a syntax which allows you to declare custom entities. You can use these so that the entity is replaced with its value, which can be a string of text. Entities may be used whenever text occurs, including the values of attributes. The example below demonstrates the use of an entity in a button.

<button label="&findLabel;"/>

The text that will appear on the label will be the value that the entity &findLabel has. A file is created which contains the entity declarations for each supported language. In English, the &findLabel entity will probably be declared to have the text 'Find'.

DTD Files

Entities are declared in DTD (Document Type Declaration) files. These types of files are normally used to declare the syntax and sematics of a particular XML file, but they also allow you to declare entities. In the Mozilla chrome system, you will find DTD files located in the locales subdirectory. You would normally have one DTD file (with an extension dtd) per XUL file.

If you look in the chrome directory, you should see an archive for your language. (en-US.jar is the default for English.) You might have locale files in multiple languages, for example, US English (en-US) and French (fr). Inside these archives, you will find the files that hold the localized text for each window. The structure of the archives is very similar to the directory structure used for skins.

Inside the archives, you would place your DTD files in which you declare entities. Typically, you will have one DTD file for each XUL file, usually with the same filename except with a .dtd extension. So, for the find files dialog, we will need a file called findfile.dtd.

For non-installed chrome files, you can just put the DTD file in the same directory as the XUL file.

Once you have created a DTD file for your XUL, you will need to add a line to the XUL file which indicates that you want to use the DTD file. Otherwise, errors will occur as it won't be able to find the entities. To do this, add a line of the following form somewhere near the top of the XUL file:

<!DOCTYPE window SYSTEM "chrome://findfile/locale/findfile.dtd">

This line specifies that the URL indicated is to be used as a DTD for the file. In this case, we have declared that we want to use the findfile.dtd DTD file. This line is typically placed just before the window element.

Declaring Entities

The entities are declared using a simple syntax as shown below:

<!ENTITY findLabel "Find">

This example creates an entity with the name findLabel and the value Find. This means that wherever the text &findLabel appears in the XUL file, it will be replaced with the text Find. In the DTD file for a different language, the text for that language will be used instead. Note that entity declarations do not have a trailing slash at the end of them.

For example, the following text:

<description value="&findLabel;"/>

is translated as:

<description value="Find"/>

You would declare an entity for each label or string of text that you use in your interface. You should not have any directly displayed text in the XUL file at all.

In addition to using entities for text labels, you should use them for any value that could be different in a different language. Access keys and keyboard shortcuts for example.

<menuitem label="&undo.label;" accesskey="&undo.key;"/>
<!ENTITY undo.label "Undo">
<!ENTITY undo.key "u">

The example above uses two entities, one for the label on the Undo menu item and the second for the access key.

Changing the Find Files Dialog

Let's take a look at how we would put all of this together by modifying the find files dialog so that it uses a DTD file for all of its text strings. The entire XUL file is shown below with the changes shown in red.

<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="findfile.css" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://findfile/locale/findfile.dtd">

<window
  id="findfile-window"
  title="&findWindow.title;"
  persist="screenX screenY width height"
  orient="horizontal"
  onload="initSearchList()"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script src="findfile.js"/>

<popupset>
   <popup id="editpopup">
     <menuitem label="Cut" accesskey="&cutCmd.accesskey;"/>
     <menuitem label="Copy" accesskey="&copyCmd.accesskey;"/>
     <menuitem label="Paste" accesskey="&pasteCmd.accesskey;" disabled="true"/>
   </popup>
</popupset>

<keyset>
   <key id="cut_cmd" modifiers="accel" key="&cutCmd.commandkey;"/>
   <key id="copy_cmd" modifiers="accel" key="&copyCmd.commandkey;"/>
   <key id="paste_cmd" modifiers="accel" key="&pasteCmd.commandkey;"/>
   <key id="close_cmd" keycode="VK_ESCAPE" oncommand="window.close();"/>
</keyset>

<vbox flex="1">

 <toolbox>

  <menubar id="findfiles-menubar">
    <menu id="file-menu" label="&fileMenu.label;"
        accesskey="&fileMenu.accesskey;">
      <menupopup id="file-popup">
        <menuitem label="&openCmd.label;"
                  accesskey="&openCmd.accesskey;"/>
        <menuitem label="&saveCmd.label;"
                  accesskey="&saveCmd.accesskey;"/>
        <menuseparator/>
        <menuitem label="&closeCmd.label;"
                  accesskey="&closeCmd.accesskey;" key="close_cmd" oncommand="window.close();"/>
      </menupopup>
    </menu>
    <menu id="edit-menu" label="&editMenu.label;"
          accesskey="&editMenu.accesskey;">
      <menupopup id="edit-popup">
        <menuitem label="&cutCmd.label;"
                  accesskey="&cutCmd.accesskey;" key="cut_cmd"/>
        <menuitem label="&copyCmd.label;"
                  accesskey="&copyCmd.accesskey;" key="copy_cmd"/>
        <menuitem label="&pasteCmd.label;"
                  accesskey="&pasteCmd.accesskey;" key="paste_cmd" disabled="true"/>
      </menupopup>
    </menu>
  </menubar>

  <toolbar id="findfiles-toolbar">
    <toolbarbutton id="opensearch" label="&openCmdToolbar.label;"/>
    <toolbarbutton id="savesearch" label="&saveCmdToolbar.label;"/>
  </toolbar>
 </toolbox>

 <tabbox>
  <tabs>
    <tab label="&searchTab;" selected="true"/>
    <tab label="&optionsTab;"/>
  </tabs>

  <tabpanels>

   <tabpanel id="searchpanel" orient="vertical" context="editpopup">

   <description>
     &findDescription;
   </description>

   <spacer class="titlespace"/>

   <groupbox orient="horizontal">
     <caption label="&findCriteria;"/>

     <menulist id="searchtype">
       <menupopup>
         <menuitem label="&type.name;"/>
         <menuitem label="&type.size;"/>
         <menuitem label="&type.date;"/>
       </menupopup>
     </menulist>
   <spacer class="springspace"/>
     <menulist id="searchmode">
       <menupopup>
         <menuitem label="&mode.is;"/>
         <menuitem label="&mode.isnot;"/>
       </menupopup>
     </menulist>
   <spacer class="springspace"/>

   <menulist id="find-text" flex="1"
             editable="true"
             datasources="file:///mozilla/recents.rdf"
             ref="http://www.xulplanet.com/rdf/recent/all">
     <template>
       <menupopup>
         <menuitem label="rdf:http://www.xulplanet.com/rdf/recent#Label" uri="rdf:*"/>
       </menupopup>
     </template>
   </menulist>

   </groupbox>

  </tabpanel>

  <tabpanel id="optionspanel" orient="vertical">
     <checkbox id="casecheck" label="&casesensitive;"/>
     <checkbox id="wordscheck" label="&matchfilename;"/>
    </tabpanel>

  </tabpanels>
 </tabbox>

 <tree id="results" style="display: none;" flex="1">
   <treecols>
     <treecol id="name" label="&results.filename;" flex="1"/>
     <treecol id="location" label="&results.location;" flex="2"/>
     <treecol id="size" label="&results.size;" flex="1"/>
   </treecols>

   <treechildren>
     <treeitem>
       <treerow>
         <treecell label="mozilla"/>
         <treecell label="/usr/local"/>
         <treecell label="&bytes.before;2520&bytes.after;"/>
       </treerow>
     </treeitem>
   </treechildren>
 </tree>

 <splitter id="splitbar" resizeafter="grow" style="display: none;"/>

 <spacer class="titlespace"/>

 <hbox>
   <progressmeter id="progmeter" value="50%" style="display: none;"/>
   <spacer flex="1"/>
   <button id="find-button" label="&button.find;"
           oncommand="doFind()"/>
   <button id="cancel-button" label="&button.cancel;"
           oncommand="window.close();"/>
 </hbox>
</vbox>

</window>

Each text string has been replaced by an entity reference. A DTD file has been included near the beginning of the XUL file. Each entity that was added should be declared in the DTD file. The window will not be displayed if an entity is found in the XUL file that hasn't been declared.

Note that the name of the entity is not important. In the example above, words in entities have been separated with periods. You don't have to do this. The entity names here follow similar conventions as the rest of the Mozilla code.

You might notice that the text '2520 bytes' has been replaced by two entities. This is because the phrase structure may be different in another locale. For example, the number might need to appear before the equivalent of 'bytes' instead of after. Of course, this might need to be more complicated in order to display KB or MB as needed.

The access keys and keyboard shortcuts have also been translated into entities because they will likely be different in a different locale.

Next, the DTD file (findfile.dtd):

<!ENTITY findWindow.title "Find Files">
<!ENTITY fileMenu.label "File">
<!ENTITY editMenu.label "Edit">
<!ENTITY fileMenu.accesskey "f">
<!ENTITY editMenu.accesskey "e">
<!ENTITY openCmd.label "Open Search...">
<!ENTITY saveCmd.label "Save Search...">
<!ENTITY closeCmd.label "Close">
<!ENTITY openCmd.accesskey "o">
<!ENTITY saveCmd.accesskey "s">
<!ENTITY closeCmd.accesskey "c">
<!ENTITY cutCmd.label "Cut">
<!ENTITY copyCmd.label "Copy">
<!ENTITY pasteCmd.label "Paste">
<!ENTITY cutCmd.accesskey "t">
<!ENTITY copyCmd.accesskey "c">
<!ENTITY pasteCmd.accesskey "p">
<!ENTITY cutCmd.commandkey "X">
<!ENTITY copyCmd.commandkey "C">
<!ENTITY pasteCmd.commandkey "V">
<!ENTITY openCmdToolbar.label "Open">
<!ENTITY saveCmdToolbar.label "Save">
<!ENTITY searchTab "Search">
<!ENTITY optionsTab "Options">
<!ENTITY findDescription "Enter your search criteria below and select the Find button to begin the search.">
<!ENTITY findCriteria "Search Criteria">
<!ENTITY type.name "Name">
<!ENTITY type.size "Size">
<!ENTITY type.date "Date Modified">
<!ENTITY mode.is "Is">
<!ENTITY mode.isnot "Is Not">
<!ENTITY casesensitive "Case Sensitive Search">
<!ENTITY matchfilename "Match Entire Filename">
<!ENTITY results.filename "Filename">
<!ENTITY results.location "Location">
<!ENTITY results.size "Size">
<!ENTITY bytes.before "">
<!ENTITY bytes.after "bytes">
<!ENTITY button.find "Find">
<!ENTITY button.cancel "Cancel">

Now, to change a language all you need to do is create another DTD file. By using the chrome system to add the DTD file to a different locale, the same XUL file can be used in any language.


(Next) Next, we'll look at property files.

Find files example so far: Source


Copyright (C) 1999 - 2004 XulPlanet.com