You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



 

Mozilla Language Pack internal structure

newsgroup discussion / mailing list
Mozilla Localization Project Staff

MLP [Localization Project home] | [Localization docs] | [Language pack tech]

Introduction

Mozilla can automatically install additional components using XPI packages.

The typical scenario sees a language pack for Mozilla constituted by one XPI archive. This is a PKZip compatible archive, containing the resources for a new language, targeted for a particular Mozilla release user interface, plus an installer script. In this way the user can add more installed languages in a click.

Mozilla enable each user, and each user profile, to dynamically select the preferred language for the application UI, among the installed ones.

Contents

Mozilla Localizable resources

The Mozilla user interface is composed of several XML pages the layout engine renders at runtime. This imply the user interface resources are constituted by only text files and graphic stored in conventional web formats. The localizable resources, being a part of the user interface, don't except to this rule.

What you need to keep in mind is not all the text editors are good enough to edit any text file. The keyword here is text encoding. The encoding is the way an application converts letters and symbols into an electronic documents. Not all the plain text documents have the same encoding. Even the same program (like Windows Notepad) ran on different machines (e.g. on a Slovak one and a Chinese one) can store the same symbols in different ways; or can't access / accept every kind of script or symbol. You'll need tools able to "encode" your strings text in the encodings Mozilla understood.

The user interface files are parsed very likely to web documents. They although differs having access to functionalities for security reasons web documents need to be forbidden. The UI resources need then to be installed and registered. All the user interface material works referring to each file through a peculiar "chrome" protocol, in a similar way web pages point to other documents using the HTTP protocol.

For convenience the various UI files, composing the Mozilla applications suite, are contained in a small number of zip compressed files with the .jar extension (even if they have quite a little to share with Java ARchives) under the binaries chrome/ folder. These archives are usually arranged in a way the localizable material, if present, is grouped under a locale/ folder.

Additional localizable resources, placed outside the chrome/ folder, include the user profile defaults and other optional resources like search plugins, spell checker dictionaries, and the such. This material doesn't require a registration.

Let's see in the typical scenario: the English - United States (en-US) locale resources, bundled with Mozilla. These are the files usually a localizer have to work on, to get his language version of Mozilla.

chrome/ en-US.jar !locale/en-US/component/*
each component registered under: chrome://component/locale/*
The en-US.jar archive contains the bulk of the registered localizable material used in all the Mozilla applications user interface (divided by components).

chrome/ en-mac.jar !locale/en-US/component/*
or en-win.jar, or en-unix.jar; each component registered under: chrome://component/locale/*
Contains just the platform dependent user interface part of Mozilla. All these files contains exactly the same components. So only one of the three files components are effectively registered.

chrome/ US.jar !locale/US/component/*
each component registered under: chrome://component/locale/*
Contains so called regional contents. These are the regional specific pointers to external resources used throughout the user interface. This material uses a different locale code from the one used for the rest of the localizable resources. Hence they can be selected indipendently.

defaults/profile/US/*
Profile templates. These files contain the user profile templates for several files such: bookmarks, default window geometry, MIME types binding, default Sidebar panels, default search plugins. These files are a part of the regional contents, but are used only on the creation of a new user profile. The folder name has to match the locale code used by the regional material.

searchplugins/*, components/myspell/*, ...
Additional stuff. These optional files ranges from Mycroft search plugins, to spell checker dictionaries, to any other localizable or locale specific component might appropriate distribute to fully adapt the application towards the user habits.

Localizable text files

As stated above, not all the text editors are good enough for editing Mozilla text resources. Since the application has been designed from the beginning with international audience in mind, conventional 8 bit encodings many popular text editors still use, soon revealed being not enough for Mozilla purposes.

Mainly localizable resources are made up of:

  • DTD files = XML definition tables. Encoding: UTF-8
  • properties files = Java/JS properties definitions. Encoding: Escaped Unicode (\uXXXX)

To properly hand edit these files you'll need a text editor capable to support these encodings. In this way Mozilla can use the full set of the symbols defined by the Unicode consortium.

Tools like Mozilla Translator can handle all these details transparently for the user.

Both dtd and properties text files contains a collection of named strings. The former must comply to the XML documents rules, the latter to JavaScript conventions. This dictates the way comments, quotes, and allowed characters can be used.

The table below list the general rules to keep in mind if you're going to directly edit these files:

   dtd
String format:
<!ENTITY string_name "Text Text">
Not allowed:
&
<
"
%
Use instead:
&amp;
&lt;
&quot;
&#037;
Other useful predefined entities:
>&gt;
'&apos;
Any other Unicode char through &decimalnumber;
Single or multi line comments:  <!-- Text Text Text -->
   properties
String format:
string_name = Text Text
Not allowed:
New line (ends string)
Backslash "\"
Double or single quotes " '
Use instead:
\n As newline
\\ As backslash
\" and \'
Other useful escaped chars:
\bBackspace
\fForm feed
\rCarriage return
\tTab

Any other Unicode char through \uXXXX, where XXXX is a four hexadecimal digit Unicode number

A trailing backslash "\" as last char in the line continue the string in the following document line.

Whole line comments (from the first character): # Text Text
End of the line comments: // Text Text
Multi line comments: /* Text Text Text */

