Java Web应用程序i18n

我已经获得了使用2.3 servlet规范将i18n引入J2EE Web应用程序的(相当艰巨的)任务。 该应用程序非常庞大,已经积极开发了8年多。

因此,我想在第一次就把事情做好,这样我就可以限制我需要在JSP,JavaScript文件,servlet和其他任何地方乱写的时间,用消息包中的值替换硬编码的字符串。

这里没有使用框架。 我怎样才能支持i18n。 请注意,我希望每个视图都有一个JSP,它可以从(a)属性文件加载文本,而不是为每个受支持的语言环境加载不同的JSP。

我想我的主要问题是我是否可以在’后端’中的某处设置区域设置(即在登录时从用户配置文件中读取区域设置并在会话中存储值),然后期望JSP页面能够正确加载指定的字符串。正确的属性文件(即,当语言环境为法语时,来自messages_fr.properties),而不是添加逻辑以在每个JSP中查找正确的语言环境。

我有什么想法可以解决这个问题吗?

在国际化应用程序时需要注意很多事情:

区域设置检测

您需要考虑的第一件事是检测最终用户的Locale。 根据您想要支持的内容,它可能很容易或有点复杂。

  1. 您肯定知道,W​​eb浏览器倾向于通过HTTP Accept-Language标头发送最终用户的首选语言。 在Servlet中访问此信息可能与调用request.getLocale()一样简单。 如果您不打算支持任何花哨的Locale Detection工作流程 ,您可能会坚持使用此方法。
  2. 如果您的应用程序中有用户配置文件,则可能需要向其添加首选语言和首选格式设置区域设置。 在这种情况下,您需要在用户登录后切换Locale。
  3. 您可能希望支持基于URL的语言切换(例如: http : //deutsch.example.com/或http://example.com?lang=de )。 您需要根据URL信息设置有效的Locale – 这可以通过各种方式完成(即URLfilter)。
  4. 您可能希望支持语言切换(从下拉菜单中选择它或其他东西),但我不推荐它(除非它与第3点结合使用)。

如果您只想支持第一种方法,或者您不打算添加任何其他依赖项(如Spring Framework),那么JSTL方法就足够了。

当我们使用Spring Framework时,它有很多不错的function,您可以使用它们来检测Locale(如CookieLocaleResolver , AcceptHeaderLocaleResolver , SessionLocaleResolver和LocaleChangeInterceptor )以及外部化字符串和格式化消息(请参阅spring:message选项卡 )。
Spring Framework允许您轻松实现上面的所有场景,这就是我喜欢它的原因。

字符串外化

这应该是容易的,对吧? 好吧,主要是 – 只需使用适当的标签。 您可能面临的唯一问题是外部化客户端(JavaScript)文本。 有几种可能的方法,但我要提到这两种方法:

  1. 让每个JSP编写已翻译字符串的数组(带有消息标记),并在客户端代码中简单地访问该数组。 这种方法更简单但可维护性更低 – 您需要实际从有效页面(实际引用客户端脚本的页面)中编写有效字符串。 我之前已经做过并相信我,这不是你想要在大型应用程序中做的事情(但它可能是小型应用程序的最佳解决方案)。
  2. 另一种方法原则上可能听起来很难,但实际上将来更容易处理。 我们的想法是在客户端集中字符串(将它们移动到一些常见的JavaScript文件)。 之后,您需要实现自己的Servlet,它将根据请求返回此脚本 – 内容应该被翻译。 您将无法在此处使用JSTL,您需要直接从Resource Bundles获取字符串。
    它更容易维护,因为你可以有一个中心点来添加可翻译的字符串。

级联

我讨厌这样说,但从Localizability的角度来看,连接真的很痛苦。 它们很常见,大多数人都没有意识到这一点。

那么什么是串联呢?

原则上,每个英语句子都需要翻译成目标语言。 问题是,正确翻译的消息使用不同的单词顺序比英语对应的多次发生(因此英语“安全政策”被翻译成波兰语“Politykabezpieczeństwa” – “政策”是“polityka” – 顺序不同)。

好的,但它与软件有什么关系?

在Web应用程序中,您可以像这样连接字符串:

 String securityPolicy = "Security " + "policy"; 

或者像这样:

 

Security policy

两者都有问题。 在第一种情况下,您需要使用MessageFormat.format()方法并将字符串外部化为(例如) "Security {0}""policy" ,在后者中您将外部化整个段落的内容(p标记) , 包括 span标签。 我知道这对翻译来说是痛苦的,但实际上并没有更好的方法。
有时你必须在你的段落中使用动态内容 – JSTL fmt:format标签也会在这里帮助你(它在后端工作石灰MessageFormat)。

布局

在本地化应用程序中,经常发生翻译的字符串比英语字符串更长的时间。 结果可能看起来很丑陋。 不知何故,你需要修复样式。 还有两种方法:

  1. 通过调整常见样式来解决问题(并祈祷它不会破坏其他语言)。 这是非常痛苦的维护。
  2. 实现CSS本地化机制。 我正在谈论的机制应该提供默认的,与语言无关的CSS文件和每种语言的覆盖。 我们的想法是为每种语言覆盖CSS文件,以便您可以按需调整布局(仅适用于一种语言)。 为此,默认CSS文件以及JSP页面不得在任何样式定义旁边包含!important关键字。 如果你真的必须使用它,将它们移动到基于语言的en.css – 这将允许其他语言修改它们。

文化特定问题

避免使用可能特定于西方文化的图形,颜色和声音。 如果您真的需要它,请提供本地化方法。 避免使用方向敏感图形(因为当您尝试本地化以说阿拉伯语或希伯来语时,这将是一个问题)。 此外,不要假设整个世界使用相同的数字(即阿拉伯语不是这样)。

日期和时区

在Java中处理日期的时间至少不容易。 如果你不打算支持除格里高利历之外的其他任何东西,你可以坚持使用内置的日期和日历类。 您可以使用JSTL fmt:timeZone,fmt:formatDate和fmt:parseDate在JSP中正确设置时区,格式和解析日期。

我强烈建议像这样使用fmt:formatDate:

  

将日期和时间转换为有效(最终用户)的时区非常重要。 将它转换为易于理解的格式也非常重要 – 这就是我推荐默认格式化风格的原因。
BTW。 时区检测并不容易,因为Web浏览器不太适合发送任何内容。 相反,您可以将首选时区字段添加到用户首选项(如果有的话)或通过客户端脚本从Web浏览器获取当前时区偏移(请参阅Date对象的方法 )

数字和货币

数字和货币应转换为本地格式。 它以与格式化日期类似的方式完成(解析也类似地完成):

  

复合邮件

您已经被警告不要连接字符串。 相反,你可能会使用MessgageFormat 。 但是,我必须声明您应该尽量减少使用复合邮件。 这只是因为目标语法规则通常是不同的,所以翻译者可能不仅需要重新排序句子(这可以通过使用占位符和MessageFormat.format()来解决),而是基于不同的方式翻译整个句子。什么将被取代。 我举几个例子:

 // Multiple plural forms English: 4 viruses found. Polish: Znaleziono 4 wirusy. **OR** Znaleziono 5 wirusów. // Conjugation English: Program encountered incorrect character | Application encountered incorrect character. Polish: Program napotkał nieznaną literę | Aplikacja napotkała nieznaną literę. 

字符编码

如果您计划将本地化为不支持ISO 8859-1代码页的语言,则需要支持Unicode – 最好的方法是将页面编码设置为UTF-8。 我见过人们这样做:

 <%@ page contentType="text/html; charset=UTF-8" %> 

我必须警告你:这还不够 。 你真的需要这个声明:

 <%@page pageEncoding="UTF-8" %> 

此外,您仍然需要在页眉中声明编码,只是为了安全起见:

  

我给你的清单并不详尽,但这是一个很好的起点。 祝好运 :)

