Last updated Mar 27, 2024

Internationalising your plugin

This is a guide for developers wanting to provide internationalisation support for their plugins. This means that other people will be able to provide translated strings for the labels and messages in your plugin without you having to recompile your plugin, thereby simplifying the process of localisation.

How Does Internationalisation Work?

Before you implement internationalisation (i18n) support for your plugin, it is important to understand how a plugin is internationalised.

First, all messages in the plugin must be moved out of code and in to a properties file in the plugin. The properties file stores the translations for all messages inside the plugin. The properties file uses the Java properties file format . Each line is a key-value pair separated by an equals sign (=). The key is used to refer to the resource in the code and the value is the translated message:

1
2
com.example.plugin.fruit.basket.label=Your Fruit Basket
com.example.plugin.fruit.apple=Apple
com.example.plugin.fruit.banana=Banana
com.example.plugin.fruit.pear=Pear
com.example.plugin.fruit.basket.add.button=Add Fruit

All i18n keys in the properties file must adhere to the following rules:

  • Must include a prefix which is unique to your plugin. We suggest using your plugin key (which would be com.example.plugin.fruit in the example above).
    The remainder of the key (for example, basket.label or apple) should define the message.
  • Must be unique.

Once you have your messages defined in a properties file, you need to use the relevant i18n APIs in the Atlassian product to replace hard-coded messages with references to your properties file keys. The sections below describe how to do that.

Translating an Existing Plugin

If your plugin has already been internationalised, follow these steps to translate the plugin:

  1. Extract the contents of the plugin JAR file.
  2. Locate the properties file which contains i18n key-value pairs.
  3. Create a copy of this file with the same file name and desired locale extension. For example, if the default properties file is called mypluginprops.properties, make a copy of this file and rename it to MyPluginName_ll_CC.properties, where '_ll_CC' is the locale extension for your required language and country.
  4. In your copied properties file, replace the default string values (not the keys) with your translated strings as desired.
  5. When you rebundle your plugin into a JAR file, ensure that your translated file is copied to the same location from where you obtained the original properties file. For example, if the original properties file was in path/to/, your copied and translated properties file needs to be in the same location.

What should you use for a locale extension?

A locale is composed of a language code and optionally a country code (as described in the Java locale documentation):

<lowercase-language-code><UPPERCASE-COUNTRY-CODE>

For example, the properties file mypluginprops_fr_CH.properties has a language code of 'fr' (French) and country code of 'CH' (Switzerland). Be aware that the language code is lower case and the country code is upper case ( capital letters). Refer to the list of language codes (ISO-639) and country codes ( ISO-3166) for more information.

A properties file with no locale extension is the default properties file and should be English.

How Do I Internationalise My Plugin?

This section provides instructions on how to internationalise your plugin from scratch.

To internationalise your plugin, please follow these steps, described in more detail below:

  1. Add internationalisation properties to your plugin
  2. Internationalise your plugin descriptor file
  3. Internationalise your plugin classes
  4. Internationalise your Velocity templates

1. Add internationalisation properties to your plugin

Create a properties files for all messages in your plugin

As mentioned above, a properties file contains translatable strings for your plugin in key-value pairs. For example:

1
2
com.example.plugin.fruit.basket.label=Your Fruit Basket
com.example.plugin.fruit.apple=Apple
com.example.plugin.fruit.banana=Banana
com.example.plugin.fruit.pear=Pear
com.example.plugin.fruit.basket.add.button=Add Fruit
  1. Create a default (English) properties files for your plugin in the following location:

    1
    2
    src/main/resources/com/example/mypluginname/mypluginprops.properties
    
  2. f you can translate your plugin into another language, enter translated message strings into a separate file for that language, and then name the file with the correct locale extension. For example, a properties file containing German message strings would be saved with the following name and extension:

    1
    2
    src/main/resources/com/example/mypluginname/mypluginprops_de_DE.properties
    

    Create a separate properties file for each language you need. For the moment, your properties files will be empty. In the following steps we will demonstrate how language-specific messages in your plugin can be moved from your plugin into the properties file.

Please Note: If you any of your translations use non-ASCII characters, which will include most languages other than English, then you must convert your text file to a Unicode-encoded ASCII text file using a tool such as native2ascii . Please refer to the Java Internationalization FAQ for more information.

Define your properties file as a new i18n resource

To register the new i18n properties file with the system, you will need to add a resource of type 'i18n' at the top level of your plugin descriptor, atlassian-plugin.xml.

1
2

<atlassian-plugin>
    ...
    <resource type="i18n" name="mypluginprops" location="com.example.mypluginname"/>
    ...
</atlassian-plugin>

The attributes on the resource element have the following meaning for i18n resources:

  • type: must always be 'i18n'.
  • name: must be unique and match the base name of your plugin properties file (that is, without the locale extension and .properties file name extension).
  • location: the path to your properties file under the src/main/resources directory in your plugin which (based on the example above), would be src/main/resources/com/example/mypluginname.

Note:

2. Internationalise your Plugin Descriptor File

If you have plugin modules that contain explicitly-defined text strings, they can be replaced with translated strings from your properties file.

For example, if you have a web-item plugin module with a explicitly-defined label in English:

1
2

<atlassian-plugin>
    ...
    <web-item>
        <label>Add Fruit</label>
        <link>/pages/addfruittobasket.action?pageId=$page.id</link>
    </web-item>
    ...
</atlassian-plugin>

You can move the contents of that label to the properties file by using the key attribute on the label element.

1
2

