国际化

290 阅读9分钟

现在很多系统都有国际化的功能,在实际开始做之前我们先思考几个问题:

  1. 系统真的需要国际化么

  2. 是系统语言国际化还是需要开发多套国际化系统

  3. 如何实现国际化

    第一个问题很多人会忽略,但也很重要,大多数系统刚刚起步阶段不需要国际化,等待真的有需求的时候再考虑也可以。有的人觉得现在框架都有国际化组件,就算不需要也可以先做着,等后期需要了直接使用。我个人觉得前期尽量划分出最小功能集合,大胆砍掉无用需求,即使很小的开发代价也不考虑。其实真正的考虑国际化不是一个小的代价。涉及到需求、设计、开发、测试、上线等很多流程。框架自带的功能往往无法满足需求,后期真的有需求可能也用不上。所以在初期一定考虑系统最小功能集合。

    第二个问题可能有点让人迷惑,国际化怎么还分语言和系统呢。我们考虑一个实际的情况,例如一个在中国的外国人,他在国内使用系统,但是汉语不是很好,希望系统能提供英文这个时候重点是把系统展示的信息都改成英文,这通常框架都会支持。但是这个功能真的需要么?在中国的外国人汉语真的一点不懂么?二是系统展示的语言都是很官方的标准语言,外国人真的不懂么?如果一个真的一点汉语也不懂的人,提供了系统本身的国际化之后用户就能使用了么?关联的外部系统是否也支持国际化。这就又回到了第一个问题,是否真的需要国际化。

    再考虑一种情况,一个中国的系统在国外提供服务,用户都是外国人。那么提供一套国际化系统真的可行么?通常国与国之间都有壁垒,尤其是系统数据。通常系统都需要部署多套,甚至由于各个国家人文、法律的不同系统诧异很多。大多数情况需要开发多套,在各个国家独立部署。这个时候一个系统的国际化显然不合适,需要多套系统国际化。

    第三个问题可能是开发人员考虑的重点,事实上第三个问题是最简单的问题,现在各种框架都有支持,学会使用就好,但是第三个问题也是一个复杂的问题,到底国际化要做成什么样,需要哪些功能呢?下面对于第三个问题展开说一下,由于个人能力有限,仅依据java说一下。

    说清楚一个问题最简单的方法就是由浅入深。

    简单说就是把一个语言翻译成不同的语言,根据用户配置展示出来。再抽象一层就是定义符号,一个符号关联多种语言。根据符号和语言类型选择展示内容。当然语言本身也是一种符号,所以有些开发人员会把英语作为符号。

    再深入一步,国际化的各种译文从哪里来?可以从第三方系统购买,但是购买的内容如何集成到自己的系统呢?可以开发一个自己的国际化译文管理系统,让业务人员和翻译人员通过系统不断完善译文。系统提供必要的接口与各个业务系统对接。

    下面简单说一下系统如何实现国际化和国际化译文系统什么样。

    系统如何实现国际化:

    对于java语言本身jdk就提供了国际化方案,其实原理很简单就是读取国际化译文文件,然后根据根据用户指定的语言和语言符号展示译文内容。

    基于上面的描述简单说一下java相关的类: ResourceBundle:提供了读取配置文件和查找译文的能力。例如:

ResourceBundle.getBundle("messages",new Locale("en","CN")).getString("test")

这里面包括一些约定的内容。第一个参数指定语言文件名称。约定是在resources路径下。文件messages开头。例如messages.properties.messages_zh_CN.properties等。对于初学Java 的人要注意,如果文件找不到,一定要看一下编译之后的classes路径下是否有messages文件。这个和大包工具有关,大多是是用maven。所以一定要注意maven需要将resources路径下的文件放到编译包里面。实际上第一个参数可以理解成:classpath:messages。或者自定义路径下:classpath:i18n/messages等。只要java能找到的文件就行。对于classpath是什么这里不再多说。第二个参数就是指定使用那个语言的那个区域。例如中国大陆、中国台湾。最后的getString的参数就是符号,然后就是符号的译文。这个实际测试一下就可以。不会创建java工程的可以学习java编程思想。 对于这个类,在实际使用中是远远不够的。还需要哪些内容呢? 例如我们目前请求大多数是http请求或者一些框架提供的rpc请求。那么语言通常是系统默认或者用户指定,那么后端系统如何知道使用那种语言呢?下面说一下基于http请求的几种方案。

使用那种语言通常由请求端根据服务器时区定位出语言或者客户端根据系统或者服务器配置得到默认的语言或者用户指定的语言。那么如何传递给后端呢?对于http请求可以放到head、body、cookie(属于head)里面。后端读取即可,目前框架已经很完善了。读取参数简单的调用框架接口即可。例如httprequest、serveletRequest等。拿到参数之后只要构建一个Locale实例就是上面提供jdk的getBundle的第二个参数即可。下面基于一个Spring实际的开发项目说明一下: 最关键的就是知道spring包装了哪些ResourceBundle。可以查spring代码。最常用的可能是

