My favorites | Sign in
Project Home Wiki Issues Source
Search
for
Internationalization  
How to customize the presentation of user interface strings for non-US-English locales
Updated Feb 26, 2010 by hil...@google.com

Introduction

Internationalization (i18n) of news content is probably not a high priority for most news organizations, but what if your news organization already publishes in a language other than English? It would look very peculiar to present your non-English news content surrounded by instructions, labels, and navigation links that are all in English. Fortunately, the user-facing pieces of this project have been developed so that you can provide translations of UI strings that your readers would find more appropriate.

How i18n works in GWT

To summarize the comprehensive GWT i18n documentation, the general GWT approach to this problem is to build support for different locales into the application at compile-time. This is done by way of GWT's deferred binding mechanism. When a user visits a GWT application they will be served a version of the compiled JavaScript code that applies to their own locale and does not contain any strings or other resources relevant to any other locales.

Which is to say, when deploying an internationalized GWT application, your organization's developers must make the last touch. Non-technical users cannot tweak the translations on the fly without the help of developers.

Details

Adding locales to the application configuration

The Living Stories codebase is divided into 3 GWT entry points: the LivingStoryPage, the StartPage, and the ContentManager. Of these 3, the first 2 are presently internationalized fairly thoroughly, and there are some international strings in the third.

To tell the GWT compiler to generate bindings for these locales, you need to edit the .gwt.xml files that are associated with each entry point. (e.g., LivingStoryPage.gwt.xml). Each one of these has a section marked as being relevant for <!-- i18n --> (this is the one that you care about.)

This section looks like this:

  <extend-property name="locale" values="en"/>
  <!-- to add additional locales, uncomment this line or add similar lines as appropriate -->
  <!-- <extend-property name="locale" values="es"/>  -->
  <set-property-fallback name="locale" value="en"/>

saying that there will be bindings for English only, but suggesting how to add more bindings. To indicate to the compiler that you wish to support more languages, add more <extend-property> lines with the "values" parameter set appropriately. (The useful values are the same as those valid for the lang property of an element in html.)

Users visiting your page will be identified by locale based on their browser user agent and other settings; alternately, they can choose a given locale by specifying "?locale=[desired locale code]" in the query string of the page URL. This is useful for testing.

You may also change the <set-property-fallback> element in your .gwt.xml files to specify a default "fallback" locale other than English if you choose.

Adding locale-specific .properties files, client-side

Now that you've told the GWT compiler you want to generate bindings for other locales, you actually need to provide strings for them! The main way this is done is through .properties files -- basically standard Java .properties files, with a few bits of syntax added for a few special purposes.

If you look within src/com/google/livingstories/client/, you'll see several of these i18n .properties files already there. (At the time of this writing, you'll see 5 core .properties files, plus locale-specific _en.properties overrides for 2 of them.)

Some of these files are called XXXXXConstants.properties (e.g., ClientConstants.properties). These hook into an implementation of the Constants interface which is used for strings that are of a completely fixed nature. Others are called XXXXMessages.properties. These hook into the Messages interface, which is used for strings which include values that are computed programatically, values that should be plugged in at runtime. (Messages are also used in GWT for plural form: more on this later.)

To translate these strings into your chosen locale, (let's say it's French, coded as "fr", for the remainder of this discussion), you need to provide French versions of these files, e.g., ClientConstants_fr.properties. Just put these files in your repository alongside the "locale-neutral" version of these files. When you recompile -- assuming you've followed the .gwt.xml steps appropriately -- the new strings will appear in your chosen locale automatically.

How to see what .properties files are autogenerated (and overriding them)

This is not the end of the story, however. There are 2 classes of .properties files that do not appear directly in the source code:

There are no .properties files in the codebase that directly correspond to these strings. Instead, the GWT compiler automatically generates the default .properties files for these strings at compile time.

You can override these strings just the same as you would if the .properties files were actually in the source code; however, you need to know what the filenames would be and at what path.

You can figure this out by adding the -extra argument to the GWT compiler when it runs. In Eclipse, you can specify this by running "GWT compile project" (the GWT icon on the toolbar), opening the "Advanced" settings disclosure area, and typing, e.g.,

-extra [name of a convenient dump directory; on Linux or Mac, it might be /tmp/extra]

into the "Additional compiler arguments:" area. Once the compilation finishes, you'll see something like the following in the dump directory:

ContentManager/
LivingStoryPage/
StartPage/

within, e.g., ContentManager, you'll see the following i18n-related .properties files:

com.google.livingstories.client.ClientMessages.properties
com.google.livingstories.client.contentmanager.LivingStoryManagerMyUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.ErrorPageErrorPageUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.ShareLinkWidgetPopupShareLinkWidgetPopupUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.ShareLinkWidgetShareLinkWidgetUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.contentitems.ContainerStreamViewFooterContainerStreamViewFooterUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.contentitems.DocumentAssetPreviewDocumentAssetPreviewUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.contentitems.GraphicAssetPreviewGraphicAssetPreviewUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.contentitems.SlideshowPreviewSlideshowPreviewUiBinderImplGenMessages.properties
com.google.livingstories.client.lsp.views.contentitems.VideoAssetPreviewVideoAssetPreviewUiBinderImplGenMessages.properties

You can translate this dot-separated namespace back into the directory structure of your project -- that indicates where your locale-specific override files should be. Most of these files correspond to a single .ui.xml file in the original source; ClientMessages.properties corresponds to ClientMessages.java, which uses plural forms.

You can see an example of this already checked in to the code base, in fact. src/com/google/livingstories/client/ClientMessages_en.properties overrides one of the plural forms specified in the autogenerated ClientMessages.properties files. You would use the exact same approach for translating to a different locale, though you would probably override more strings. :-)

Server-side i18n

Almost all of the strings shown to users come from client-side code. There is an exception, however, in the e-mails that are sent out to users who've subscribed to e-mail updates on a story.

The code that implements this is now internationalized, but not via the GWT Constants or Messages interfaces, which only work on the client side. Instead, the server side code uses the most typical mechanism for finding localized strings in a Java program, a java.util.PropertiesResourceBundle (instantiated via java.util.ResourceBundle.getBundle()).

The underlying global properties file used for this purpose is src/com/google/livingstories/server/rpcimpl/emailTemplate.properties . To specify translations of this file for other locales, simply add files to this directory of the form emailTemplate_fr.properties, etc., and all else will work automatically. The placeholder syntax to use is the same as for XXXXMessages.properties. When translating the updateEmailTemplate string, your translators should be careful to enclose placeholder {1} somewhere within an ancestor element that looks like <span class="p_span" ...>...</span> tags, and placeholder {2} somewhere within an ancestor element that looks like <div class="s_div" ...>...</div> , for the reformatting routines to work appropriately.

Implementation details, for the curious

To identify the relevant locale for an e-mail alert, the RPC sent by a subscription request -- or by the alternate subscription servlet mechanism -- includes the user's current locale and this information is saved to the datastore. The code that sends the alerts refers to this stored locale in order to determine the desired locale of the e-mail for each subscribed user.

Adding new UI strings

This section is relevant for anyone doing development work on the main living stories sourcebase, rather than developers merely providing locales without altering the underlying code. That caution aside, this is fairly easy.

If adding new strings to ClientMessages.java, no special steps are necessary. The new strings will be added to the autogenerated .properties files automatically.

If adding new strings to the server-side emailTemplate.properties file, no special steps are necessary here either. Server-side .properties files are authoritative in and of themselves.

If adding new strings to a .ui.xml file, no special steps are necessary, but you will probably have to rerun the GWT compiler with an -extra directory and inspect the autogenerated .properties file to find out the MD5 hash-based keys for your new strings.

If adding new strings to a plain .properties file, an additional step is necessary. Each of these .properties files corresponds to an automatically generated Java interface. When changing a .properties file, you need to then regenerate the interface.

If you're on Linux (or Mac, but this is untested), this part has now (as of 2/25/2010) mostly been automated for you via an Eclipse external tool builder. The only thing you need to do is specify an environment variable called SDKJAR. You can specify it in the environment from which you're running eclipse, or you can go to Project->Properties->Builders. Select the "i18nRefresh" configuration, click "Edit", click the "Environment" tab, and add an appropriate definition for SDKJAR.

An appropriate setting for SDKJAR will depend on your system setup. The path will probably end with something like plugins/com.google.gwt.eclipse.sdkbundle.2.0.0_2.0.0.v2009120620003/gwt-2.0.0/gwt-dev.jar Substitute appropriate version numbers if you have a different version of the gwt plugin.

That done, every time you save one of the source .properties file, all the interfaces will be regenerated.

On Windows, to accomplish this task you will probably need to run scripts manually. If you have cygwin installed this is easy to do via the cygwin shell; alternately, you could convert the following scripts (in the root directory of the project) to the .cmd format.

  ClientConstants-i18n
  ContentManagerConstants-i18n
  ContentManagerMessages-i18n
  LspConstants-i18n
  LspMessages-i18n

Look at gxpc.cmd and gxpc.sh for a simple example of how to do this conversion.

Then, after you've modified one of these source .properties files, step through the following procedure:

  • Launch a command line.
  • Set the SDKJAR environment variable appropriately, as above
  • Look for an appropriate script in the top living-stories directory. (For instance, if you changed ClientConstants.properties, the relevant script would be called ClientConstants-i18n)
  • Run the script
  • Optional run sanify-autogenerated-files.sh (tested on Linux; should run fine on cygwin, but untested). This will eliminate a local path from a comment in the newly autogenerated .java file -- which, uncorrected, also likely makes that line of code >100 characters in length, contrary to our coding standards -- and add a copyright notice.
  • In Eclipse, refresh the appropriate .java file (or its parent directory, or the whole project if you like.)

You will now be able to use your new UI string in new code.

To fully automate this process within Eclipse, you would have to convert the following additional files to the windows command format:

  All-i18n.sh
  sanify-autogenerated-files.sh

and then specify the location of the i18nRefresh builder as All-i18n.cmd instead of All-i18n.sh.


Sign in to add a comment
Powered by Google Project Hosting