Java Web 应用程序国际化 (i18n)

8
我被分配了一个相当令人畏惧的任务,即将i18n引入使用2.3 servlet规范的J2EE Web应用程序中。该应用程序非常庞大,已经开发了8年以上。
因此,我希望第一次就做好,这样我就可以限制我需要浏览JSP、JavaScript文件、servlet和其他任何地方的时间,用消息包中的值替换硬编码的字符串。
这里没有使用框架。我如何支持i18n?请注意,我希望每个视图只有一个JSP,从(一个)属性文件(s)加载文本,而不是为每种支持的语言环境创建不同的JSP。
我猜我的主要问题是,我是否可以在“后端”(即从登录用户配置文件中读取语言环境并将值存储在会话中)设置语言环境,然后期望JSP页面能够正确地从正确的属性文件中加载指定的字符串(即当语言环境为法语时从messages_fr.properties中加载),而不是在每个JSP中添加查找正确语言环境的逻辑。
有什么想法可以解决这个问题吗?

可能是如何国际化Java Web应用程序的问题的重复。 - BalusC
3个回答

19

国际化应用程序需要注意很多事项:

区域设置检测

首先要考虑的是检测终端用户的区域设置。根据您想要支持的内容,这可能很简单或有点复杂。

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

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

我来翻译一下,这段内容和编程有关。它提到了Spring Framework,该框架具有许多不错的功能,您可以使用这些功能来检测区域设置(例如CookieLocaleResolverAcceptHeaderLocaleResolverSessionLocaleResolverLocaleChangeInterceptor),并且可以外部化字符串和格式化消息(请参见spring:message tab)。
Spring Framework可以让您轻松地实现上述所有场景,这就是为什么我更喜欢它的原因。

字符串外部化

这应该很容易,对吧?嗯,大多数情况下确实如此 - 只需使用适当的标记即可。您可能会遇到的唯一问题是在外部化客户端(JavaScript)文本时。有几种可能的方法,但让我提及其中的两种:

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

连接

我不想说,但从本地化的角度来看,连接确实很痛苦。它们非常普遍,大多数人没有意识到。

那么连接是什么呢?

原则上,每个英语句子都需要翻译成目标语言。问题是,很多时候正确翻译的消息使用的单词顺序与其英语对应物不同(因此,英语“Security policy”翻译为波兰语“Polityka bezpieczeństwa” - “policy”是“polityka” - 顺序不同)。

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

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

String securityPolicy = "Security " + "policy";

或者像这样:
<p><span style="font-weight:bold">Security</span> policy</p>

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

布局

在本地化应用程序中,经常发生翻译后的字符串比英语字符串长得多的情况。结果可能看起来非常丑陋。不知何故,您需要修复样式。这里又有两种方法:

  1. 通过调整公共样式来解决问题(并祈祷它不会破坏其他语言)来逐个解决问题。这很难维护。
  2. 实施 CSS 本地化机制。我所说的机制应该提供默认的、与语言无关的 CSS 文件和每种语言的覆盖文件。其想法是为每种语言设置覆盖 CSS 文件,以便您可以按需调整布局(仅针对一种语言)。为了做到这一点,必须将默认的 CSS 文件以及 JSP 页面中的任何样式定义旁边的!important关键字去掉。如果您真的必须使用它,请将它们移动到基于语言的 en.css 中-这将允许其他语言修改它们。

文化特定问题

避免使用可能特定于西方文化的图形、颜色和声音。如果您确实需要,请提供本地化手段。避免使用方向敏感的图形(当您尝试本地化到阿拉伯语或希伯来语时,这将是一个问题)。此外,不要假设整个世界都在使用相同的数字(即阿拉伯语不适用)。
日期和时区
在Java中处理日期和时间至少可以说是不容易的。如果您不打算支持除格雷戈里历以外的任何内容,可以使用内置的Date和Calendar类。 您可以使用JSTL fmt:timeZone、fmt:formatDate和fmt:parseDate在JSP中正确设置时区、格式和解析日期。
我强烈建议像这样使用fmt:formatDate:
<fmt:formatDate value="${someController.somedate}" 
    timeZone="${someController.detectedTimeZone}"
    dateStyle="default" 
    timeStyle="default" />

重要的是将日期和时间转换为有效的(最终用户的)时区。同时,将其转换为易于理解的格式也非常重要-这就是为什么我推荐默认格式化样式。
顺便说一句,时区检测并不容易,因为Web浏览器没有发送任何信息。相反,您可以通过客户端脚本从Web浏览器获取当前时区偏移量(请参见Date对象的方法),或者在用户首选项中添加首选时区字段(如果有)。 数字和货币 数字以及货币应该转换为本地格式。这与格式化日期的方式类似(解析也是类似的):
<fmt:formatNumber value="1.21" type="currency"/> 

复合消息

您已经被警告不要连接字符串。相反,您可能会使用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" %>

此外,为了保险起见,您仍需要在页面头部声明编码:

<META http-equiv="Content-Type" content="text/html;charset=UTF-8"> 

我给你的列表并不全面,但这是一个很好的起点。祝你好运 :)


感谢您的出色回答。不幸的是,我正在使用的应用程序非常庞大(这是一个非常古老的项目)。Spring在项目中引入了一段时间(如果我没记错的话,大约有10年),我正在使用Spring的i18n支持来部分地应用字符串。不幸的是,似乎没有人真正考虑过i18n,所以看起来我被困在了一个相当漫长而繁琐的任务中... - NRaf

1

您可以使用JSTL标准标签库中的fmt标签来实现这一点。获取JSTL规范的副本,阅读i8N章节,其中讨论了一般文本+日期、时间、货币。非常清晰地写明了如何使用标签完成所有操作。您还可以通过编程方式设置区域设置。


1

您不需要(也不应该)为每个区域设置单独的JSP文件。难点在于找出未进行国际化的键,并将它们移动到每个区域的文件中,例如messages_en.properties、messages_fr.properties等。

区域设置计算可以发生在多个位置,具体取决于您的逻辑。我们支持存储在数据库中的用户区域设置以及浏览器区域设置。每个进入应用程序的请求都会带有一个“Accept-Language”头,指示浏览器配置的语言和偏好,例如首选日语,其次是英语。如果是这种情况,应用程序应读取消息_ja.properties,并对不在该文件中的键进行回退到消息_en.properties。对于存储在数据库内的用户区域设置也可以采用同样的方式。请注意,标准只是切换浏览器语言并期望内容进行国际化。(我们最初是将区域设置存储在数据库中,然后转而支持从浏览器获取区域设置)。此外,由于翻译人员可能会漏掉从英语(主要语言文件)复制键和值到其他语言,因此您仍需要一个默认值,以便为未在其他文件中的值提供英语默认值。

我也发现mygengo非常有用,当将翻译工作交给懂某种特定语言的人时,这节省了我们很多时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接