The Problem
Internationalization has always been a bit of a challenge. Internationalizing plugins for Atlassian products is no exception, even though Atlassian developers devoted quite a lot of thought and effort to making your job easy. If your plugin only exposes "static" user interface through velocity templates, you are mostly in luck. The situation is worse if you want to do the modern thing and go wild with javascript and ajax to make your plugin look and feel modern and sexy.Javascript support for i18n in Atlassian plugins is still a bit of a hack. You typically have to perform the following steps to internationalize your texts:
- define all your texts in property files, then translate these texts into whatever language you want your plugin be available in
- put all these strings as hidden <input type="hidden"> in a <fieldset> in your velocity template
- refer to these hidden input fields in your javascript code using AJS.params()
- it is a pretty ugly hack, requiring double lookups - first from a text to its property key, then (in another place in code) from the property key to hidden input field name
- the hidden input fields unnecessarily clutter your velocity template and the resulting HTML file
- sometimes you have no velocity template to put strings in, because you are not invoking javascript from any static file that you control. Instead, you attach your javascript as a resource through a context (web-resource), or using a filter (servlet-filter). In this case, there is no way to create hidden input fields you could refer to
The Solution
My solution to the problem consists of the following steps:- define all your texts in property files - just as before
- expose your internationalized property file from a REST resource
- in your javascript, retrieve the property file into a hashtable using REST call
- refer to the texts in javascript by their property file key
Property file
Then add a js.properties file somewhile in your plugin's resources and add your strings to it, as usual:first.key=First text second.key=Second textYou will use localized version of this file to internationalize your plugin - e.g. js_pl_PL.properties file will be used for for Polish locale.
REST Part
First create a REST component in your atlassian-plugin.xml file:
<rest key="rest-endpoint"
name="REST Component"
path="/path-to-rest"
version="1.0">
<description>Provides the REST resource for my plugin.</description>
</rest>
Now it is time to expose the contents of the properties file from your REST component.
You need REST resource class:
@Path("endpoint")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public class MyRestResource {
private static final ResourceBundle bundle
= ResourceBundle.getBundle("path.to.properties.file.js");
@GET
@Path("i18n")
@Consumes({MediaType.WILDCARD})
@Produces({MediaType.APPLICATION_JSON})
public Response getI18n() {
List<I18NStringRepresentation> result =
new ArrayList<I18NStringRepresentation>();
for (String key : bundle.keySet()) {
result.add(new I18NStringRepresentation(key, bundle.getString(key)));
}
return Response.ok(new I18NStringsRepresentation(result)).build();
}
}
and two classes to wrap internationalized strings:
@XmlRootElement
public class I18NStringsRepresentation {
@XmlElement private List<I18NStringRepresentation> strings
= new ArrayList<I18NStringRepresentation>();
public I18NStringsRepresentation() {}
public I18NStringsRepresentation(List<I18NStringRepresentation> strings) {
this.strings = strings;
}
}
@XmlRootElement
public class I18NStringRepresentation {
@XmlElement private String key;
@XmlElement private String value;
public I18NStringRepresentation() {}
public I18NStringRepresentation(String key, String value) {
this.key = key;
this.value = value;
}
}
And now the javascript part
(function() {
var i18n_strings = [];
AJS.$(document).ready(function() {
var endpoint =
contextPath
+ '/rest/rest-endpoint/1.0/endpoint/i18n'
+ '?dummy=' + new Date().getTime(); // make IE not cache responses
AJS.$.ajax({
type: "GET",
url: endpoint,
success: function(data, status, xhr) {
for (var i = 0; i < data.strings.length; ++i) {
i18n_strings[data.strings[i].key] = data.strings[i].value;
}
continueWithYourJavascriptStartupTasks();
},
});
});
}());
After this bit of initialization, you are able to refer to internationalized strings from your javascript
by their propert file key, just as you do in your Java code or in your velocity template:
alert(i18n_strings['first.key']);
AJS.$('#somediv').html(i18n_strings['second.key']);
Thanks for the information
ReplyDeletehttp://extreme-java.blogspot.com