Roo中的i18n

什么是i18n

中文维基上对i18n有这样的解释:

国际化与本地化(英文:Internationalization and Localization)通常简写为I18n和L10n。

基于他们的英文单字长度过长,常被分别简称成i18n(18意味着在国际化这个单字中,i 和 n 之间有 18 个字母)及L10n。

注意区别程序国际化和程序本地化的区别:程序国际化一般指为程序将程序的显示语言与程序内部显示逻辑脱钩的过程,程序本地化一般指根据特定区域设置调整程序显示语言的过程。

Roo中的i18n

在Roo项目的WEB-INF/i18n文件夹下,默认会有两个message文件,分别为:application.propertiesmessages.properties。如何理解这两个文件?

application.properties 和 messages_*.properties

在Roo项目中全局搜索application.properties关键字,在webmvc-config.xml文件中发现如下代码:

从注释可以看出,messages*.propertiesapplication.properties 文件的作用是为程序的国际化提供本地化翻译(有点绕)。其中messages*.properties提供了的Roo自动生成的通用的本地化描述,application.properties提供了程序业务实体等“application specific”的本地化描述。

注意application.properties文件也可以进行扩展,例如:

Spring 框架会自动匹配语言设置,选取适当的application文件。

那么,这两者有什么区别呢?这里有一段描述:

The main purpose of two different files here is that the messages_*.properties files contain translations of commonly used labels in a Roo Web UI. Roo does actually not manage these files, it just copies them into the target project.

The application.properties file contains all labels that are specific to your application (like field labels, entity labels, menu labels). Roo would manage this file if a new field or entity is added but it would obviously not be able to not translate those.

可以看出,messages_*.properties中的“键值对”不受Roo控制,属于完全由用户自定义的范围。application.properties文件则由Roo管理,例如当你为某个Entity用Roo Shell建立了CRUD页面的时候,Roo自动在application.properties中添加了一系列类似“label_demo_imlab_ims_entity_Sensor”的键值对作为某个label的显示内容。这些键值对的key是根据entity生成的,后面会提到。值得注意的是,由于Roo不会帮你自动翻译label,生成对应的application_*.properties文件,所以Roo将其统一添加到唯一的application.properties文件中。

那么,spring框架是如何使用这两个文件的呢?注意到上述代码中的:

也可以写成:

messageSource怎么理解?ReloadableResourceBundleMessageSource有什么作用?

ResourceBundle和MessageSource

我们先把ReloadableResourceBundleMessageSource分解成两个概念:ResourceBundle和MessageSource。这里有一篇好文章,详细地介绍了spring mvc的Messages和Internationalization机制:

ResourceBundle

A ResourceBundle is the Java abstraction to get locale-specific resources such as objects and text strings.

ResourceBundle就像一个工具类,实现了根据baseName和locale参数读取不同message文件。例如我们可以这样使用ResourceBundle:

我们可以根据locale的值拿到名为“baseName_*”的不同的resources。ResourceBundle搜索资源文件的顺序如下所示,设locale的设置为(language1, country1, and variant1):

  • baseName + “” + language1 + “” + country1 + “_” + variant1
  • baseName + “” + language1 + “” + country1
  • baseName + “_” + language1
  • baseName + “” + language0 + “” + country0 + “_” + variant0
  • baseName + “” + language0 + “” + country0
  • baseName + “_” + language0
  • baseName

ResourceBundle会在依照这个顺序从上网下搜索,返回第一个满足条件的文件。

MessageSource

MessageSource是一个接口,如下所示:

我们通过MessageSource接口的getMessage()方法取得本地化值。值得注意的是,实现MessageSource接口时,读取资源的工作一般来说交由ResourceBundle来负责,再在其之上添加占位符参数支持等功能。注意MessageSource接口的实现类在context中必须以messageSource命名。

ReloadableResourceBundleMessageSource

Roo默认采用ReloadableResourceBundleMessageSourceMessageResource的实现类,它具有自动定时更新Message缓存而无需重启服务器的特性。默认“WEB-INF/i18n/messages”和“WEB-INF/i18n/application”这两个basename作为搜索的关键字。fallbackToSystemLocale属性设置为false意味着当MessageSource找不到对应的资源文件时将使用默认message文件而不是服务器本地化(如message_EN)文件。

使用方法

在java文件里, 我们可以这样用:

在jsp里,我们可以这样用:

就可以取出本地化label值。或者:

把“message”对应的本地化值保存在”message_var”这个jsp变量中。

但是我们注意到,Roo默认生成的项目中,语言选项选定以后,项目中每一个url地址的资源都被本地化了,这是怎么做到的呢?

webmvc-config.xml文件中我们可以看到这样的代码:

可以看到代码为HandlerMapping注册了LocaleChangeInterceptor这个拦截器。LocaleChangeInterceptor的作用可以看这里

Interceptor that allows for changing the current locale on every request, via a configurable request parameter.

可以看出spring mvc正是通过这个拦截器调用MessageSource来完成本地化的。但locale参数如何获得?留意到webmvc-config.xml文件中这一行代码:

参考这里,可知:

在Spring MVC应用程序中,用户的区域是通过区域解析器来识别的,它必须实现LocaleResolver接口。Spring MVC提供了几个LocaleResolver实现,让你可以按照不同的条件来解析区域。

注意要将LocaleResolver的实现bean命名为“localeResolver”,DispatcherSelvlet才能自动侦测到。具体地可以分为如下几种:

AcceptHeaderLocaleResolver:通过检验HTTP请求的accept-language头部来解析locale。 SessionLocaleResolver:通过检验用户session中预置的属性来解析locale。 CookieLocaleResolver:通过检验用户cookie中预置的属性来解析locale。

相应bean的属性设置请参考api文档。

最后

综上所述,我们可以看到spring mvc实现i18n的方式:通过LocaleChangeInterceptor来调用LocaleResolver获得用户设定或者默认的locale参数;项目中某个label的显示不依赖于语言,而是通过message机制结合locale属性动态地改变。显示层在渲染label时通过message code和locale属性在message.properties和application.properties文件中寻找对应的本地化值。