Spring MVC 处理国际化

Spring MVC 处理国际化

有时候我们可能需要国际化,比如开发一个需要提供中文和英文的网站,特别是比较有影响力的网站更是如此,如下图的农行,国际化一般分为语言、时区和国家信息等

image

Spring MVC 国际化核心结构

DispatcherServlet 会解析一个 LocaleResolver 接口对象,通过它来决定用户区域(User Locale),解析出对应用户系统设定的语言或者用户选择的语言,以确定其国际化。LocaleResolver 是一个接口,当 DispatcherServlet 初始化的时候会解析一个它的实现类,常用实现类如下:

实现类 特点
AcceptLanguageLocaleResolver 控制器无需额外代码,无需显式配置
SessionLocaleResolver 使用 Session 传输语言环境,根据用户 session 变量读取区域设置(可变);若 session 未设置,使用默认值(浏览器默认语言)
CookieLocaleResolver 使用 Cookie 传送语言环境,根据 Cookie 数据获取国际化信息;若用户禁止 Cookie 或未设置,通过 accept-language HTTP 头部确定默认区域

为了支持修改国际化配置,Spring MVC 提供了国际化拦截器 LocaleChangeInterceptor,通过它获取参数后,可结合 CookieLocaleResolver(浏览器 Cookie)或 SessionLocaleResolver(服务器 Session)实现国际化切换。

指定国际化资源文件

resources 目录下添加以下资源文件,用于存储不同语言的配置信息:

默认资源文件(spring 找不到对应 locale 信息时加载)

message.properties

user.title=用户登录
user.name=用户名
user.password=密码
user.submit=登录

简体中文资源文件

message_zh_CN.properties

user.title=用户登录
user.name=用户名
user.password=密码
user.submit=登录

繁体中文资源文件

message_zh_HK.properties

user.title=用戶登陸
user.name=用戶名
user.password=密碼
user.submit=登陸

英语-北美资源文件

message_en_US.properties

user.title=User login
user.name=username
user.password=password
user.submit=login

MessageSource 接口配置

MessageSource 接口是 Spring MVC 为加载消息设置的接口,此处使用 ReloadableResourceBundleMessageSource 实现类,支持系统运行期无需重启即可重新加载属性文件。

/**
 * 加载国际化资源的MessageSource
 */
@Bean
public MessageSource messageSource() {
    var messageSource = new ReloadableResourceBundleMessageSource();
    // 设置文件编码
    messageSource.setDefaultEncoding("UTF-8");
    // 文件名为 message,前缀为 classpath,后缀通过 Local 来决定
    messageSource.setBasename("classpath:message");
    // 缓存时间,0 表示不缓存(此处设置 1 小时内不重新加载)
    messageSource.setCacheSeconds(3600);
    return messageSource;
}

注意:Bean 的名称必须约定为 messageSource,这是 Spring 默认配置,不可随意修改,否则会导致 Spring 无法找到该 Bean。

语言区域解析器(LocaleResolver)配置

通过配置 LocaleResolver 确定默认使用的语言,此处以 SessionLocaleResolver 为例(使用 Session 传输语言环境)。

/**
 * 设置默认语言
 * @return 默认语言
 */
@Bean
public LocaleResolver localeResolver(){
    return new SessionLocaleResolver(); // 使用Session传输语言环境
}

完整配置类(I18nConfig)

package com.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

@Configuration
public class I18nConfig {

    /**
     * 加载国际化资源的MessageSource
     */
    @Bean
    public MessageSource messageSource() {
        var messageSource = new ReloadableResourceBundleMessageSource();
        // 设置文件编码
        messageSource.setDefaultEncoding("UTF-8");
        // 文件名为 message,前缀为 classpath,后缀通过 Local 来决定
        messageSource.setBasename("classpath:message");
        // 缓存时间,0 表示不缓存(此处设置 1 小时内不重新加载)
        messageSource.setCacheSeconds(3600);
        return messageSource;
    }

    /**
     * 设置默认语言
     * @return 默认语言解析器
     */
    @Bean
    public LocaleResolver localeResolver(){
        return new SessionLocaleResolver(); // 使用Session传输语言环境
    }

}

将 I18nConfig 装载到 WebConfig 中

package com.config;