<atlassian-plugin>
    ...
    <web-item>
        <label key="com.example.plugin.fruit.basket.add.button"/>
        <link>/pages/addfruittobasket.action?pageId=$page.id</link>
    </web-item>

    <resource type="i18n" name="i18n" location="com.example.mypluginname.MyPluginName"/>
    ...
</atlassian-plugin>

The exact attribute name for this process may vary based on the type of module in your atlassian-plugin.xml file. For example, the Confluence Component Module requires the attribute i18n-name-key for its localisation key. You should consult the relevant documentation for the module you wish to localise. A good starting place is the Plugin Module Types documentation.

For a list of plugin modules specific to:

In your properties file, src/main/resources/com/example/mypluginname/MyPluginName.properties, add the property key with the value in English:

1
2
com.example.plugin.fruit.basket.add.button=Add Fruit

Do the same for your other language properties files.

3. Internationalise your Plugin Classes

If you would like to retrieve the value of a properties file key for use in a class, follow the instructions below.

Confluence

Within an action class, which should be a subclass of ConfluenceActionSupport, you can retrieve a string from a properties file by calling the getText() method.

For example, if your i18n properties file has the com.example.plugin.fruit.basket.add.button property defined, you can use the value of the property by calling the following method:

1
2
getText("com.example.plugin.fruit.basket.add.button")

This will return the string, "Add Fruit", assuming the same properties file as above.

If your property value includes MessageFormat-style parameters, you can pass these as an array to the call to getText().

In MyPluginName.properties:

1
2
com.example.plugin.fruit.basket.contains=Fruit basket item count: {0}

In an action class:

1
2
List<String> fruits=Arrays.asList("apple","banana","apricot");
        return getText("com.example.plugin.fruit.basket.contains",new String[]{String.valueOf(fruits.size())});

This will return the string, "Fruit basket item count: 3" in this example.

Tip: You should never write i18n strings that depend on the cardinality of the values passed to it - for instance, "com.example.plugin.fruit.basket.contains.one" or "com.example.plugin.fruit.basket.contains.multiple" - as not all languages have the same pluralisation rules as English. See Pluralising internationalisation strings to find out more on how to write i18n strings that account for plurals.

If you need to create an internationalised message for the user outside an action class, you can use the SAL I18nResolver object. It can be dependency-injected into your macro or component class through the setter or constructor:

1
2
public class AddFruitMacro extends BaseMacro {

    private final I18nResolver i18n;

    public void setI18nResolver(I18nResolver i18n) {
        this.i18n = i18n;
    }

    public String execute(Map params, String body, RenderContext context) {
        return "<button>" + i18n.getText("com.example.plugin.fruit.basket.add.button") + "</button>";
    }
}

In order to use I18nResolver (or any other SAL class), you will need to bring it into your plugin with a <component-import> like this:

1
2

<component-import key="i18nResolver" interface="com.atlassian.sal.api.message.I18nResolver"/>

Tip: Ensure that you use the appropriate services for dealing with date formatting ( userAccessor.getConfluenceUserPreferences(user).getDateFormatter(formatSettingsManager) so that the time zones are respected.

4. Internationalise your Velocity Templates

If your plugin contains English text in Velocity templates, it is relatively easy to internationalise that content as well.

To render an internationalised message in Velocity templates, use $i18n.getText(), optionally with an array of parameter values. Here is a snippet of HTML with some examples:

1
2
<p>
    $i18n.getText("com.example.plugin.fruit.basket.contains", [$fruits.size()])
</p>
<button>
    $i18n.getText("com.example.plugin.fruit.basket.add.button")
</button>

For details about the objects that are accessible from Velocity, please refer to:

5. Internationalise your JavaScript

See the dedicated internationalising your plugin's JavaScript guide for details.

Flexible Terminology

Flexible Terminology allows admins to change certain terms (sprint and epic) to any names they choose, to ensure consistent naming between Jira and Agile at Scale Frameworks (including SAFe, LeSS, etc). Updated terms are then available in both Jira and Marketplace apps. To make your translations compatible with Flexible Terminology, you need to mark all supported terms with % characters. Those terms will get new names, configured by the admin. If you, for some reason, want to keep the original names despite the admin configuration, don’t make any changes.

Preparing terms for customization

Supported terms: sprint, epic. Terminology changes apply in any variant of English. Jira will keep capitalization of original terms whenever it's possible.

To mark occurrence surround it with % characters.

1
2
com.example.plugin.fruit.basket.contains = This occurrence of %sprint% will be replaced, but this sprint will not.

Flexible Terminology supports both singular and plural forms. They should be marked as follows:

sprint%sprint%

sprints%sprints%

epic%epic%

epics%epics%

Internationalisation Testing

The Sun I18N Testing Guidelines and Techniques contains information on internationalisation testing.

The Maven 2 Localization Tools plugin can also help you test i18n/l10n. It can report on missing keys, extra keys, messages that are same between locales, and more, and it has the ability to create pseudo-localised properties for testing. You can add it to your pom.xml like this:

1
2
<project>
  ...
  <build>
    ...
  </build>
  ...
  <reporting>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>l10n-maven-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
        <configuration>
          <locales>
            <locale>de</locale>
            <locale>es</locale>
            <locale>fr</locale>
            ...
          </locales>
        </configuration>
      </plugin>
    </plugins>
  </reporting>
  ...
  <pluginRepositories>
    <pluginRepository>
      <id>codehaus.org</id>
      <name>CodeHaus Plugin Snapshots</name>
      <url></url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>
</project>

Known i18n Issues

Additional Resources

Rate this page: