« Documentation Home

Creating multilingual extensions

This documentation relates to Opera's now deprecated .oex Extension API framework for Opera versions <= 12.15 and also provided by our OEX2NEX shim library.

For the latest Opera Extensions API documentation for Opera versions > 12.15 please consult the latest Opera Extensions API documentation online.

Localising an Opera extension is done, broadly speaking, in two parts:

Read on for an in-depth explanation, including an example extension showing localisation in practice.

Please Note: the Opera addons catalog supports all standard language and country codes defined in the ICU project, and our processing and interpretation via pyICU: a Python ICU wrapper. We support all standard language codes, but not script codes, variants and keywords as mentioned in the ICU user guide, except codes that we've made an exception for. Examples of non-supported codes are as follows:

If you find that a code you are using is not supported, and you feel that it should be, please let us know, by commenting on this article comment thread.

Contents

Introduction

Authors who want to provide their extensions in different languages don't have to create separately packaged versions of their .oex file. Opera's extensions use the same packaging and configuration as W3C Widgets, which includes built-in mechanisms for providing multilingual resources, all wrapped up in a single self-contained archive.

Make sure you grab all the source files for this article, wrapped up for your convenience in a single zip archive. It contains the packaged .oex extensions - as well as separate folders with all the uncompressed files — to make it easy to follow the recommended Opera extensions developer workflow and take advantage of our Developer Mode.

Although this article deals specifically with Opera extensions, the general concepts and techniques outlined here will also apply to the localisation of traditional W3C Widgets.

Switching Opera to a different locale

By default, the version of Opera that you installed will be set to match the locale of your operating system. In order to test that our localised extension works correctly, we'll need to explicitly switch Opera to different locales.

In Windows and Linux, this is achieved by going to Menu > Settings > Preferences... and changing the Language option in the General tab. If you installed the international version of Opera, this will — for the most common languages — also change the language of the browser's user interface itself (including menus, dialog boxes, etc). For non-international versions, it will inform you that you don't have the appropriate language file available, but it will still send that language preference to websites to serve you content in that language if available, and it will switch the language on multilingual extensions.

After changing Opera's locale, even if the user interface language changes immediately you will need to close and reopen the browser for the change to affect extensions.

The language preferences dialogs on Windows

Figure 1: The language preferences dialogs on Windows.

Beyond choosing the primary language for the browser, on Windows/Linux we can also define a series of fallback locales, which will be used when content is not available for a specific language. This is done by selecting the Details... button next to the language dropdown and modifying the Preferred languages for webpages list.

The browser locale affects more than just the user interface. Information about the user's preferred locale(s) is sent as part of the browser's request headers. For instance, if the list of preferred locales was comprised of British English, English and German, Opera's Accept-Language header would contain something like en-GB,en;q=0.9,de;q=0.8. Websites can use this information in the request header to serve content to the browser in different languages, according to the user's preference.

Language &amp; Text preferences dialog in OS X

Figure 2: Language & Text preferences dialog in OS X.

In OS X, Opera follows the platform convention, which is slightly different: instead of providing a way to change the locale and the list of preferred languages in the browser's preferences, it follows the OS-wide settings in System Preferences... > Language & Text (System Preferences... > International > Language in older versions of OS X). For changes made in this dialog to take effect in Opera, you will need to close and reopen the browser.

Our extension

To illustrate the localisation process, in this article we'll be localising a fairly basic extension. To keep things simple, all this extension does is add a button to the browser's toolbar which brings up a pop-up containing some short text.

Our first version can be found in Localisation test — version 1.1.

Our example extension in action

Figure 3: Our example extension in action.

This version of the extension contains the following files, all placed in the root of the archive:

And here's the config.xml:

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" version="1.1">
    <name>Localization test</name>
    <description>Localization test</description>
    <author href="http://dev.opera.com"
            email="[email protected]">Patrick H. Lauke</author>
</widget>

In its current state, this extension is unlocalised — there are no provisions for different languages, and no information has been given in the configuration file as to what the current language being used is. So, from this simple starting point, let's get cracking and turn this into a localised, multilingual extension.

Localisation techniques

The W3C Widget Packaging and Configuration specification defines two complementary methods that are used to provide extension content in different languages: element-based localisation, which takes care of the extension metadata, and folder-based localisation, which lets us provide different versions of our content for specific languages.

Localising config.xml metadata

The config.xml file contains metadata that describes an extension — information about the name of the extension, who the author is, a short description, licensing, etc. This information is presented to the user in the extension manager and any browser dialogs relating to that extension.

For a more in-depth look at the contents and purpose of config.xml, see our extensive guide on The ins and outs of config.xml.

config.xml metadata used in the extension manager

Figure 4: config.xml metadata used in the extension manager.

Our first step in localising our extension is to provide part of this metadata in different languages through element-based localisation. In our configuration file, we simply need to add multiple versions of our elements, specifying their respective language — using the standard IANA language subtags — with an xml:lang attribute.

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" version="1.2">
    <name>Localization test</name>
    <name xml:lang="en-gb">Localisation test</name>
    <name xml:lang="fr">Test de localisation</name>
    <name xml:lang="it">Test di localizzazione</name>
    <name xml:lang="de">Lokalisierungstest</name>
    <name xml:lang="ru">тест локализации</name>
    <name xml:lang="ja">ローカリゼーションテスト</name>
    <description>Localization test</description>
    <description xml:lang="en-gb">Localisation test</description>
    <description xml:lang="fr">Test de localisation</description>
    <description xml:lang="it">Test di localizzazione</description>
    <description xml:lang="de">Lokalisierungstest</description>
    <description xml:lang="ru">тест локализации</description>
    <description xml:lang="ja">ローカリゼーションテスト</description>
    <author href="http://dev.opera.com"
                    email="[email protected]">Patrick H. Lauke</author>
</widget>

The only elements that allow this form of localisation are:

To keep things neat and tidy, I want to explicitly set the language of our default elements to xml:lang="en" as well. When doing this, we need to also include the defaultlocale attribute to our <widget> element, so the browser knows what the default/fallback is.

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" defaultlocale="en" version="1.2">
    <name xml:lang="en">Localization test</name>
    <name xml:lang="en-gb">Localisation test</name>
    <name xml:lang="fr">Test de localisation</name>
    <name xml:lang="it">Test di localizzazione</name>
    <name xml:lang="de">Lokalisierungstest</name>
    <name xml:lang="ru">тест локализации</name>
    <name xml:lang="ja">ローカリゼーションテスト</name>
    <description xml:lang="en">Localization test</description>
    <description xml:lang="en-gb">Localisation test</description>
    <description xml:lang="fr">Test de localisation</description>
    <description xml:lang="it">Test di localizzazione</description>
    <description xml:lang="de">Lokalisierungstest</description>
    <description xml:lang="ru">тест локализации</description>
    <description xml:lang="ja">ローカリゼーションテスト</description>
    <author href="http://dev.opera.com"
        email="[email protected]">Patrick H. Lauke</author>
</widget>

You can find this updated version of config.xml in Localisation test — version 1.2.

Now, when installing the extension, the browser will attempt to find the version of each element that most closely matches the user's selected locale and language preference, with a series of fallback steps:

  1. First, the browser will try to find an exact match for the user's locale. For instance, if I have set my browser to en-gb, Opera will first attempt to find elements that are marked as xml:lang="en-gb".
  2. If no exact match is found, the browser will attempt to find any other elements from within the same language range. In our case, it would start to look for anything marked as simply xml:lang="en".
  3. The above two steps are repeated for each language/locale that the user may have set in their preferences.
  4. If all else fails, the browser will fall back to the default content — the elements matching the default locale or, if defaultlocale was not defined in the widget element, any unlocalised elements.

Note that if config.xml contains multiple versions of the same element localised for a specific language, or multiple versions of an unlocalised element, only the first one of those (in source order) will be processed. For example, if our configuration contained


<description xml:lang="en">Localization test</description>
<description xml:lang="en">Some other description</description>
…
<description xml:lang="en">Yet another description</description>

only the first description would be used.

We're off to a good start. Now, if you switch your browser to different locales, you'll notice that the metadata (when installing or managing the extension) is presented in different languages.

Our extension now shows properly localised metadata in the install, management and uninstall dialogs, but the icon is still the same

Figure 5: Our extension now shows properly localised metadata in the install, management and uninstall dialogs, but the icon is still the same.

However, element-based localisation doesn't allow us to set different, language-specific icons and most importantly, the actual functionality of our extension is still only available in the default English version. We'll tackle this more fundamental part of localisation in a moment but first, let's organise our files a little better — this will hopefully make things clearer as we proceed.

Organising files inside an extension