import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * Root WebApplicationContext 配置类
     */
    @Override
    protected Class<?> @Nullable [] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    /**
     * Servlet WebApplicationContext 配置类
     */
    @Override
    protected Class<?> @Nullable [] getServletConfigClasses() {
        return new Class[]{ServletConfig.class, I18nConfig.class};
    }

    /**
     * 前端控制器 DispatcherServlet 拦截的请求
     * /: 所有的请求
     * /*: 所有的请求,不包含jsp
     * *.suffix:以 suffix 结尾的请求
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

国际化拦截器配置

ServletConfig 中通过 Java 配置国际化拦截器 LocaleChangeInterceptor

package com.config;

import com.converter.String2Position;
import com.intercreptor.RoleInterceptor;
import com.intercreptor.StopWatchInterceptor;
import org.hibernate.validator.HibernateValidator;
import org.jspecify.annotations.Nullable;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.format.FormatterRegistry;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * Spring 为了方便 spring mvc 相关配置,创建了 WebMvcConfigurer 接口
 */
@Configuration
@ComponentScan(basePackages = "com.controller")
@EnableWebMvc // 以注解的方式驱动 Spring MVC(包含注解映射的HandlerMapping及HandlerAdapter等相关组件)
public class ServletConfig implements WebMvcConfigurer {
    private final MessageSource messageSource;

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

    /**
     * 配置静态资源不被拦截
     * 静态资源:css, js, 图片, 视频, 音频, 文档
     * @param registry 资源处理器
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**") // 静态资源的url:根路径下
                .addResourceLocations("/public/"); // 静态资源存放的位置(访问链接无需包含public)
    }

    /**
     * 处理跨域问题(全局),局部跨域使用@CrossOrigin注解
     * @param registry 跨域处理器
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 允许所有的请求跨域
                .allowedOrigins("http://localhost:5173") // 跨域来源(携带cookie时不可设为*)
                .allowedHeaders("*") // 允许所有的请求头
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD") // 允许的请求方式
                .allowCredentials(true) // 允许携带 cookie
                .maxAge(3600); // 1 小时内允许跨域访问
    }

    /**
     * 配置视图解析器
     * @param registry 视图解析器
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.viewResolver(
                new InternalResourceViewResolver("/WEB-INF/pages/", ".jsp")
        );
    }

    /**
     * 配置拦截器
     * @param registry 拦截器注册器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor()); // 配置国际化拦截器
    }
}

控制器实现(用于切换 Locale)

创建控制器 I18nController,用于接收语言切换参数并更新 Session 中的区域信息:

package com.controller;

import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.util.Locale;

@Controller
public class I18nController {

    /**
     * 切换语言区域
     * @param language 语言参数(zh_CN, en_US, zh_HK)
     * @param session HTTP Session
     * @return 逻辑视图名称(跳转到登录页面)
     */
    @RequestMapping("/locale/change")
    public String locale(String language, HttpSession session) {
        // 将接收到的参数封装成 Locale 并设置到 session 中
        Locale locale = null;
        if (language == null) {
            locale = Locale.CHINA;
        } else {
            String[] langArr = language.split("_");
            locale = new Locale(langArr[0], langArr[1]);
        }
        System.out.println(locale);
        session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, locale);
        return "login"; // 跳转到登录页面
    }
}

前端页面实现(使用 Spring MVC 标签库,以 JSP 为例)

创建 JSP 页面,通过 Spring MVC 标签库 mvc:message 读取国际化资源,并提供语言切换链接:

<%--
  Created by IntelliJ IDEA.
  User: Jing61
  Date: 2025/12/29
  Time: 14:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="mvc" uri="http://www.springframework.org/tags" %>
<html>
<head>
  <title><mvc:message code="user.title"/></title>
</head>
<body>
<a href="${pageContext.request.contextPath}/locale/change?language=zh_CN">简体中文</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="${pageContext.request.contextPath}/locale/change?language=en_US">English</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="${pageContext.request.contextPath}/locale/change?language=zh_HK">繁体中文</a>
<form action="" method="post">
  <div>
    <label><mvc:message code="user.name"/> :</label>
    <input type="text" name="username"/>
  </div>
  <div>
    <label><mvc:message code="user.password"/>:</label>
    <input type="text" name="password"/>
  </div>
  <input type="submit" value='<mvc:message code="user.submit"/>'>
</form>
</body>
</html>

测试结果

简体中文(zh_CN)

访问链接:localhost:8080/locale/change?language=zh_CN
image

2. 英语-北美(en_US)

访问链接:localhost:8080/locale/change?language=en_US
image

3. 繁体中文(zh_HK)

访问链接:localhost:8080/locale/change?language=zh_HK
image

posted @ 2025-12-29 16:17  Jing61  阅读(1)  评论(0)    收藏  举报