国际化 LocaleResolver 与消息解析

同一套页面,中文用户看中文、英文用户看英文——国际化(i18n)需要解决两件事:当前请求该用哪种语言(Locale 从哪来),以及消息文案按 Locale 去哪查(MessageSource)。

第一步:准备消息文件

在 classpath 下放一组以 basename 命名的 properties,Locale 用语言后缀区分:

# messages.properties(默认语言,无后缀)
welcome=Welcome, {0}!
order.total=Total: {0}

# messages_zh_CN.properties(简体中文)
welcome=欢迎,{0}!
order.total=合计:{0}

中文内容注意 UTF-8 编码与 native2ascii(或用 IDE 的 properties 编辑器自动转码),容器读取编码问题以官方文档为准。

第二步:声明 MessageSource

Spring 的 MessageSource 负责按 key + Locale 查文案,标准实现是 ResourceBundleMessageSource:

@Configuration
public class I18nConfig {
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("messages");          // 对应 messages*.properties
        source.setDefaultEncoding("UTF-8");
        source.setFallbackToSystemLocale(false); // 找不到语言包时回退默认 messages
        return source;
    }
}
// 说明:Spring Boot 会自动配置名为 messageSource 的 MessageSource,可覆盖属性定制

第三步:Locale 从哪来——LocaleResolver

LocaleResolver 决定每个请求的 Locale。Spring MVC 默认使用 AcceptHeaderLocaleResolver——直接读请求头 Accept-Language,无需任何配置:

LocaleResolver依据特点
AcceptHeaderLocaleResolverAccept-Language 请求头默认实现,改语言靠浏览器
SessionLocaleResolver会话属性用户本次会话可切换语言
CookieLocaleResolverCookie刷新/重启后仍记住选择

可切换语言的经典搭配:SessionLocaleResolver(存储)+ LocaleChangeInterceptor(从 ?lang= 参数改):

@Configuration
public class LocaleConfig {
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver resolver = new SessionLocaleResolver();
        resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return resolver;
    }
}

// 需要配合拦截器监听切换参数(见第 30 章拦截器注册方式)
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang");        // /hello?lang=en_US 即可切换
        registry.addInterceptor(interceptor);
    }
}

第四步:在代码里取消息

@RestController
public class HelloApi {
    private final MessageSource messageSource;

    public HelloApi(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @GetMapping("/api/welcome")
    public String welcome(@RequestParam String name, Locale locale) {
        // Locale 参数由 Spring 自动注入当前请求的语言区域
        return messageSource.getMessage("welcome", new Object[]{name}, locale);
    }
}
// Accept-Language: zh-CN → 欢迎,张三!  Accept-Language: en → Welcome, ZhangSan!

不想要方法参数时,可用 LocaleContextHolder.getLocale() 拿当前线程绑定的 Locale(它在 DispatcherServlet 处理期间总可用)。

视图层的配合

JSP 有 spring:message 标签、Thymeleaf 有 #{...} 表达式,底层都走 MessageSource + 当前 Locale,适合页面文案;接口国际化则直接注入 MessageSource 返回按语言渲染的文案或错误码。

常见坑

  • properties 中文乱码:多为编码问题,统一 UTF-8 并核对读取编码。
  • 文案找不到:检查 basename 路径与文件位置(classpath 根),或开 fallbackToSystemLocale 观察行为。
  • 接口报错文案没生效:错误消息可能来自 Bean Validation 的 ValidationMessages.properties(校验消息与业务消息是两个来源,见第 26 章)。

国际化 = MessageSource(文案仓库)+ LocaleResolver(语言从哪来)+ Locale(当前语言上下文)。默认 AcceptHeaderLocaleResolver 即可起步;要做「站内一键切语言」,就上 Session/Cookie LocaleResolver + LocaleChangeInterceptor 的组合。

笔记加载中…