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.
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 2com.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:
com.example.plugin.fruit
in the example above).basket.label
or apple
) should define the message.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.
If your plugin has already been internationalised, follow these steps to translate the plugin:
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.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.
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:
As mentioned above, a properties file contains translatable strings for your plugin in key-value pairs. For example:
1 2com.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
Create a default (English) properties files for your plugin in the following location:
1 2src/main/resources/com/example/mypluginname/mypluginprops.properties
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 2src/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.
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:
.properties
file name extension).src/main/resources
directory in your plugin which (based on the
example above), would be src/main/resources/com/example/mypluginname
.Note:
com/example/mypluginname/
shown in the example above.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 2com.example.plugin.fruit.basket.add.button=Add Fruit
Do the same for your other language properties files.
If you would like to retrieve the value of a properties file key for use in a class, follow the instructions below.
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 2getText("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 2com.example.plugin.fruit.basket.contains=Fruit basket item count: {0}
In an action class:
1 2List<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 2public 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.
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:
See the dedicated internationalising your plugin's JavaScript guide for details.
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.
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 2com.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%
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>
ConfluenceActionSupport_<locale>.properties
file, please
see Translating ConfluenceActionSupport ContentRate this page: