Skip to end of metadata
Go to start of metadata

Summary

The following is about developing internationalized/localized plugins for Confluence. Considering how simple it is to have i18n/i10n support in your plugin, there is really no excuse not to have it. It doesn't mean that you have to translate anything, but by doing it you allow others to translate the labels and messages for your plugin. By doing this you also allow others to customize the labels and messages by just overriding the properties they want to override by including a properties file in their confluence/WEB-INF/classes/(package of your plugin's properties file)/(name of your plugin's properties file).

Internationalization in plugin development is not the same as requiring/developing language packs for Confluence. For that, please see Language Pack Plugins.

If you're interested in providing translations for plugins bundled with confluence, more info on that can be found at Internationalising Confluence Bundled Plugins.

I wrote this because I couldn't find a lot of information on internationalization/internationalisation/i18n (smile) in Confluence Plugin Development except for the comments at the bottom of Web UI Plugins and what David Peterson had done in the Calendar plugin and some comments here and there. So, I thought it would be good to have something in Confluence that briefly describes how to do this, at least in a similar fashion that David Peterson used to implement it in the Calendar plugin.

Internationalizing Atlassian-plugin.xml

First, edit atlassian-plugin.xml of the plugin you are developing.

Add a new resource that is a child of the atlassian-plugin element similar to the following:

location will be the path of the properties file under your plugin's resources directory and first part of the name of the properties file. You'll want to make sure that this is packaged in your plugin as a resource in the same package you define here, with filename ending in .properties.

Attention

I actually have used "(specific package to my plugin)/AtlassianPlugin" as the location and defining the files src/main/resources/(specific package to my plugin)/AtlassianPlugin_en.properties and AtlassianPlugin.properties as I think this is a good standard.

For example: <resource type="i18n" name="i18n" location="(specific package to my plugin)/AtlassianPlugin" />
Note that Maven2 treats src/main/resources/* as the root so you do not need to specify the 'resources' directory within the location path.

Then in label elements such as the ones under web-item elements, you can set an attribute named key that defines the key in the properties file(s) you will create later. For example:

Then add properties files with the same path (package) you defined in the location under either src/etc for Maven 1, src/main/resources/ for Maven 2, or whatever location will mean that the properties files will make it into the jar in the package defined in location.

So, for this example, a Maven 2 project may contain the following i18n properties files (both identical, containing messages in English):

For French and German you'd also add:

Each of these files should contain the keys and values for the keys you defined in atlassian-plugin.xml, like:

You must use a i18n properties key that will be unique across all plugins. All plugins share the same i18n resources as well as other resources (please vote for CONF-9580), so if you define a key that is not unique to your plugin, you may override another plugin's keys or vice versa.

The properties file without locale in the filename (in this case: SomeIdentifierLikeMyPluginName.properties) usually is in English.

Internationalizing Other Classes

Inside the action, just use this to get the value of my.i18n.property.key

You can also pass in parameters as a List or an Object array as the second parameter into getText(...). Then refer to those params in the property value itself.

For example, if you pass in one param:

and in the properties was:

Then that getText method would return the message "dogs are great".

if you pass in two params:

and in the properties was:

Then that getText method would return the message "cats are better because they take care of themselves".

To utilize the getText(...) functionality outside of the main action classes, one way to do it is to create a new class like MyActionContext, set the instance of the action on it, provide getText(...) convenience methods to access those methods, and then pass the context object around to those classes that need to use it. The following is some example code for the context object:

Internationalizing Velocity Templates

If you have some .vm files to internationalize, use this in the vm file to get the value of my.i18n.property.key:

And this if you need to replace one param

And this if you need to replace two params (in property value they are referred to as {0} and {1}

For info on passing in numbers and dates and formatting them automatically for the locale, see the message format documenation at Sun.

Best Practices

Use Composite Messages

Organization/Packages/Filenames

  • Include a default (something).properties file and (something)_en.properties file in your projects code along with whatever other translations you can. It is a pain and breeds inconsistency in interface to make the user write a properties file just to get a plugin to work, so try to include as much as you can in the plugin itself. It's tough to support something if someone sends you a screenshot with whatever they wanted in the message text of tabs, etc.
  • Default (something).properties file and (something)_en.properties file should be in English (not because of any bias, just because it is the world's most dominant language across every Locale).
  • For v2 of Custom Space User Management Plugin I just put all of the i18n messages used in atlassian-plugin.xml and the classes into one file.

Property key names

  • If available use a standard glossary for standard Confluence terms like "space" for example. TODO: if there is a page for this include link here. See the Language Pack Plugins also for common translations of Confluence terms.
  • The following is totally arbitrary, but is a start for a "best practice" IMO. In that plugin I also decided that the following makes sense as a standard naming convention for keys in an i18n Java properties file (the following parts would be period-delimited for example: bulkedit.adminaction.choice.adduserstogroups** Should be in all lowercase (just to help with consistency
    • First part: a unique identifier for the plugin. All plugins share the same i18n resources as well as other resources (please vote for CONF-9580) so if you don't choose something unique to your plugin, you will almost certainly collide with another plugin.
    • First part: view, UI piece, or general category
    • Second part: object id if in UI, general type of message like error
    • Third part (optional): choice (used for checkboxes/select/radiobuttons)
    • Fourth part (optional): label/help/value of choice/some other identifier
    • See an example here (again I'm not saying this is the greatest or best practice): AtlassianPlugin_en.properties
    • Note that I'm not totally sold on this way of doing it, and please feel free to add whatever you think is better. According to Sakai, they like the type of control (button/etc) to be earlier in the key name, however when I alphabetized the i18n props, it was easier if they were grouped by the id or general name of the field group, especially in forms.

Retrieving localised/localized texts

  • From within your Action, use the default getText(...) methods defined in ConfluenceActionSupport
  • If you need to access texts from within other classes (i.e. a Macro) you can use the static method ConfluenceActionSupport.getTextStatic(...)
  • No need to access I18NBean bean directly as the classes just mentioned already wrap it. Furthermore, access to it already caused problems when it changed from class to interface in v2.5.6.

Dates

  • Roberto Dominguez says:
    • Make sure you use the appropriate services for dealing with date formatting (i.e. userAccessor.getConfluenceUserPreferences(user).getDateFormatter(formatSettingsManager) so the time zones are respected.

Tools

  • Here is a Maven 2 plugin that can help with i18n/l10n. It can report on missing keys, extra keys, messages that are same between locales/etc. and it has the ability to create pseudolocalized properties for testing:

Issues

  1. Aug 24, 2007

    My comments:

    • There are a couple of methods already providing locali(z|s)ed properties:
    • ConfluenceActionSupport.getText() can be used within actions
    • static ConfluenceActionSupport.getTextStatic() can be used outside of actions
    • AFAIK I18NBean already does the magic of dealing with the appropriate resource bundle (I haven't dig to much into it, but if somebody could look at the class). It can be retrieved through static GeneralUtil.getI18n() BUT you might not want to use it directly (see below)

    WRT I18NBean, it changed from being a class (til v2.5.5) to being an interface in v2.5.6. This has broken a number of plugins (See http://jira.atlassian.com/browse/CONF-9225, and somewhere I saw similar stacktrace on an LDAP plugin). see http://forums.atlassian.com/thread.jspa?forumID=101&threadID=19652

    IMHO, there is no need of actually accessing I18NBean, so to avoid this problem I'd recommend (best practice?) to use either ConfluenceActionSupport.getText() or static ConfluenceActionSupport.getTextStatic() that already wrap access to it.

    Also, somehow related, is making sure you use the appropriate classes for dealing with dates (i.e. userAccessor.getConfluenceUserPreferences(user).getDateFormatter(formatSettingsManager) so the time zones are respected.

    1. Aug 24, 2007

      Thanks! That's great info, and I just added it under best practices. In the plugin I was working on we decided to use our own i18n properties file for all of the properties. I can see advantages to using Atlassian's but I've also seen just between 2.5.4 and 2.5.6 that they've removed properties without telling anyone, so I think it may be hazardous to depend on those properties being there.

      1. Aug 24, 2007

        Gary,

        I wasn't referring to specific properties but to way to access the texts. I've updated the Best Practices section to clarify.

  2. Aug 24, 2007

    For the record, I would actually recommend against using the ResourceBundle directly - it will throw exceptions if you request a key which doesn't actually exist yet, which is causing some pain in the Calendar plugin at present. I would have recommended using the I18NBean, except that it seems changes in Confluence 2.5.6 have broken backwards-compatibility for that class. Very unfortunate...

    1. Aug 27, 2007

      David,

      Thanks for the input!

      I actually ended up doing a combination of the following to get i18n properties for a resource that I defined specifically for the plugin I was working on:
      1) Just calling getText(...) from the actions.
      2) Creating a context object that has the following code, and then passing it around to classes that need access to the i18n resource:

      I now see that I forgot to update the wiki page to include this, however I'm not really sure that it is the best way anyway... it worked fine though.

      Gary

      1. Oct 25, 2009

        I believe that the code above will be using the default locale, to get i18n text outside of an action I use the following code:

  3. Dec 08, 2008

    Hi there,

    Is there a way to dynamically map a key to a specific text fragment? I am creating buttons dynamically, which I am adding to the confluence UI and I am trying to figure out how to make the "label" key map to something (dynamically).

    Thanks!

    Benjamin

    1. Dec 17, 2008

      Hi,

      You can create a dynamic button by creating a javascript to change button's label. This approach is done in client side scripting as you create a javascript. Otherwise, you can create a velocity script to change button's label. Both ways, should be able to be applied.

      Cheers,

  4. Mar 06, 2009

    Btw: ConfluenceActionSupport.getTextStatic() apparently does not work within LongRunningTask s.

    I needed to inject the I18NBean into the LongRunningTask before starting the longrunning task.

  5. Oct 25, 2009

    For some reason not working for macro paramers descriptions keys. For example: confluence.extra.chart.chart.param.type.desc

    However for confluence.extra.chart.chart.desc (descripion of macro itself) working ok).

    Code used is the same

    1. Oct 25, 2009

      Looks like not present in bundle. And name generated automagically. May be return empty keys for such cases?