I18n 国际化概述
国际化也称作 i18n ,其来源是英文单词 internationalization 的首末字符
i 和 n ,18为中间的字符数。由于软件发行
可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化。通常来讲,软件中的国际化是通
过配置文件来实现的,假设要支撑两种语言,那么就需要两个版本的配置文件。
主要通过分离程序的核心逻辑 与语言、区域相关的资源 (如文本、日期格式、货币符号等),使程序能适应不同国家
/ 地区的语言和文化习惯。
Java国际化
Java自身是支持国际化的 ,java.util.Locale
用于指定当前用户所属的语言环境等信息,java.util.ResourceBundle
用于查找绑定对应的资源文件。Locale
包含了language
信息和country
信息,Locale
创建默认locale
对象时使用的静态方法:
1 2 3 4 5 6 7 8 9 private static Locale createConstant (String lang, String country) { BaseLocale base = BaseLocale.createInstance(lang, country); return getInstance(base, null ); }
Locale 类
作用 :代表特定的语言和区域(如 zh_CN
表示中文(中国),en_US
表示英文(美国))。
资源文件(Resource Bundle)
存储不同语言 / 区域的文本数据,文件名格式为
baseName_languageCode_countryCode.properties
(如
message_zh_CN.properties
)。
配置文件命名规则:
基础名(baseName
):自定义,如
message
。
语言代码(languageCode
):遵循 ISO 639-1 标准(如
zh
中文,en
英文)。
国家 / 地区代码(countryCode
):遵循 ISO 3166-1
标准(如 CN
中国,US
美国)。
basename_language_country.properties
必须遵循以上的命名规则,java 才会识别。其中,basename
是必须的,语言和国家是可选的。这里存在一个优先级概念,如果同时提供了
messages.properties
和
messages_zh_CN.propertes
两个配置文件,如果提供的
locale
符合en_CN
,那么优先查找messages_en_CN.propertes
配置文件,如果没查找到,再查找messages.properties
配置文件。最后,提示下,所有的配置文件必须放在classpath
中,一般放在resources
目录下
ResourceBundle 类
作用 :加载资源文件并根据 Locale
获取对应语言的文本。
核心方法
ResourceBundle.getBundle(String baseName, Locale locale)
:根据基础名和区域加载资源文件。
getString(String key)
:根据键获取对应的值。
演示 Java 国际化
创建资源文件
image-20250521171839434
1 2 3 4 5 6 7 8 9 10 11 greeting =Hello, Default! welcome =Default welcome message greeting =你好,世界! welcome =欢迎使用国际化功能 greeting =Hello, World! welcome =Welcome to i18n!
编写 Java 代码演示国际化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.util.Locale;import java.util.ResourceBundle;public class I18nExample { public static void main (String[] args) { Locale zhCN = Locale.CHINA; printMessage(zhCN); Locale enUS = Locale.US; printMessage(enUS); Locale frCA = new Locale ("fr" , "CA" ); printMessage(frCA); } private static void printMessage (Locale locale) { ResourceBundle bundle = ResourceBundle.getBundle("message" , locale); String greeting = bundle.getString("greeting" ); String welcome = bundle.getString("welcome" ); System.out.println("===== Locale: " + locale + " =====" ); System.out.println(greeting); System.out.println(welcome); System.out.println(); } }
输出
image-20250521171821295
Spring 中支持国际化
MessageSource接口
加载资源文件并根据Locale
提供国际化消息。
spring中国际化是通过MessageSource这个接口来支持的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface MessageSource { @Nullable String getMessage (String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) ; String getMessage (String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException; String getMessage (MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException; }
常见实现类
ResourceBundleMessageSource
这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源,需遵循上述的特定文件命名规则。
ReloadableResourceBundleMessageSource
这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新资源的信息,支持资源文件热加载(开发环境实用)。
StaticMessageSource
它允许通过编程的方式提供国际化信息,可以通过这个来实现db中存储国际化信息的功能。
LocaleResolver 接口
解析用户请求中的Locale
信息(如浏览器语言、URL
参数、Cookie 等)。
常用实现类
AcceptHeaderLocaleResolver
:基于浏览器请求头中的Accept-Language
。
SessionLocaleResolver
:基于用户会话(Session)存储Locale
。
CookieLocaleResolver
:基于 Cookie
存储Locale
。
FixedLocaleResolver
:固定Locale
,不动态变化。
使用Spring6国际化
创建资源文件
国际化文件命名格式:基本名称 _ 语言 _
国家.properties
在 Spring 配置文件中定义MessageSource
Bean,指定资源文件位置
bean名称必须为:messageSource
1 2 3 4 5 6 7 8 9 10 @Configuration public class AppConfig { @Bean public MessageSource messageSource () { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource (); messageSource.setBasenames("messages" ); messageSource.setDefaultEncoding("UTF-8" ); return messageSource; } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class I18nTest { public static void main (String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext (AppConfig.class); org.springframework.context.MessageSource messageSource = context.getBean("messageSource" , org.springframework.context.MessageSource.class); testLocale(messageSource, Locale.US); testLocale(messageSource, Locale.CHINA); testLocale(messageSource, new Locale ("fr" )); } private static void testLocale (org.springframework.context.MessageSource messageSource, Locale locale) { System.out.println("=== Locale: " + locale + " ===" ); String greeting = messageSource.getMessage("greeting" , null , locale); String welcome = messageSource.getMessage("welcome" , null , locale); String paramMessage = messageSource.getMessage( "www.ergoutree.com" , new Object []{"Alice" , new SimpleDateFormat ("HH:mm:ss" ).format(new Date ())}, locale ); System.out.println(greeting); System.out.println(welcome); System.out.println(paramMessage); System.out.println(); } }
配置 LocaleResolver
以SessionLocaleResolver
为例(存储Locale
到
Session):
1 2 3 4 5 6 7 8 9 @Configuration public class WebConfig implements WebMvcConfigurer { @Bean public LocaleResolver localeResolver () { SessionLocaleResolver slr = new SessionLocaleResolver (); slr.setDefaultLocale(Locale.US); return slr; } }
配置 LocaleChangeInterceptor
允许通过 URL
参数(如?lang=en
)切换Locale
:
1 2 3 4 5 6 7 8 9 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor (); interceptor.setParamName("lang" ); registry.addInterceptor(interceptor); } }
监控国际化文件的变化
在 Spring
框架中,ReloadableResourceBundleMessageSource
类是ResourceBundleMessageSource
的增强版本,支持动态监控资源文件的变化 ,无需重启应用即可生效。这在开发和测试环境中特别有用。
用ReloadableResourceBundleMessageSource
这个类,功能和上面案例中的ResourceBundleMessageSource
类似,不过多了个可以监控国际化资源文件变化的功能,有个方法用来设置缓存时间:
1 public void setCacheMillis (long cacheMillis)
-1:表示永远缓存
0:每次获取国际化信息的时候,都会重新读取国际化文件
大于0:上次读取配置文件的时间距离当前时间超过了这个时间,重新读取国际化文件
还有个按秒设置缓存时间的方法setCacheSeconds
,和setCacheMillis
类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class AppConfig { @Bean public MessageSource messageSource () { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource (); messageSource.setBasenames("classpath:messages" ); messageSource.setDefaultEncoding("UTF-8" ); messageSource.setCacheSeconds(0 ); return messageSource; } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import org.springframework.context.support.ReloadableResourceBundleMessageSource;import java.util.Locale;import java.util.concurrent.TimeUnit;public class ReloadableI18nTest { public static void main (String[] args) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext (AppConfig.class); ReloadableResourceBundleMessageSource messageSource = context.getBean("messageSource" , ReloadableResourceBundleMessageSource.class); printMessage(messageSource); System.out.println("\n>>> 请在5秒内修改messages_zh_CN.properties文件内容... <<<\n" ); TimeUnit.SECONDS.sleep(5 ); printMessage(messageSource); } private static void printMessage (ReloadableResourceBundleMessageSource messageSource) { Locale zhCN = Locale.CHINA; System.out.println("当前缓存时间: " + messageSource.getCacheMillis() + "ms" ); System.out.println("greeting: " + messageSource.getMessage("greeting" , null , zhCN)); } }