Reputation: 2528
I'm currently developing a multi-lingual Xpages driven application using standard ways to localize static and dynamic strings through internal .property
resources and resourceBundles
. Within the application, users can choose their preferred language, this decision currently is stored in a user config document; I'm also planning to store those decisions as browser cookies. If there is no user defined preference the browser's default language drives the locale used within the application. This works fine for all server side elements.
Now I have to add some client side scripting, and I there need to use some localized strings as well. I have to admit that I don't have a clue which is the best approach for this.
Main questions are:
Edit: I considered going the dojo i18n way, but I don't know how to implement such a custom plugin.
Upvotes: 3
Views: 776
Reputation: 3757
An alternative is to read the resource bundles directly in the browser and reference that in JavaScript where you need it. So to answer your main questions:
Yes, but if you load it directly from the NSF it's not really usable yet. I would recommend to create an XPage to output it for you as a JSON object:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
<xp:this.resources>
<xp:bundle
src="/labels_en.properties"
var="translations">
</xp:bundle>
</xp:this.resources>
<xp:this.afterRenderResponse><![CDATA[#{javascript:
try {
var externalContext = facesContext.getExternalContext();
var writer = facesContext.getResponseWriter();
var response = externalContext.getResponse();
response.setContentType("application/json");
var jsonOutput = {};
var keys = translations.keySet();
for (var key in keys) {
jsonOutput[key] = translations[key];
}
writer.write( "var translations = " + toJson(jsonOutput) ) ;
writer.endDocument();
} catch (e) {
print(e);
}
}]]>
</xp:this.afterRenderResponse>
</xp:view>
You need to include the XPage from 1. as a client side JavaScript library on your page. I created a global JavaScript variable called translations
in the XPage from 1. so you can call it as translations.key
where key
references a variable from the properties file.
In the XPage from answer 1, you can load the appropriate resource bundle based on the user's locale (browser language setting):
<xp:this.resources>
<xp:bundle var="translations">
<xp:this.src><![CDATA[#{javascript:
var language = "en"; //default language
switch (context.getLocaleString() ) {
case "nl":
language = "nl";
break;
}
return "/labels_" + language + ".properties";}]]></xp:this.src>
</xp:bundle>
</xp:this.resources>
Upvotes: 3
Reputation: 1579
I use the following approach: All my client-side JavaScript (CSJS) libraries are stored as file resources in the database design (Resources > Files). In the code of the libraries I use an EL-like notation to mark translateable parts, for example:
var text="#{TRLT.TextToTranslate}";
Whenever I want to use the libraries on a XPage, I do not directly reference them in the XPage's resources, but add a reference to a "Helper-XPage" (js.xsp) instead:
<xp:this.resources>
<xp:headTag tagName="script">
<xp:this.attributes>
<xp:parameter name="src" value="js.xsp?lng=#{sessionScope.language}&v=#{applicationScope.versionApp}"/>
<xp:parameter name="type" value="text/javascript"/>
</xp:this.attributes>
</xp:headTag>
</xp:this.resources>
The js.xsp behaves like a JavaScript resource: I set rendered="false"
on the XPage and manually create a response with Content-Type="text/javascript"
in the beforeRenderResponse event. When the XPage is called, it reads all CSJS libraries, concatenates them and fills in all the translations based on the dictionaries that I generally use for server-side translations.
Here is the code for the beforeRenderResponse event of js.xsp:
importPackage(java.io);
importPackage(java.lang);
importPackage(java.util.regex);
importPackage(javax.servlet.http);
importPackage(org.apache.commons.io); // AFAIK this package is not installed by default (but generally very helpful)
var i,arr,lng,js,libs,c,s,m,bfr,dct,response,ec,is,os;
//---------------------------- initialize main variables
ec=facesContext.getExternalContext(); // the external context
lng=(param.lng || "en"); // the language can be provided as url parameter, otherwise use a default
dct=(applicationScope["TRLT_"+lng] || {}); // in my case, the dictionaries (HashMaps) containing the translations are stored in the applicationScope, but of course they can be loaded from anywhere (resource bundles etc.)
libs=(param.libs ? fromJson(param.libs) : ["mylib1.js","mylib2.js"]) // the library names can be provided as url parameter, otherwise use a default
//---------------------------- concatenate all libraries
js=new StringBuilder();
for (i=0;i<libs.length;i++) {
if (s=IOUtils.toString(ec.getResourceAsStream(libs[i]),"UTF-8")) js.append("\n\n"+s);
}
js=js.toString();
//---------------------------- search for and replace translateable parts
m=Pattern.compile("[#$]\\{TRLT\\.([^}]+)\\}").matcher(js);
bfr=new StringBuffer();
c=0;
while (m.find() && c<1e6) {
c++;
s=m.group(1);
m.appendReplacement(bfr,dct[s] || s || "");
}
m.appendTail(bfr);
js=bfr.toString();
//---------------------------- create the response and finalize
response=ec.getResponse();
response.setHeader("Cache-Control","max-age="+(60*60*24*365).toFixed(0)); // its important to set the expiration "a bit" into the future to prevent the browser from reloading the js.xsp everytime you reference it on another XPage; in order to force the browser to update the XPage, use versioning (see url parameter "v" in the headTag definition above)
response.setDateHeader("Expires",new Date().getTime()+(1000*60*60*24*365));
response.setHeader("Content-Type","text/javascript; name=\"libs.js\"");
response.setHeader("Content-Disposition","inline; filename=\"libs.js\"");
is=new ByteArrayInputStream(js.getBytes("UTF-8"));
os=response.getOutputStream();
IOUtils.copy(is,os);
is.close();
os.close();
facesContext.responseComplete();
return;
PS: I had to modify the codes presented here because the original versions have some dependecies to my general framework as well as some additional caching and error handling. Hence I can't guarantee that there are no typos, but in principle it should work.
Upvotes: 2