The way extensions are packaged gives authors a lot of flexibility in how they want to organise their files. At the simplest level, we can keep all our files in the root of the .oex archive and, if we stick to some basic naming conventions, the browser will automatically find standard components like our index.html start file and icon.png. However, we can also be a lot more methodical and structured, adding any number of subdirectories to our extensions to better organise our content. Personally, I like to keep a separate folder for images and styles. So, let's tidy up our extension as follows:

Jumping into our extension's HTML files for a moment, we now obviously need to change the references to our toolbar icon in index.html

<!DOCTYPE html>
<html>
<head>
  <script>
    window.addEventListener("load", function(){
      var theButton;
      var ToolbarUIItemProperties = {
        title: "Localization test",
          icon: "/images/button.png",
          popup: {
            href: "popup.html",
              width: 300,
              height: 100
          }
      }
      theButton = opera.contexts.toolbar.createItem(ToolbarUIItemProperties);
      opera.contexts.toolbar.addItem(theButton);
  }, false);
  </script>
</head>
<body>
</body>
</html>

and our stylesheet in popup.html


<!DOCTYPE html>
<html>
    <head>
        <title>Localization test</title>
        <link rel="stylesheet" href="/styles/general.css">
    </head>
    <body>
        <h1>Localization test</h1>
    </body>
</html>

As with regular web pages, references to external resources (like the src attribute on an <img> element, or the href pointing a stylesheet on a <link> element) can be relative to the current file, or relative to the root of the extension itself (starting the reference with a slash, e.g. /images/button.png), as we've done in our example above.

As our icon.png has now moved from its default location at the root of the extension to the images subfolder, we need to edit the config.xml file to explicitly tell the browser where to find it. Once again, I've opted for a reference that's relative to the root of the extension:

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" defaultlocale="en" version="1.3">
    …
    <icon src="images/icon.png"/>
</widget>

You can find these further organizational updates in Localisation test — version 1.3.

You might now think that, in order to provide a custom icon for a specific language, we can just add separate icon elements with different xml:lang attributes but unfortunately icon does not allow for element-based localisation.

For this, as well as for more fundamental needs such as localising index.html (which sets the title tooltip associated with the button that appears in the toolbar) and popup.html, and having language-specific resources like the button.png, we need to use a different method — folder-based localisation.

Localising files and resources

Folder-based localisation is used when we want to provide completely separate files and resources, depending on the user's locale and language settings.

First, we need to create a special locales folder in the root of our extension.

locales is a reserved folder name, as it serves the specific purpose of containing content for folder-based localisation. It must not be used for any other type of content.

In this folder we then create subfolders that match the IANA language subtags for each locale that we want to provide localised content for.

Although language tags don't usually need to be in any particular case (uppercase, lowercase, or even mixed), these folder names must all be lowercase to account for differences in case-sensitive file handling on different operating systems.

So, after we set up our various folders and create the separate, localised versions of our files, our extension is now structured as follows:

You can find this version inside Localisation test — version 1.4.

Notice that I didn't make separate copies for every individual file in the various language subfolders. For instance, I only provided different icon.png and button.png images for the English (default locale), Russian and Japanese versions (the word "Test" that appears in the English version works fine for all the other languages I want to cater for as well). Also, as my stylesheet for the popup doesn't do anything locale-specific and applies to all languages, I only have a single copy of it outside of the locales folder, meaning that it's regarded as unlocalised content.