您可以使用带标记的JSTL标准标记库来完成此操作。 获取JSTL规范的副本,阅读i8N章节,讨论一般文本+日期,时间,货币。 写得非常清楚,并向您展示如何使用标签完成所有操作。 您还可以通过编程方式设置Locale之类的内容

你不应该(并且不应该)每个语言环境都有一个单独的JSP文件。 艰巨的任务是找出不是i18n-ed的密钥并将它们移动到每个语言环境的文件,比如messages_en.properties,messages_fr.properties等等。

根据您的逻辑,可以在多个位置进行区域设置计算。 我们支持存储在数据库中的用户区域设置以及浏览器区域设置。 进入您的应用程序的每个请求都将有一个“Accept-Language”标题,指示您的浏览器配置的语言是什么,具有首选项,即首先是日语,然后是英语。 如果是这种情况,应用程序应该读取messages_ja.properties以及不在该文件中的密钥,回退到messages_en.properties。 对于存储在数据库中的用户区域设置,情况也是如此。 请注意,标准只是在浏览器中切换语言并期望内容为i18n-ed。 (我们最初开始在数据库中存储区域设置,然后从浏览器移动到支持区域设置)。 此外,您仍然需要默认值,因为翻译人员错过了将密钥和值从英语(主语言文件)复制到其他语言,因此您需要默认使用英语来获取其他文件中没有的值。

我还发现mygengo在向其他知道某种语言的人提供翻译工作时非常有用,它为我们节省了很多时间。