聊一聊国际化i18n

1,891 阅读4分钟

i18n 概述

i18n 是国际化的缩写,其完整的写法是 Internationalization,翻译为国际化。

国际化是指在软件开发中对于不同语言和地区的支持。目的是为了让一款软件可以在不同的语言和地区环境下正常运行,使其适应全球各地的用户。

这通常包括对语言翻译,日期、时间和数字格式等本地化的支持。它也可以包括对某些语言字符集(如中文或日语)的支持,以确保正确显示所有字符。

国际化是为了让软件可以在全球范围内的多种语言和地区环境下正常工作,以提高用户体验并扩大其市场。

前后端在处理i18n问题时有啥区别

前端国际化处理

前端通常使用 JavaScript 库(例如 i18next)
在客户端,国际化字符串通常存储在 JSON 文件中,每个文件对应一种语言。
前端国际化库读取并解析 JSON 文件,并将相应的字符串显示在网页上。

举个例子,可以在 JavaScript 中使用以下代码:

// 加载语言文件
i18next.init({
    lng: 'en',
    resources: {
        en: {
            translation: {
                "key1": "Hello World!"
            }
        },
        fr: {
            translation: {
                "key1": "Bonjour le monde!"
            }
        }
    }
});

// 在页面上使用国际化字符串
document.querySelector('#content').innerHTML = i18next.t('key1');

前端框架通常都提供了国际化库,如 React,Vue 等。你可以使用这些国际化库来动态地切换语言,并且根据用户语言设置自动地选择合适的语言文件。
以 Vue.js 为例,可以使用 vue-i18n 来实现国际化:

import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

const i18n = new VueI18n({
  locale: 'zh', // 语言标识
  messages: {
    zh: {
      message: {
        hello: '你好,世界'
      }
    },
    en: {
      message: {
        hello: 'Hello, world'
      }
    }
  }
})

new Vue({
  i18n,
  render: h => h(App)
}).$mount('#app')

在页面模板中,可以使用以下语法来引用国际化字符串:

<template>
  <div>
    {{ $t('message.hello') }}
  </div>
</template>

后端国际化处理

MessageSource、MessageSourceAccessor和Locale是Spring框架中用于国际化的关键类:

MessageSource是一个接口,用于访问应用程序中的消息资源(如文本、错误消息等),它提供了getMessage()方法用于获取消息的文本内容。

MessageSourceAccessor是MessageSource的封装类,它简化了消息资源的访问方式,使得在代码中访问消息更加方便,例如通过getMessage(String code, Object... args)方法来获取消息内容。

Locale是表示特定地理、政治或文化区域的类,用于指定消息的语言和国家/地区。在Spring中,可以通过指定Locale对象来获取特定地区的消息内容。

因此,这三个类的关系是:MessageSource接口定义了访问消息资源的方法,MessageSourceAccessor是对MessageSource的封装,方便在代码中访问消息资源,而Locale则是指定消息的语言和地区的类,用于获取特定地区的消息内容。

在这几个类中,有一些与设计模式相关的实现方式:

MessageSource接口的设计类似于抽象工厂模式,它定义了一个可以创建消息资源的接口,具体的实现由不同的子类来完成。

MessageSourceAccessor类的设计类似于门面模式,它封装了MessageSource的具体实现细节,提供了更简洁、易用的接口,让客户端代码更容易调用。

Locale对象的使用涉及到策略模式,它代表了不同的地理、政治或文化区域,而应用程序可以根据具体的需要选择不同的Locale来获取对应的消息资源。

废话不多说了,下面提供一段使用示例:

基础用法:

1、首先,在 Spring 配置文件中配置 MessageSource 和 MessageSourceAccessor:

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  <property name="basename" value="classpath:messages" />
  <property name="defaultEncoding" value="UTF-8" />
</bean>

<bean id="messageSourceAccessor" class="org.springframework.context.support.MessageSourceAccessor">
  <constructor-arg ref="messageSource" />
</bean>

在上面的示例代码中,我们使用 ReloadableResourceBundleMessageSource 配置了 MessageSource,并使用 MessageSourceAccessor 封装了 MessageSource。
2、代码中的使用

@Controller
public class HelloController {

  @Autowired
  private MessageSourceAccessor messageSourceAccessor;

  @RequestMapping(value = "/hello", method = RequestMethod.GET)
  public String hello(ModelMap model, Locale locale) {
    String message = messageSourceAccessor.getMessage("hello.message", new Object[] { "World" }, locale);
    model.addAttribute("message", message);
    return "hello";
  }

}

进阶用法

以上是比较简单的用法:
项目里面在具体使用的时候,通常会定义几个properties文件,以键值对的形式存放,需要翻译转换的内容,key一般代表的是通用的代号,比如错误码,value通常就是错误码的实际含义。

例如,我们可以在 resources 目录下创建如下文件:

messages.properties,保存通用的英文文本内容,如错误码、按钮文本等。
messages_zh.properties,保存中文翻译文本内容。
其中,messages.properties 中的内容一般作为默认值,messages_zh.properties 中的内容则作为中文翻译文本,具体内容如下:

messages.properties:

error.code.10001=System error
error.code.10002=Database error
button.submit=Submit

messages_zh.properties:

error.code.10001=系统错误
error.code.10002=数据库错误
button.submit=提交

然后,我们就可以在 Spring Boot 项目中使用 ReloadableResourceBundleMessageSource 作为 MessageSource 实现类,同时在配置文件中指定资源文件的路径,如下所示

spring.messages.basename=classpath:messages

这样,我们就可以通过 MessageSourceAccessor 来获取对应的文本内容了,示例代码如下:

@Service
public class UserService {
    
    private final MessageSourceAccessor messageSourceAccessor;
    
    @Autowired
    public UserService(MessageSource messageSource) {
        this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
    }
    
    public String getErrorMessage(String errorCode) {
        return messageSourceAccessor.getMessage("error.code." + errorCode, "Unknown error");
    }
    
    public String getButtonLabel() {
        return messageSourceAccessor.getMessage("button.submit", "Submit");
    }
    
}