springMVC项目进行国际化改造

2018-01-25  本文已影响0人  Ray昱成

1. 概述

1.1名词定义

i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。 在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落。通常与i18n相关的还有L10n(“本地化”的简称)。

1.2 多语言的原则

笔者认为,在对我们已有系统进行多语言改造,应当遵循几个原则:

1)尽可能不改动内部代码逻辑,仅仅是在vm模板中进行更改。这样可以避免在上层改动造成逻辑上的错误;

2)对于不可预知的、没有正确配置翻译文案的字段,在进行翻译函数处理时,直接显示原语言,确保不会出错。

1.3 对今后需要多语言的系统的建议

1)禁止文案硬编码在java代码中

例如:如果一定要写入静态文本,也建议做成枚举,在Controller放入错误提示信息时也应当说明可能会出现哪些错误提示。

2)各个系统使用枚举时,一些公用的枚举,应当保持一致

2. 多语言的流程

3.多语言的配置

3.1 资源文件绑定器

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource" 
    p:basenames="file:${prod_ui_template}/config/i18n/icfmng"
    p:useCodeAsDefaultMessage="true"
    p:defaultEncoding="UTF-8"
    p:cacheSeconds="600"/>

3.2 拦截器的配置

<bean id="localeChangeInterceptor" class="XXXXXX.XXXX.IcfmngLocaleChangeInterceptor">
        <property name="paramName" value="locale"/>
        <property name="enableLocales">
            <list>
                <value>en_US</value>
                <value>zh_CN</value>
            </list>
        </property>
    </bean> 
public class IcfmngLocaleChangeInterceptor extends LocaleChangeInterceptor {
    
    /** 参数名称 */
    private String       paramName     = DEFAULT_PARAM_NAME;

    /** 默认语言 */
    private String       defaultLocale = "zh_CN";

    /** 支持的语言*/
    private List<String> enableLocales = new ArrayList<String>();
    
    /**
     * 
     * @see org.springframework.web.servlet.i18n.LocaleChangeInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws ServletException {
        String newLocale = request.getParameter(paramName);
        if (newLocale != null) {
            
            if (!enableLocales.contains(newLocale)) {
                newLocale = defaultLocale;
            }
            LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
            if (localeResolver == null) {
                throw new IllegalStateException(
                    "No LocaleResolver found: not in a DispatcherServlet request?");
            }
            LocaleEditor localeEditor = new LocaleEditor();
            localeEditor.setAsText(newLocale);
            localeResolver.setLocale(request, response, (Locale) localeEditor.getValue());
        }
        // Proceed in any case.
        return true;
    }
    
    /**
     * 
     * @param enableLocales
     */
    public void setEnableLocales(List<String> enableLocales) {
        this.enableLocales = enableLocales;
    }
}

用户切换语言的时候客户端发送get请求,local=en_US 或者 local=zh_CN 跟在url后,拦截器拦截到用户请求后,作出相应的相应。

3.3 LocaleResolver的配置

localResolver 为区域解析器,用户的区域是通过区域解析器来识别的,它必须实现localResolver接口。Spring MVC提供了几个LocaleResolver实现,让你可以按照不同的条件来解析区域。除此之外,你还可以实现这个接口,创建自己的区域解析器。

要定义一个区域解析器,只需在web应用程序上下文中注册一个LocaleResolver类型的Bean就可以了。你必须将区域解析器的Bean名称设置为localeResolver,这样DispatcherServlet才能自动侦测到它。请注意,每DispatcherServlet只能注册一个区域解析器。

一般来说可以选择基于 cookie级别的国际化(locale存cookie)或者基于session级别的国际化(locale存session),即可以从以下两个类继承:

org.springframework.web.servlet.i18n.CookieLocaleResolver

org.springframework.web.servlet.i18n.SessionLocaleResolver

这里我们以基于cookie的国际化为例。

1) 增加区域解析器的配置

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

      <property name="cookieName" value="locale"/>

      <property name="defaultLocale" value="zh_CN"/>

  </bean>

   在这里可以配置 cookieName 和默认的语言。

3.4 资源文件的规则