Introduction to the Mozilla Chrome

The ultimate goal is create and have your new language resources registered in Mozilla. Once they're registered, the user is able to select and activate these resources as current ones, application wide. This means select your language in the Preferences (Edit | Preferences | Appearance | Language/Contents), and restart the application.

Registering your language resources means having them listed in the Mozilla registry. Mozilla registry keeps track of a number of packages installed. Each Mozilla component (application) is usually modularized in three different packages:

  • content: one mix of XML and JavaScript files containing the user interface logic for a particular application (like e.g. Mail, Navigator, Composer, the Bookmarks Manager, and so on). The XML files describing each app window use a particular set of tags called XUL. They're manipulable at runtime though DOM properties, using JavaScript.
  • skin: a collection of XLS, CSS, and web graphic files, which defines the ultimate appearance for the parts of the user interface, described in the content of the application.
  • locale: the localizable resources the content will use to communicate to the user.

This modularization allows, for one application content, more than one possible appearance (skin, aka themes) or language (locale), just switching the files pointed by the application's XUL files.

This possible due the concept of currently selected locale and skin. We'll of course focus on the former, but the very same concepts applies to the latter.

Every locale package can be mainly identified by two properties: the content (application) component it's a part of, and the locale (language-NATION code) it's owned by.

Once the user select one UI language for Mozilla, all the locale packages with the corresponding locale code are mapped through the chrome:// protocol, as the default locale resources. The application files, actually, have no direct pointers to resources in the user storage devices. They're instead accessed through the locale/ section of the chrome:// space of the current application. And the currently selected default locale, makes Mozilla map the corresponding locale resources in the locale/ "folder" of the application.

Let's clarify the concept with an example.
The application whose component name is "navigator" create a new window described in the navigator.xul XML file. It needs a string to show in the web search button.

The application pass the navigator.xul document to the Mozilla engine to have the window created. "navigator" is a registered application, with its corresponding content, skin and locale packages registered. We may have more than one different locale packages registered for the navigator component, each one with a copy of the same resources in a different language, recognizable by its locale code. We must have anyway, just one locale package selected at a given time.

The application doesn't need to know which locale is currently selected. Since all the resources will be accessed through the chrome protocol, and only the selected locale resources will be mapped under the locale/ chrome address of the navigator component, the currently selected resources will be the application resources (the only ones accessible) accessed by navigator.

The application will pass the following URI for file describing the new window:

chrome://navigator/content/navigator.xul
Likewise, navigator.xul will reference a string inside
chrome://navigator/locale/navigator.dtd
to retrieve the text to be displayed in button. The application then will display whatever language with no modification. It's the underlying chrome handling which will take care for passing the relevant file from the selected locale resources to the application.

How to register the localizable resources

The user interface part of the Mozilla localizable resources needs to be registered, in order to be selected and used.

Registering resources in Mozilla require one of the following actions:

  • Let a JavaScript installer script inside an XPI package register the contents distributed along its resource bundle. The script should be smart enough to handle the following situations:
    • Conditional registration of platform dependent material
    • Application wide, or single user profile installation on multiuser environments
    • Error conditions handling
  • List the new package in the chrome/installed-chrome.txt file the new packege(s), so that it can be registered the next time the root user restarts Mozilla.

Structure of one registrable package

The smallest unit can be registered in Mozilla is one package.

You can think about a package as a group of files owned by a Mozilla application, registered under its component name. As discussed above, packages can exist of three main kinds: content, skin, locale. We're interested in creating locale packages.

If one component is structured in a way it can be localized, its localizable resources are externalized into a locale package with the same component name. We'll be able to register as many locale packages with the same component name as we wish, each one distinguished by its locale code.

The basic components of a package are:

  • The files contained by the package itself, grouped inside one folder or inside one folder of a .jar archive (you can think about jars in all the respects as conventional zip archives).
  • The contents.rdf XML file. This file will describe where's the package insertion point once registered, plus some more information about the package itself, like the author, the Mozilla version for which is targeted, and so on.

