Xpages:我们如何为客户端 JS 代码实现本地化?
Xpages: how can we implement localization for client side JS code?
我目前正在开发一个多语言 Xpages 驱动的应用程序,使用标准方法通过内部 .property
资源和 resourceBundles
本地化静态和动态字符串。在应用程序中,用户可以选择他们喜欢的语言,这个决定目前存储在用户配置文件中;我还计划将这些决定存储为浏览器 cookie。如果没有用户定义的首选项,则浏览器的默认语言会驱动应用程序中使用的语言环境。这适用于所有服务器端元素。
现在我必须添加一些客户端脚本,而且我还需要使用一些本地化的字符串。我不得不承认,我不知道哪种方法是最好的。
主要问题是:
- 我能以某种方式使用现有的文件资源/资源包吗?
- 如果是这样:我如何从我的脚本代码中引用这些资源?
- 如果不是:组织和引用我自己的文件资源的最佳方式是什么?
- 最后:如果我必须自己做这一切:我如何找出用户首选的语言环境?如果设置了 cookie(见上文),这很容易,但是如果我需要参考浏览器的首选项怎么办 and/or 如果用户不允许使用 cookie?
编辑: 我考虑过 dojo i18n way,但我不知道如何实现这样的自定义插件。
我使用以下方法:我所有的客户端 JavaScript (CSJS) 库都存储为数据库设计中的文件资源(资源 > 文件)。在库的代码中,我使用了一个类似于 EL 的符号 来标记可翻译的部分,例如:
var text="#{TRLT.TextToTranslate}";
每当我想在 XPage 上使用这些库时,我不会直接在 XPage 的资源中引用它们,而是添加对 "Helper-XPage" (js.xsp) 的引用:
<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>
js.xsp 的行为类似于 JavaScript 资源:我在 XPage 上设置了 rendered="false"
并在 beforeRenderResponse 中手动创建了一个带有 Content-Type="text/javascript"
的响应 事件。调用 XPage 时,它会读取所有 CSJS 库,将它们连接起来并根据我通常用于服务器端翻译的词典填写所有翻译。
这里是 js.xsp 的 beforeRenderResponse 事件的代码:
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:我不得不修改此处提供的代码,因为原始版本对我的通用框架有一些依赖性,还有一些额外的缓存和错误处理。因此我不能保证没有错别字,但原则上应该可以。
另一种方法是直接在浏览器中读取资源包,然后在 JavaScript 需要的地方引用它。所以回答你的主要问题:
是的,但是如果您直接从 NSF 加载它,它还不能真正使用。我建议创建一个 XPage 以将其输出为 JSON 对象:
<?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>
您需要将 1. 中的 XPage 作为客户端 JavaScript 库包括在您的页面上。我在 1 的 XPage 中创建了一个名为 translations
的全局 JavaScript 变量。因此您可以将其称为 translations.key
,其中 key
引用属性文件中的变量。
- 无需回答...
在答案 1 的 XPage 中,您可以根据用户的区域设置(浏览器语言设置)加载适当的资源包:
<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>
我目前正在开发一个多语言 Xpages 驱动的应用程序,使用标准方法通过内部 .property
资源和 resourceBundles
本地化静态和动态字符串。在应用程序中,用户可以选择他们喜欢的语言,这个决定目前存储在用户配置文件中;我还计划将这些决定存储为浏览器 cookie。如果没有用户定义的首选项,则浏览器的默认语言会驱动应用程序中使用的语言环境。这适用于所有服务器端元素。
现在我必须添加一些客户端脚本,而且我还需要使用一些本地化的字符串。我不得不承认,我不知道哪种方法是最好的。
主要问题是:
- 我能以某种方式使用现有的文件资源/资源包吗?
- 如果是这样:我如何从我的脚本代码中引用这些资源?
- 如果不是:组织和引用我自己的文件资源的最佳方式是什么?
- 最后:如果我必须自己做这一切:我如何找出用户首选的语言环境?如果设置了 cookie(见上文),这很容易,但是如果我需要参考浏览器的首选项怎么办 and/or 如果用户不允许使用 cookie?
编辑: 我考虑过 dojo i18n way,但我不知道如何实现这样的自定义插件。
我使用以下方法:我所有的客户端 JavaScript (CSJS) 库都存储为数据库设计中的文件资源(资源 > 文件)。在库的代码中,我使用了一个类似于 EL 的符号 来标记可翻译的部分,例如:
var text="#{TRLT.TextToTranslate}";
每当我想在 XPage 上使用这些库时,我不会直接在 XPage 的资源中引用它们,而是添加对 "Helper-XPage" (js.xsp) 的引用:
<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>
js.xsp 的行为类似于 JavaScript 资源:我在 XPage 上设置了 rendered="false"
并在 beforeRenderResponse 中手动创建了一个带有 Content-Type="text/javascript"
的响应 事件。调用 XPage 时,它会读取所有 CSJS 库,将它们连接起来并根据我通常用于服务器端翻译的词典填写所有翻译。
这里是 js.xsp 的 beforeRenderResponse 事件的代码:
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:我不得不修改此处提供的代码,因为原始版本对我的通用框架有一些依赖性,还有一些额外的缓存和错误处理。因此我不能保证没有错别字,但原则上应该可以。
另一种方法是直接在浏览器中读取资源包,然后在 JavaScript 需要的地方引用它。所以回答你的主要问题:
是的,但是如果您直接从 NSF 加载它,它还不能真正使用。我建议创建一个 XPage 以将其输出为 JSON 对象:
<?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>
您需要将 1. 中的 XPage 作为客户端 JavaScript 库包括在您的页面上。我在 1 的 XPage 中创建了一个名为
translations
的全局 JavaScript 变量。因此您可以将其称为translations.key
,其中key
引用属性文件中的变量。- 无需回答...
在答案 1 的 XPage 中,您可以根据用户的区域设置(浏览器语言设置)加载适当的资源包:
<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>