资源文件绑定器指明资源文件的路径和文件名统一的前缀,并且设定了所有的语言选项,至此,资源文件的命名就确定下来。在进行多语言的时候,框架会根据当前的语言选项,自动帮我们找到对应的资源文件进行多语言“翻译”。

如果需要多语言的字段较多,建议在excel 整理好,再用脚本语言去生成配置文件,这样不容易出错。

要注意的是,不要在eclipse下修改资源文件,eclipse下打开资源文件是乱码,修改会出错。建议使用其它编辑软件。

4. 多语言的使用

完成了多语言的配置后,就可以开始对页面进行多语言啦!在进行多语言的时候,只需要调用getMessage 即将多语言的key映射到对应的多语言翻译的字段。

4.1 Controller层进行多语言

在Controller层进行多语言需要先获得web容器上下文 WebApplicationContext,代码示例如下:

/**
     * 根据key获取多语言value
     * @param key
     * @param arry 填充翻译后的参数
     * @param defult 默认值
     * @return
     */
    public static String getValueByKey(String key, Object[] arry,
                                       String defult) {
        try {
            WebApplicationContext ctx = RequestContextUtils.getWebApplicationContext(request);
            LocaleResolver localeResolver = ctx.getBean("localeResolver", LocaleResolver.class);
            return ctx.getMessage(key, arry, defult, localeResolver.resolveLocale(request));
        } catch (Exception e) {
            ExceptionUtil.caught(e, "根据key获取多语言value值出错|key=", key);
        }
        return defult;

    }

在这里获得上下文以后,取出localeResolver这个Bean,解析当前request的语言选项参数,即可调用 ctx.getMessage(key, new Object[] {}, "", locale); 对key值进行多语言的映射,返回对应的多语言字段。

getMessage方法中,key对应多语言资源文件的键值,第二个参数是用于填充到翻译后的字段的,比如 若翻译后为“当前第{0}页,总共{1}页”
第二个参数的两个String就可以填充进去。 第三个参数为默认的返回值,即翻译失败时返回这个。第四个参数为当前的语言选项。

4.2 vm模板中进行多语言

在vm模板中进行多语言会简单很多,这里建议使用宏来调用getMessage方法,代码如下:

## 根据用户的语言选择对 $key进行翻译

## #macro(t $key, $args , $defaultMessage)

##   #if($stringUtil.isBlank($args))

##     #set($args = [])

##   #end

##   #if($stringUtil.isBlank($defaultMessage))

##        #set($defaultMessage = $key)

##   #end

##   #SLITERAL($springMacroRequestContext.getMessage($key, $args , $defaultMessage))

## #end

#macro(t $key, $args , $defaultMessage)#if($stringUtil.isBlank($args))#set($args = [])#end#if($stringUtil.isBlank($defaultMessage))#set($defaultMessage = $key)#end#SLITERAL($springMacroRequestContext.getMessage($key, $args , $defaultMessage))#end

调用t这个宏,第二个参数用于填充,第三个参数为默认返回值,如果第三个参数不填,如果在资源文件中找不到时会返回key。

在页面vm模板中调用宏:
#t("COMMON_ORDER_NO") 或者 #t('COMMON_ORDER_NO')

---------------------------------分隔线---------------------------------------------------

至此,多语言的实现原理已经介绍完了,如果你是在新做一个系统,那么一个比较合理的做法应该是:

Ⅰ 对于静态的字段的多语言,直接在页面vm模板上调用getMessage方法进行多语言映射;
Ⅱ 对于动态的字段,在Controller层进行多语言。这就需要在写代码的过程中特别注意,任何你需要在前端展示的字段,都不要硬编码写死在程序中,而是使用枚举。

比如在Controller层中调用了某一方法,这一方法里面可能会抛出异常,而这一异常的错误信息又需要在前端展示,那么切忌将错误信息写死在代码,这会使后期多语言改造非常困难。如果将错误信息写成枚举,那么在Controller中即可根据该枚举取得key值(比方说,我们的做法是把枚举的classname和name拼在一起作为key值),这样就可以很方便地在Controller层进行多语言。

上一篇下一篇

猜你喜欢

热点阅读