Creating a new locale package

Let's suppose we want to create a Simplified Chinese (whose locale code is "zh-CN") package for the "editor" component. Let's suppose we're targeting Mozilla version 1.2. We should follow the steps below:

  • Extract the English resources from the locale/en-US/editor/ folder of en-US.jar file, using a conventional zip decompression utility.
  • Localize the original English files to Chinese, editing .dtd, .properties and other files.
  • Compile the contents.rdf file accordingly the information of the package, and place it inside the same folder of the localized resource files.
  • The package could be then optionally archived inside a compressed jar file.

Let's see what our contents.rdf should looks like:

contents.rdf for the zh-CN locale package of the editor component.
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:chrome="http://www.mozilla.org/rdf/chrome#" >

<!-- We first create a "zh-CN" category in the "locale" root   -->
  <RDF:Seq about="urn:mozilla:locale:root" >
    <RDF:li resource="urn:mozilla:locale:zh-CN" />
  </RDF:Seq>

<!-- Then we fill/update more info about the Simplified Chinese
     locale group, assigning the a global version for its contents -->
  <RDF:Description about="urn:mozilla:locale:zh-CN"
       chrome:displayName="Chinese Simplified (zh-CN)"
       chrome:author="MLP Staff"
       chrome:name="zh-CN"
       chrome:previewURL="http://www.mozilla.org/previewimages/China.png"
       chrome:localeVersion="1.2" >

<!-- Inside the zh-CN locale, we add our "editor" package
     marking it with its version information              -->
       <chrome:packages>
         <RDF:Seq about="urn:mozilla:locale:zh-CN:packages">
           <RDF:li resource="urn:mozilla:locale:zh-CN:editor"
                   chrome:localeVersion="1.2" />
         </RDF:Seq>
       </chrome:packages>

  </RDF:Description>

</RDF:RDF>

Having the package registered

Once the package has been completed, you can either create an XPI package as described in the following section, able to copy your work in the Mozilla folders and register the packages you've put in it; or add one line in the installed-chrome.txt file placed inside the chrome/ folder.

Placing the localized files for the Chinese editor package seen above, in a folder called editorpackagetest inside the Mozilla chrome/ directory, the line could look like:
locale,install,url,resource:/chrome/editorpackagetest/

Once restarted Mozilla, you can see if the registration has succeeded both looking in the install.log file (only for XPI installations) and in the chrome.rdf, searching for the information you put in the registered package.

If you've just added a new locale node in the Mozilla registry (e.g. if we didn't had any Simplified Chinese packages installed yet), you should also be able to see the displayName property added in the Preferences (Edit | Preferences | Appearance | Language/Contents).

Also remember to set the correct version information in your contents.rdf file, or Mozilla will refuse to let the user select the added resources.

Creating a simple XPI language pack

This chapter will show how you can distribute all your work into one self installable language pack, in the form of an XPI archive.

Our self installer XPI archive will copy the localized resources grouped in jar compressed files into the host machine, and then will register them. We'll see how prepare jar files, the XPI file itself and its installation script.

Packing the resources in JAR files

Creating a jar archive from the localized resource files of Mozilla, is as easy as compressing a bunch of files grouped into folders with a zip compatible utility, and then rename the extension of the compressed file from .zip to .jar. Keep in mind anyway the directory structure will have to match the information then used in the installation script.

Here's the directory structure we will use for an our hypothetical Italian resource jar, called it-IT.jar:
it-IT.jar contents.
/locale/it-IT/communicator/contentAreaCommands.dtd
                           contentAreaCommands.properties
                           contents.rdf
                           ...
                           /bookmarks/addBookmark.dtd
                                     /bm-props.dtd
                                     /...
                           /directory/...
                           /dom/...
                           /downloadmanager/...
                           /history/...
                           ...
/locale/it-IT/cookie/contents.rdf
                    /cookie.properties
                    /cookieAcceptDialog.dtd
                    ...
/locale/it-IT/editor/contents.rdf
                    /EdAdvancedEdit.dtd
                    /EdColorPicker.dtd
                    ...
/locale/it-IT/...

We can prepare in a similar way it-mac.jar, it-unix.jar, it-win.jar and IT.jar localizing the contents of their English counterparts.

Remember the regional contents enclosed in the IT.jar archive, will require a different locale code from the language resources. This new locale needs to be properly marked as regional locale in the contets.rdf files used in these packages. In short this means adding the localeType property

chrome:localeType="region"
in the regional locale node, along name, author, and the such.