This works because when the browser needs to find a particular file or resource, it will apply the same language-matching algorithm we saw above:

  1. First, it will try to find an exact match for the user's locale. If I've set my preference to en-gb, Opera will look for a /locales/en-gb/ folder, and use this folder as a new root context. Unless otherwise specified in config.xml, it will start to look for default files like index.html and icon.png in this folder. Any references relative to the location of the config.xml file in the root of the extension, like images/icon.png, are internally translated to be relative to this locale folder, e.g. /locales/en-gb/images/icon.png.
  2. If no exact match is found (the folder or file doesn't exist), the browser will try to find it inside any locales subfolders in the same language range. In the case of our icon, it would start to look in /locales/en/images/icon.png.
  3. If there is still no match, the above two steps are repeated for each language/locale defined in the preferences.
  4. If all else fails, the browser will try and find the file in the locales subfolder matching the defaultlocale value, or failing that in the actual root of the .oex archive as unlocalised content (as is the case with /styles/general.css).

Folder-based localisation algorithm step by step walkthrough

This may appear confusing at first, so let's work through this algorithm for all the files that are being referenced. Let's assume that I've set my preference to en-gb. First of all, the browser will need to find the icon for our extension:

  1. config.xml says the icon should be found in <icon src="images/icon.png"/>. As there is a locales folder, let's use our preferred locale as a root context and see if /locales/en-gb/images/icon.png exists.
  2. Moving up in the language range, let's check /locales/en/images/icon.png.

Next, the default index.html file:

  1. config.xml doesn't mention any custom location for the start file, so let's begin by looking in /locales/en-gb/index.html.

In index.html, our JavaScript references /images/button.png for the toolbar button:

  1. Let's start with /locales/en-gb/images/button.png.
  2. Again, we move up the language range and see if the image is in /locales/en/images/button.png.

When the user presses the toolbar button, the JavaScript in index.html will try to open the popup.html file:

  1. As this is a relative reference to our index file, let's look in /locales/en-gb/popup.html.

Lastly, popup.html references a stylesheet at /styles/general.css:

  1. We start with /locales/en-gb/styles/general.css.
  2. Moving up, we look in /locales/en/styles/general.css.
  3. At this point, we loop through each individual fallback language defined in our preferences — for instance, if my list of preferred locales was comprised of en-gb, de and it, the above steps would be repeated to look for the stylesheet in /locales/de/styles/general.css and in /locales/it/styles/general.css.
  4. Ok, so the stylesheet doesn't appear to be in any of the preferred locales. Did the widget definition in config.xml contain a defaultlocale attribute? Yes, it's set to defaultlocale="en", but we already checked there and /locales/en/styles/general.css doesn't exist. As a last resort, let's see if the stylesheet can be found from the root of the widget as unlocalised content, at /styles/general.css.

Admittedly, this algorithm may seem quite daunting at first, and there are certain complex situations where (particularly because of the third step, which loops through the various fallback locales first before resorting to default/unlocalised versions) Opera will behave in apparently unexpected ways.

Opera is set to French, but the extension shows the Japanese version of the icon due to the language fallback list in our preferences

Figure 6: Opera is set to French, but the extension shows the Japanese version of the icon due to the language fallback list in our preferences.

If you're getting strange results after changing the Language settings a few times, check the list of fallback locales and make sure the order of those locales is still correct — see the Preferred languages for webpages dialog on Windows/Linux, or the Languages & Text preference in OS X.

In the example shown in Figure 6, our Language is set to French, but the browser is showing the localised icon of the Japanese version. Looking at the Preferred languages... list (after an extensive round of testing different locales), we see that the fallback stack now contains fr, ja, ru, it, en-gb, en. When looking for the icon, the browser will first try to locate it in /locales/fr/images/icon.png, and after that it will loop through the remaining languages in the stack before resorting to the default. As the next language in our stack does have an icon at /locales/ja/images/icon.png, it's the Japanese icon that is displayed.

Very few users are likely to have this sort of overloaded fallback list of languages, but if you're testing your extensions, you can quickly end up in these strange situations — at which point I'd recommend simply deleting (on Windows/Linux) or unchecking (using the Edit List... option in OS X) the majority of those fallbacks to more closely match what "real" users are likely to have.

Our finished extension now shows both localised text and a locale-specific icon in the various extension installation, management and uninstall dialogs

Figure 7: Our finished extension now shows both localised text and a locale-specific icon in the various extension installation, management and uninstall dialogs.

Once you manage to wrap your head around this algorithm, you'll be able to keep your multilingual extensions lean and minimal, without unnecessary duplication — only "patching in" those files that are actually different from language to language.

End result

Our finished extension, showing appropriately localised buttons, tooltips and content for Japanese, German and Russian locales

Figure 8: Our finished extension, showing appropriately localised buttons, tooltips and content for Japanese, German and Russian locales.

And there we have it. Using the two complementary techniques of element-based and folder-based localisation, we now have a single .oex extension file that works in a variety of different languages. The dialog shown when the extension is first installed, the listing in the extension manager, the button in the browser's toolbar, and of course the popup that is shown when the button is activated, are all available in localised flavours. So what are you waiting for? If you've already created your own extension, why not try to make it work even better for your international users?

Did you create a great multilingual extension and are planning to submit it to Opera's extensions catalogue? Then make sure that you also add multilingual descriptions as part of the submission process.