Creating multilingual extensions
From Opera 15 onward, Opera 11 & 12’s extension format is no longer supported, and instead, we’ve switched to Chromium’s extension model. Check out our new documentation for developing extensions for Opera 15 and higher and start building your own extensions.
Localising an Opera extension is done, broadly speaking, in two parts:
- Adding translations of your extension's title, description, etc. in the config.xml file
- Putting localised files and resources in language-specific folders
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:
es-419
: not a standard language code format, although we are aiming to support such codes in the future.iw
: An old code for Hebrew;he
should be used instead.
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
- Switching Opera to a different locale
- Our extension
- Localisation techniques
- Localising config.xml metadata
- Organising files inside an extension
- Localising files and resources
- End result
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.
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.
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.
Figure 3: Our example extension in action.
This version of the extension contains the following files, all placed in the root of the archive:
button.png
config.xml
general.css
icon.png
index.html
popup.html
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="patrickl@opera.com">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
.
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="patrickl@opera.com">Patrick H. Lauke</author>
</widget>
The only elements that allow this form of localisation are:
name
description
license
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="patrickl@opera.com">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:
- 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 asxml:lang="en-gb"
. - 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"
. - The above two steps are repeated for each language/locale that the user may have set in their preferences.
- 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 thewidget
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.
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:
images
button.png
icon.png
styles
general.css
config.xml
index.html
popup.html
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:
locales
de
index.html
popup.html
en
images
button.png
icon.png
index.html
popup.html
en-gb
index.html
popup.html
fr
index.html
popup.html
it
index.html
popup.html
ja
images
button.png
icon.png
index.html
popup.html
ru
images
button.png
icon.png
index.html
popup.html
styles
general.css
config.xml
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:
- 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 inconfig.xml
, it will start to look for default files likeindex.html
andicon.png
in this folder. Any references relative to the location of theconfig.xml
file in the root of the extension, likeimages/icon.png
, are internally translated to be relative to this locale folder, e.g./locales/en-gb/images/icon.png
. - 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
. - If there is still no match, the above two steps are repeated for each language/locale defined in the preferences.
- If all else fails, the browser will try and find the file in the
locales
subfolder matching thedefaultlocale
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:
config.xml
says the icon should be found in<icon src="images/icon.png"/>
. As there is alocales
folder, let's use our preferred locale as a root context and see if/locales/en-gb/images/icon.png
exists.- Moving up in the language range, let's check
/locales/en/images/icon.png
.
Next, the default index.html
file:
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:
- Let's start with
/locales/en-gb/images/button.png
. - 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:
- 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
:
- We start with
/locales/en-gb/styles/general.css
. - Moving up, we look in
/locales/en/styles/general.css
. - 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
andit
, the above steps would be repeated to look for the stylesheet in/locales/de/styles/general.css
and in/locales/it/styles/general.css
. - Ok, so the stylesheet doesn't appear to be in any of the preferred locales. Did the
widget
definition inconfig.xml
contain adefaultlocale
attribute? Yes, it's set todefaultlocale="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.
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.
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
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.
codeThis article is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported license.
Comments
The forum archive of this article is still available on My Opera.
Robert Rotariu
Thursday, September 20, 2012
And I've been bashing my brains out, trying to figure why AutoStack ( https://addons.opera.com/en/extensions/details/autostack/?display=en ) localization doesn't work properly.
All this time, the problem was that localization does not work well at all in extensions.
Fix the damn localization mechanism!
Opera 12.02 x64 on Windows 7 x64 (localization doesn't work in Opera 32-bit version either)
Christoph
Friday, October 19, 2012
http://my.opera.com/christoph142/blog/2012/10/19/localizing-injectedscripts
Ricardo Jose
Friday, March 15, 2013