Note: How to choose the regional locale code?
The main goal is maintain this letter code unique. If the regional material you're bundling refers to a nation with a single, official language, the only ISO national code can be enough (e.g. "IT" for Italy). If this material refers instead to a national region where more than one language is commonly spoken, or you're developing for a minority language, a better choice can be a reg-ll-NN code, where ll and NN are your language and national ISO codes (e.g. reg-en-CA and reg-fr-CA could be the possible locale codes for the English and French regional resources for Canada, respectively).

Preparing the installation script

This is actually a complex (which doesn't equals to difficult) subject. We'll, anyway just provide an already made recipe, and quickly explore how things works.

The installation script is a conventional JavaScript text file named install.js, put in the root of our XPI package. We want this script take the following actions:

  1. Initialize the installation process.
  2. Obtain the application binaries destination folder.
  3. Verify the space available in the destination folder (different folders can reside on different storage units).
  4. If the space available is sufficient, decompress the contents of the XPI archive bin/ folder in the destination folder.
  5. Check for possible error conditions, like trying to copy into a folder we have not write access to. If the file copy has not succeeded, we can try again, obtaining the user profile folder, where we hopefully should be able to place our files.
  6. When our files are in place, we register every single package, from each resource folder or each folder of the jar archives copied.
  7. We take care of installing only packages relevant the user's host platform.

Let's see the script code:

install.js XPI installation script.

  // We use this function later to obtain the disk space available
    function verifyDiskSpace(dirPath, spaceRequired)
    {
      var spaceAvailable;
      // Get the available disk space on the given path
      spaceAvailable = fileGetDiskSpaceAvailable(dirPath);
      // Convert the available disk space into kilobytes
      spaceAvailable = parseInt(spaceAvailable / 1024);
      // do the verification
      if(spaceAvailable < spaceRequired)
      {
        logComment("Insufficient disk space: " + dirPath);
        logComment("  required : " + spaceRequired + " kB");
        logComment("  available: " + spaceAvailable + " kB");
        return(false);
      }
      return(true);
    }

  // We use this function later to obtain the user's host platform
    function getPlatform()
    {
      var platformStr, platformNode;

      if('platform' in Install)
      {
        platformStr = new String(Install.platform);

        if (!platformStr.search(/^Macintosh/))
          platformNode = 'mac';
        else if (!platformStr.search(/^Win/))
          platformNode = 'win';
        else if (!platformStr.search(/^OS\/2/))
          platformNode = 'win';
        else
          platformNode = 'unix';
      }
      else
      {
        var fOSMac  = getFolder("Mac System");
        var fOSWin  = getFolder("Win System");
        logComment("fOSMac: "  + fOSMac);
        logComment("fOSWin: "  + fOSWin);
        if(fOSMac != null)
          platformNode = 'mac';
        else if(fOSWin != null)
          platformNode = 'win';
        else
          platformNode = 'unix';
      }
      return platformNode;
    }


  // *********** Main ***********
  var srDest, err, fProgram;
  
  // ----LOCALIZATION NOTE: translate only these ------
  var prettyName       = "Simplified Chinese Language Pack";
  var regName          = "locales/mozilla/zh-CN";
  var localeName       = "zh-CN";
  var regionLocaleName = "CN";
  var PlatforNeutral   = "zh-CN.jar";
  var Regional         = "CN.jar";
  var macJar           = "zhCN-mac.jar";
  var unixJar          = "zhCN-nix.jar";
  var winJar           = "zhCN-win.jar";
  srDest               = 123; // Required space in kB
  XPIserial            = "5.0.18.2003010521";
  // --- END CHANGABLE STUFF ---

1 err = initInstall(prettyName, regName, XPIserial); 
  logComment("initInstall: " + err);

2 fProgram = getFolder("Program");
  logComment("fProgram: " + fProgram);
  var chromeType = LOCALE|DELAYED_CHROME;

3 if(verifyDiskSpace(fProgram, srDest))
  {
    setPackageFolder(fProgram);
4   err = addDirectory("",
                       XPIserial, // Installation version string
                       "bin",     // dir name in XPI to extract
                       fProgram,  // destination folder
                       "",        // subdir to create relative to fProgram
                       true);     // Force Flag
    logComment("addDirectory() returned: " + err);

5   if (err!=SUCCESS)
    { // Can't install globally, try installing to the profile
      resetError();
      fProgram = getFolder("Profile");
      logComment("fProgram: " + fProgram);
      chromeType = LOCALE|PROFILE_CHROME;
      if(verifyDiskSpace(fProgram, srDest))
      {
        setPackageFolder(fProgram);
        err = addDirectory("", XPIserial, "bin", fProgram, "", true);
        logComment("addDirectory() returned: " + err);
      }
      else err = INSUFFICIENT_DISK_SPACE;
    }

    if (err==SUCCESS)   // Everything's gone fine, go on
    {
6     // Registration of the extracted packages

                           // zh-CN.jar
      lpath   = "locale/"+localeName+"/";
      jarPath = getFolder (fProgram, "chrome/"+PlatforNeutral);
      registerChrome(chromeType, jarPath, lpath+"autoconfig/" );
      registerChrome(chromeType, jarPath, lpath+"communicator/" );
      registerChrome(chromeType, jarPath, lpath+"contents-pack/" );
      registerChrome(chromeType, jarPath, lpath+"cookie/" );
      registerChrome(chromeType, jarPath, lpath+"editor/" );
          ...

7     switch (getPlatform()) {
       case "mac":         // zhCN-mac.jar
        logComment("Registering Mac packages");
        lpath   = "locale/"+localeName+"/";
        jarPath = getFolder (fProgram, "chrome/"+macJar);
        registerChrome(chromeType, jarPath, lpath+"communicator-platform/" );
        registerChrome(chromeType, jarPath, lpath+"global-platform/" );
        registerChrome(chromeType, jarPath, lpath+"navigator-platform/" );
          ...
        break;

       case "unix":         // zhCN-nix.jar
        logComment("Registering Unix packages");
        lpath   = "locale/"+localeName+"/";
        jarPath = getFolder (fProgram, "chrome/"+unixJar);
        registerChrome(chromeType, jarPath, lpath+"communicator-platform/" );
        registerChrome(chromeType, jarPath, lpath+"global-platform/" );
        registerChrome(chromeType, jarPath, lpath+"navigator-platform/" );
          ...
        break;

       case "win":         // zhCN-win.jar
        logComment("Registering Windows packages");
        lpath   = "locale/"+localeName+"/";
        jarPath = getFolder (fProgram, "chrome/"+winJar);
        registerChrome(chromeType, jarPath, lpath+"communicator-platform/" );
        registerChrome(chromeType, jarPath, lpath+"global-platform/" );
        registerChrome(chromeType, jarPath, lpath+"messenger-mapi/" );
        registerChrome(chromeType, jarPath, lpath+"navigator-platform/" );
          ...
      }

                           // CN.jar
      lpath   = "locale/"+regionLocaleName+"/";
      jarPath = getFolder (fProgram, "chrome/"+Regional);
      registerChrome(chromeType, jarPath, lpath+"communicator-region/" );
      registerChrome(chromeType, jarPath, lpath+"editor-region/" );
      registerChrome(chromeType, jarPath, lpath+"messenger-region/" );
          ...

      err = performInstall(); 
      logComment("performInstall() returned: " + err);
    }
    else cancelInstall(err);
  }
  else cancelInstall(INSUFFICIENT_DISK_SPACE);
  
  // ******** End Main **********

Note how the logComment() calls write useful information in the install.log file, about the progress of the installation.

Being conventional JavaScript code, this installer script can be manipulated and modified in almost any thinkable way. The essential installation calls you can access are described in deeper details in the XPInstall project pages.

Putting everything in the XPI package

Once you have all the needed pieces, you can pack everything in a single file, ready to be distributed.

In the out example we'll place our contents inside an empty folder with the directory structure shown below. Note that we have registrable material in the chrome/ folder, while the files the defaults/ folder don't require a registration

Then we'll compress everything inside our final zip archive. To have this file correctly recognized by Mozilla once locally opened in Navigator (to have Navigator prompt the user for the installation), we need to change the file extension from .zip to .xpi.
Directory structure for the ChineseLanguagePack.xpi archive.
/install.js
/bin/chrome/zh-CN.jar
           /zhCN-mac.jar
           /zhCN-nix.jar
           /zhCN-win.jar
           /CN.jar
    /defaults/profile/CN/bookmarks.html
                        /localstore.rdf
                        /mimeTypes.rdf
                        /panels.rdf
                        /search.rdf
                        /chrome/userChrome-example.css
                               /userContent-example.css

As a final note, let me add XPI packages are extremely convenient for on-line installations. One html document with some JavaScript code can query the user for the needed components to install, and then start the download and the installation phase of multiple xpi package with a single call.

All the JavaScript code needed is:

  xpi = {'Chinese Language Pack': 'http://www.invented.cn/ChineseLanguagePack.xpi',
         'Beautiful skin installation': 'http://foo.bar.com/beautifulSkin.xpi',
         ...    };
  InstallTrigger.install(xpi);

The MIME type for servers offering XPI files is:

application/x-xpinstall