Table of Contents
Objective: To understand how to create a Nuxeo bundle that correctly adapts to different languages.
I speak Spanish to God, Italian to women, French to men and German to my horse. Charles V, Holy Roman Emperor (1500-1558).
If you have any comments, questions, or general-purpose harassment you would like give us about this book, then please use the comment form at the bottom of each page! We promise that we will try to incorporate any feedback you give (minus the profanity, of course), will respond to your questions, and credit you appropriately.
In the United States, the term "I18N" is pronounced "eye-eighteen-en." This is a shortening, at least when written, of the word "Internationalization" because the word has 18 characters between the leading 'L' and trailing 'N'. Internationalization, at least traditionally, has meant translating the text of applications (or websites) so that the application can be used by folks who speak (and read) different languages. Similarly, l10n has come to mean "localization" and the definition of localization is normally taken to be slightly larger than internationalization since localization includes not only the language issues, which are largely ones of text presentation, but also those associated with differences in the way that dates and times are displayed, currencies, the way decimal points are presented, and so on. Using these definitions, the United States and Great Britain would be almost identical for the purpose of Internationalization (color vs. colour) but quite different in localization terms because they use different currencies (with different currency markers, £ vs $) and write dates in different orders, 11/9/2001 vs 9/11/2001.
To see the effects of any work you do to localize your plugin, you will need to set your web browser to the language you wish to see. Normally, this is done through the preferences panel. The screen shot below shows the Content Preferences tab from the main preferences dialog in FireFox version 3. You can also see the Language choice dialog in the lower left. By promoting a particular language to the top position, you can can different output from Nuxeo. You can see a bit of the text of the login screen in the background that has been rendered in French.
If you have intsalled your copy of Nuxeo 5 in the normal place, /nuxeo/tools/NuxeoEP5, then you should perform a couple of commands as shown here:
[~] $ cd /nuxeo/tools/NuxeoEP5/NuxeoServer/server/default/deploy/nuxeo.ear/nuxeo.war/WEB-INF/classes [/nuxeo/tools/NuxeoEP5/NuxeoServer/server/default/deploy/nuxeo.ear/nuxeo.war/WEB-INF/classes] $ ls messages* messages_ar.properties messages_es.properties messages.properties messages_cn.properties messages_fr.properties messages_pt_BR.properties messages_de.properties messages_it.properties messages_ru.properties messages_en.properties messages_ja.properties messages_vn.properties [/nuxeo/tools/NuxeoEP5/NuxeoServer/server/default/deploy/nuxeo.ear/nuxeo.war/WEB-INF/classes] $ head messages_en.properties # # Translations of action labels # #: nuxeo-platform/nuxeo-platform-notification-web/src/main/resources/OSGI-INF/actions-contrib.xml #: nuxeo-platform/nuxeo-platform-webapp-core/src/main/resources/OSGI-INF/actions-contrib.xml action.email.document=Email #: nuxeo-platform/nuxeo-platform-forum/src/main/resources/OSGI-INF/forum-actions-contrib.xml action.forum.content=Forum
So, the above example script shows that inside the
nuxeo.ear/nuxeo.war
directory that we have seen
before is the root of the web directory for Nuxeo's UI, is the directory
WEB-INF/classes
. This is the directory where
"resources" (in Java parlance) are located. Java web developers will
recognize this as the directory referenced by the Java API
System.getResourceAsStream()
. Each of the files shown shown
above that start with messages_
is a translation of
the Nuxeo web UI. The abbreviations for languages, noted in the suffixes
after the underscore, are quite standardized with English being
en
, French being fr
, Japanese
being ja
, and so on. (These correspond to the notion
in Java of a "locale" such as in the Java API
Locale.getDefault()
.) These files are in the standard
Java "properties" format: blank lines and lines that start with a # are
comments and the rest are in key=value format and are terminated by the
end of the line. In the case of the properties here, the left (key) side
of the equals sign is a "label" referred to in the Java program or the
configuration files of a bundle. The right hand side is the value to
output to the user for this label.
It is worth noting a couple of files that are slightly different.
First, the file messages_pt_BR.properties
is the
translation of the Nuxeo UI into Brazilian
Portuguese. This specialization for a specific country's variant of a
language is signified by the second part of the suffix,
BR
in this case. Although Nuxeo does not provide a
translation to standard Portugese at the time of this writing, if we did
it would be named messages_pt.properties
. This
specialization (or, to be more clear, "specialisation") for English might
be done with a version of the UI for the United Kingdom labelled
messages_en_UK
, another for Canada labelled
messages_en_CA
, and one for the United States
labelled messages_en_US
. The contents of
messages_en.properties
, in this case, would be for
all the other english speakers and, much more crucially, all the
strings that do not need to change based on the country and the same for
all English speakers. This prevents needing to copy all the
strings into each file; messages_en_UK can contain only those strings that
need to change for folks that are in the United
Kingdom.
Most modern browsers correctly set the language/locale option, so normally each request handled by nuxeo has both the language and the country set to the desired values for the user. Let's look at the sequence used for a example request by a browser, when the code is trying to output a label for a button. Given a user in Quebec, the likely value of the language desired is "fr_CA" to to indicate French Canadian. The system will look for a the text label of the button in these files, in this order:
messages_fr_CA.properties
messages_fr.properties
messages.properties
The first translation found is used any later translations will be
ignored. This should help explain the presence of the undecorated
messages.properties
file in the list above!
messages.properties
is the file of last resort when
the user's country-specific and language-specific translations are either
not present or not neeeded.
Some people are tempted to put all the keys in each file and then just have someone do a complete translation by working through the file. While this apporach can work in the short term, it is storing up trouble. The point of having the sequence of files checked is to allow you to not have to duplicate things unnecessarily, facilitating change since far fewer files must be changed on average when you add or delete keys. Further, during development you can use this sequence as a reminder of things to do later, as you will see in the next section.
As in the previous lessons, you should export
the skeleton of this lesson with svn from the nuxeo
repository
http://svn.nuxeo.org/nuxeo/sandbox/iansmith/book/lesson-l10n
so you can begin the lesson with the results of previous lessons, use
maven to create the Eclipse project, and then import the project
(lesson6-l10n
) into your workspace.
To begin the internationalization, you should update your
contribution to the widgets point in file
layout-contrib.xml
<extension target="org.nuxeo.ecm.platform.forms.layout.WebLayoutManager" point="widgets"> <widget name="upcoming_when" type="datetime"> <labels> <label mode="any">upcoming.layout.label.when</label> </labels> <translated>true</translated> <fields> <field>up:occursOn</field> </fields> <properties widgetMode="edit"> <property name="required">true</property> </properties> </widget> <widget name="upcoming_who" type="text"> <labels> <label mode="any">upcoming.layout.label.who</label> </labels> <translated>true</translated> <fields> <field>up:presenter</field> </fields> </widget> <widget name="upcoming_howmuch" type="text"> <labels> <label mode="any">upcoming.layout.label.howmuch</label> </labels> <translated>true</translated> <fields> <field>up:admissionPrice</field> </fields> <properties widgetMode="edit"> <property name="required">true</property> </properties> </widget> </extension>
This sets the labels to be looked up in the various messages files
to be upcoming.layout.label.when
and so forth. It also
alerts, with the translated
tag, nuxeo that we would like it to
check on these labels rather than using the text directly from this file,
as we had done before.
You should now create a new subdirectory of
resources
in your project and call this directory
l10n
("el-ten-en"). This directory will contain the
translated text for your application. For now, we will create placeholders
for the document type's UI you created in the last lesson. The first file,
messages.properties is shown below:
upcoming.layout.label.when=untranslated: when upcoming.layout.label.who=untranslated: who upcoming.layout.label.howmuch=untranslated: how much
Finally, you need to create a deployment script to append your
translated files with those of Nuxeo itself. These must be merged because
your bundle is running as a part of Nuxeo. Naturally, you do this with a
change to the deployment-fragment.xml
file. Below is
the new, complete listing of this file:
<fragment> <extension target="application#MODULE"> <module> <java>${bundle.fileName}</java> </module> </extension> <install> <!-- Unzip the contents of our nuxeo.war into the real nuxeo.war on the server --> <unzip from="${bundle.fileName}" to="/"> <include>nuxeo.war/**</include> </unzip> <!--make sure the directory is there for messages files--> <mkdir path="lesson-l10n.tmp"/> <!-- Unzip the l10n directory to a temporary place--> <unzip from="${bundle.fileName}" to="lesson-l10n.tmp"> <include>l10n/**</include> </unzip> <!-- copy default text --> <append from="lesson-l10n.tmp/l10n/messages.properties" to="nuxeo.war/WEB-INF/classes/messages.properties" addNewLine="true"/> </install> </fragment>
If you build and deploy this file, then run it inside Nuxeo, you will see something like this when you try to create a new Upcoming document:
We are using "untranslated" to remind us to build translations; this is recommended during development, since the text is visible through the browser interface. In production, you would place some default language in this file. Nuxeo has chosen English as its default, so the contents of the messages.properties files are in English, when there are strings that are overriden by other language files. Other than these, the contents should be things that do not need translating, like names of places, or the word "orange."
Once you have mastered how to deploy your bundle in multiple languages, it should be a simple exercise (!) to add the necessary support for other language such as French an English. It is important to remember, however, that the final selection of the language-and if necessary country-is up to the browser so you will need to select the correct browser settings to see your handiwork.
Add support for French and English in the Upcoming bundle. The French words are "Quand" (when), "Combien" (how much), and "Qui" (who). Verify that changes the languages in the browser correctly displays the right text.
Add support for UK English to the Upcoming bundle but use cockney rhyming slang for the text (http://cockneyrhymingslang.co.uk). Since the rhymes are usually associated with nouns, you may want to change the text to be sentences, e.g. "What is the Condoleeza Rice?" Verify that changes the languages in the browser correctly displays the right (?) text.