ReloadableResourceBundleMessageSource

提供了时时加载的能力,配置文件换了不用重启服务,还有本地缓存。这些一看代码很容就懂了。 但是我们需要处理的是http请求,需要解析httpRequest得到参数。这就需要spring mvc出马了。 Spring mvc为我们提供了什么呢?

首先我们需要通过拦截器得到使用那种语言就是得到head、body里面的语言类型参数。mvc提供了: LocaleResolver管理Locale对象。

LocaleChangeInterceptor

拦截器。

说了这么多我们到底如何使用呢?

其实实际开发中我们不会直接使用spring和spring mvc。因为

spring-boot-starter-web

已经都帮助我们封装好了。只要引入jar包。注册拦截器和语言实例,再配置一个yml文件就可以了。 主要代码如下:

@Configuration
public class MultLanguageConfigration {
    /**
     * 默认解析器 其中locale表示默认语言
     */
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.CHINA);
        return localeResolver;
    }

    /**
     * 默认拦截器 其中lang表示切换语言的参数名
     */
    @Bean
    public WebMvcConfigurer localeInterceptor() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
                localeInterceptor.setParamName("lang");  //拦截lang参数
                registry.addInterceptor(localeInterceptor);
            }
        };
    }
}
@Resource
private MessageSource messageSource;

/**
 * 多语言测试
 */
@RequestMapping("/i18ntest")
public void i18nTest() {
    System.out.println("info is " + messageSource.getMessage("pwd",null, LocaleContextHolder.getLocale()));
}

就是这么简单,那为什么还要讲到jdk、spring、spring mvc呢?一是为了理解原理,而是为了一些老系统没有spring boot。我们需要通过Spring或者spring mvc自己开发。

下面简单说一个没有Spring mvc仅有Spring的一个国际化方法: 其实上面已经说的差不多了。无非就是把start帮我们做的事情再做一遍,但是看start源码太枯燥,我就把重要的拿出来,其实作为一个简单项目也够了。

主要包括:拦截器拦截请求得到语言类型(那个国家的语言)然后创建locale,然后将locale保存到Threadlocal里面,在后面的业务代码中可以得到locale。在包装一个MessageManager。里面就是管理MessageSource的相当于jdk的ResourceBundle。主要代码如下:

public class LocaleInterceptor  implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpRequest request, HttpResponse response) throws Exception {
        String param = request.getParameter("lang");
        String paramCookie = request.getCookie("lang");
        String paramHeader = request.getHeader("lang");
        System.out.println("param is " + param + " param cookie is " + paramCookie + " param header is " + paramHeader);
        if(StringUtils.isNoneBlank(param)) {
            LocaleManager.setLocale(param);
        } else if(StringUtils.isNoneBlank(paramCookie)) {
            LocaleManager.setLocale(paramCookie);
        } else if(StringUtils.isNoneBlank(paramHeader)) {
            LocaleManager.setLocale(paramHeader);
        }
        return true;
    }

    @Override
    public void postHandle(HttpRequest request, HttpResponse response) throws Exception {

    }
}
public final class LocaleManager {
    private static final ThreadLocal<Locale> localeThreadLocal = new NamedThreadLocal<>("locale");

    private static final Locale defaultLocale = new Locale("zh");
    public static void setLocale(String localeType) {
        if(StringUtils.isBlank(localeType)) {
            localeThreadLocal.set(defaultLocale);
        } else {
            localeThreadLocal.set(new Locale(localeType));
        }
    }

    public static Locale getLocale() {
         Locale result = localeThreadLocal.get();
         if(Objects.isNull(result)) {
             result = defaultLocale;
         }
         return result;
    }
}
public class MessageServcie {
    @Autowired
    private ReloadableResourceBundleMessageSource messageSource;
    public String getMessage(String key) {
        System.out.println("locale is " + LocaleManager.getLocale().toLanguageTag());
        return messageSource.getMessage(key, null, key, LocaleManager.getLocale());
    }
}

当然不要忘记在spring的xml文件加载必要的拦截器和bean。这些属于spring内容,大家再查spring资料。

上面基本说了国际化语言的使用。那么翻译系统涉及哪些内容呢?其实也不复杂。

  1. 需要有人录入需要翻译的内容,例如中文。

  2. 需要有人对录入的内容进行翻译

  3. 翻译之后需要版本管理

  4. 需要提供各个系统的查询接口

  5. 需要为各个系统提供到处配置文件

  6. 权限管理 再精简一下就是提供一个多级标签管理。例如 pc端、财务系统、英语。三级创建一个标签:pc-财务-英语-您好。版本管理。其实版本可以认为是一个标签。还有文件导入导出。例如:翻译文件导入、配置文件导出。

    说到这里文章开头的三个问题就算完成了。本文没有具体的实现代码,只是简单的说了一些原理的东西,希望对与准备做国际化的同学有所帮助。当然具体项目代码中要比上面说的复杂的多。即使开始写demo的时候也会遇到很多问题。希望大家认真仔细的开发代码。减少不必要的错误。