spring中i18n国际化处理多语言

news2024/11/27 10:38:42

前言

在项目中,往往用户会存在多语言的述求,比如说一个系统既有中文的用户,又有英文的用户。怎么来实现多语言呢?

首先前后端分离的项目,前端会有自己的多语言实现方案,大致效果就是,用户切换语言,那些静态的按钮,菜单,标签等前端都可以自己切换。但是调用后端由后端返回的异常提示,消息体等,也需要后端实现多语言,大致的实现方案就是由前端传入一个参数,表示是期望后端提供什么类型的语言的消息体,后端就可以通过这个来实现国际化的消息了。本文介绍的是采用spring的国际化支持来实现国际化语言。

一、spring中国际化相关概念及理论知识

MessageSource接口

spring中国际化是通过MessageSource这个接口来支持的,该接口中定义了3个获取国际化消息的方法。

org.springframework.context.MessageSource

public interface MessageSource {
    /**
     * 获取国际化信息
     * @param code 表示国际化资源中的属性名;
     * @param args用于传递格式化串占位符所用的运行期参数;
     * @param defaultMessage 当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;
     * @param locale 表示本地化对象
     */
    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
    /**
     * 与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常
     */
    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
    /**
     * @param MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个方法相同
     */
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

国际化消息的三个实现类

ResourceBundleMessageSource

这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源

spring boot中默认使用该类实现。

读取配置文件中配置的国际化信息

在application.yml中配置一下配置就能实例

spring:
  messages:
    #resources/i18n/messages_zh_CN.properties  ,取i18n/messages,规则为取文件目录开始,文件前缀结束
    basename: i18n/messages
    # 国际化编码
    encoding: utf-8
    # 在找不到当前系统对应的资源文件时,如果该属性为 true,则会默认查找当前系统对应的资源文件,否则就返回 null,返回 null 之后,最终又会调用到系统默认的 messages.properties 文件
    fallback-to-system-locale: true

ReloadableResourceBundleMessageSource

这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新资源的信息

该类也是读取配置文件中的国际化信息

实例化该类的方案如下 

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:i18n/messages");
        messageSource.setDefaultEncoding("utf-8");
        //设置缓存时间1个小时 1*60*60*1000毫秒
        //可以设置成-1表示永久缓存,设置成0表示每次都从文件中读取
        messageSource.setCacheMillis(1*60*60*1000);
        messageSource.setFallbackToSystemLocale(true);

        return messageSource;
    }

StaticMessageSource

它允许通过编程的方式提供国际化信息。

比如说配置好信息存放在数据库中,自己可以自定义去db中获取

spring中使用国际化的步骤

通常我们使用spring的时候

1、国际化语言配置文件,

通常在src/main/resources目录下建i18n文件夹,再创建各类语言的properties资源文件

i18n/messages.properties
i18n/messages_en_US.properties
i18n/messages_zh_CN.properties

当然也可以将国际化语言资源包放置在其他载体中,如数据库。

2、向容器中注册一个MessageSource类型的bean,bean名称必须为:messageSource

实现类可以用上面说的三种中选择一种

3、从spring容器中获取MessageSource类型的bean,调用getMessage方法获取配置的语言内容

4、如果是web类型的程序,语言的选择应该还要根据请求来定。一般情况,可以让前端传一个语言参数,参数可以放置在cookie,header,parameter都可以。然后通过一个拦截器获取到参数,传递给线程变量,在获取语言的时候,从线程变量中取出该值,就可以动态的根据请求获取对应的语言信息了。

二、一个国际化的demo例子

需要自定义的几个类及文件

com.shenyun.flinkgui.i18n.constant.LanguageBaseConstant 类


package com.shenyun.flinkgui.i18n.constant;

public class LanguageBaseConstant {
    public static final String LOCALE_LANGUAGE_KEY = "language";
}

com.shenyun.flinkgui.i18n.interceptor.LocaleChangeInterceptor 类

package com.shenyun.flinkgui.i18n.interceptor;


import com.shenyun.flinkgui.i18n.constant.LanguageBaseConstant;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class LocaleChangeInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // cookie :language=en_US
        // header :language=en_US
        // parameter :language=en_US
        //通过制定cookie,header,param
        // 获取本地语言的优先级  cookie<header<parameter
        String locale="";

        //1、处理cookie中带有的语言信息
        Cookie cookie = WebUtils.getCookie(request, LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
        String cookieLocale="";
        if (cookie!=null) {
            // Proceed in cookie
            cookieLocale=cookie.getValue();
        }
        if(cookieLocale!=null&& !cookieLocale.trim().equals("")){
            locale=cookieLocale;
        }

        // 2、每次请求的header中可以单独指定语言  language=zh_CN
        String headerLocale = request.getHeader(LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
        if(headerLocale!=null && !headerLocale.trim().equals("")){
            locale=headerLocale;
        }

        // 3、从get 参数数据获取 language=zh_CN
        String parameterLocale=request.getParameter(LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
        if(parameterLocale!=null && !parameterLocale.trim().equals(""));{
            locale=parameterLocale;
        }

        //4 语言信息写入线程变量
        if (locale!=null) {
            LocaleContextHolder.setLocale(parseLocaleValue(locale));
        }

        return true;
    }

    @Nullable
    protected Locale parseLocaleValue(String localeValue) {
        return StringUtils.parseLocale(localeValue);
    }

}

因LocaleChangeInterceptor类是我自己自定义实现的,逻辑为,可以通过cookie,header,parameter传入使用的指定的语言类型。大致情况如下。

// cookie :language=en_US
// header :language=en_US
// parameter :language=en_US
// 通过制定cookie,header,param
// 获取本地语言的优先级  cookie<header<parameter

spring也有默认的实现,就看自己怎么选择了。

com.shenyun.flinkgui.i18n.utils.MessageResolverUtils 类

package com.shenyun.flinkgui.i18n.utils;

import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;

import java.util.Arrays;

/**
 * desc: 获取i18n资源文件
 */
public class MessageResolverUtils {

    @Autowired
    private static MessageSource messageSource = SpringUtil.getBean(MessageSource.class);

    public MessageResolverUtils() {
    }

    /**
     * 根据 messageKey 获取国际化消息 委托给 spring messageSource
     *
     * @param code 消息key
     * @return 解析后的国际化
     */
    public static String getMessage(Object code) {
        return messageSource.getMessage(code.toString(), null, code.toString(), LocaleContextHolder.getLocale());
    }

    /**
     * 根据 messageKey 和参数 获取消息 委托给 spring messageSource
     *
     * @param code        消息key
     * @param messageArgs 参数
     * @return 解析后的国际化
     */
    public static String getMessages(Object code, Object... messageArgs) {
        Object[] objs = Arrays.stream(messageArgs).map(MessageResolverUtils::getMessage).toArray();
        String message =
                messageSource.getMessage(code.toString(), objs, code.toString(), LocaleContextHolder.getLocale());
        return message;
    }
}

com.shenyun.flinkgui.configure.AppConfiguration 类

package com.shenyun.flinkgui.configure;


import com.shenyun.flinkgui.i18n.constant.LanguageBaseConstant;
import com.shenyun.flinkgui.i18n.interceptor.LocaleChangeInterceptor;
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.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;

import java.util.Locale;


@Configuration
public class AppConfiguration implements WebMvcConfigurer {

    @Bean(name = "localeResolver")
    public LocaleResolver localeResolver() {
        CookieLocaleResolver localeResolver = new CookieLocaleResolver();
        localeResolver.setCookieName(LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
        // 设置默认语言,简体中文
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        // set language tag compliant
        localeResolver.setLanguageTagCompliant(false);
        return localeResolver;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        return new LocaleChangeInterceptor();
    }

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:i18n/messages");
        messageSource.setDefaultEncoding("utf-8");
        //设置缓存时间1个小时 1*60*60*1000毫秒
        //可以设置成-1表示永久缓存,设置成0表示每次都从文件中读取
        messageSource.setCacheMillis(1*60*60*1000);
        messageSource.setFallbackToSystemLocale(true);

        return messageSource;
    }

    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

 配置message资源文件

默认资源messages.properties

unknown.i18n=未知的国际化信息,请检查。。。

# response messages
response.get.success=获取成功

# validation messages
param.test.ok=参数 {0} 的值 {1}

英文配置messages_en_US.properties

unknown.i18n=Unknown i18n information, please check. . .

# response messages
response.get.success = Get success

# validation messages
param.test.ok=parmam {0} value is {1}

中文配置messages_zh_CN.properties,与默认配置保持一样即可

国际化语言使用

//根据配置的key获取国际化msg
String msg=MessageResolverUtils.getMessage("response.get.success");

以下是在controller层的一个使用例子,具体的情况根据自身的业务情况

@Controller
@RequestMapping("language")
public class LanguageController {


    @ResponseBody
    @RequestMapping("getLanDemo")
    public String getLanDemo(){
        return MessageResolverUtils.getMessage("response.get.success");
    }
    @ResponseBody
    @RequestMapping("getLanDemo2")
    public String getLanParamDemo(String name){
        return MessageResolverUtils.getMessages("param.test.ok","name",name);
    }
}

三、国际化消息配置在数据库中的思路

国际化消息实现类中,有一个StaticMessageSource,这个类中有2个方法比较重要

public void addMessage(String code, Locale locale, String msg); 
public void addMessages(Map<String, String> messages, Locale locale);

自定义一个StaticMessageSource类

public class MessageSourceFromDb extends StaticMessageSource implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        //此处我们在当前bean初始化之后,模拟从db中获取国际化信息,然后调用addMessage来配置国际化信息
        this.addMessage("desc", Locale.CHINA, "我是从db来的信息");
        this.addMessage("desc", Locale.UK, "MessageSource From Db");
    }
}

上面的类实现了spring的InitializingBean接口,重写afterPropertiesSet方法,这个方法会在当前bean初始化之后调用,在这个方法中模拟从db中获取国际化信息,然后调用addMessage来配置国际化信息

使用上面的类,实现MessageSource接口的实例化类即可从数据库中获取国际化消息,但是数据的自己设计表结构。

参考项:

1、文章:https://blog.csdn.net/ysj_2021/article/details/125650190

2、项目:Dinky: Dinky 是一个开箱即用的一站式实时计算平台,以 Apache Flink 为基础,连接 OLAP 和数据湖等众多框架,致力于流批一体和湖仓一体的建设与实践。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92143.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rocketmq源码-pull模式拉取消息、同步拉取消息

前言 上一篇博客&#xff0c;记录的是push模式&#xff0c;异步发送netty请求拉取消息的代码&#xff0c;这篇博客主要记录consumer发送同步netty请求&#xff0c;去拉取消息的逻辑&#xff0c;但是对于同步发送请求&#xff0c;需要结合LitePullConsumer来看 在Lite PullCon…

C语言基础—运算符及优先级

本章主要讲解运算符的优先级和结合顺序 知识点&#xff1a; 运算符分类&#xff0c;记忆了解13种运算符注意运算符的易错点运算符的优先级 运算符及优先级运算符分类❗ 运算符易错不能直接连续判断“< 值 <”关于判断两个浮点数是否相等逻辑表达式&#xff08;布尔型&am…

CSS:border-image

border-image属性对图像的规格和比例比较高&#xff0c;导致使用成本比较高。另外&#xff0c;常见的场景中&#xff0c;大家更倾向于扁平化而不是非拟物化&#xff0c;边框装饰通常在项目中不会出现。 border-image是由多个CSS属性缩写的&#xff0c;比如: border-image-sou…

hc32和stm32 can波特率设置

前言 笔者在调试一款新的mcu的can通信时候&#xff0c;最麻烦的是波特率设置。由于没有弄明白其计算原理&#xff0c;经常出错&#xff0c;且不同的波特率有不同的采样点的要求。浪费了不少时间。这次一次搞明白can波特率的计算公式。 can波特率计算 在ISO 11898-1-2015 标准…

音视频基础概念(2)——音频

目录 1. 基本知识 2.采样率和采样位数 3.音频编码 4. 声道数 5. 码率 6. 音频格式 日常生活中&#xff0c;音视频随处可见&#xff0c;包括视频、音频、编解码、封装容器、音视频等概念。 1. 基本知识 音频数据的承载方式最常用的是脉冲编码调制&#xff0c;即PCM。于…

JAVA面试(2022年Java常见面试问题)

1、谈谈你对Spring中IOC和AOP的理解。 答案&#xff1a; 2、谈谈Spring的bean的创建过程和生命周期。 答案&#xff1a; 3、谈一下JVM的内存分配和垃圾回收机制。 答案&#xff1a; 4、谈一下你使用比较多的设计模式和场景。 答案&#xff1a; 5、谈一些mysql的事务隔离。 …

什么是文件系统?

【推荐阅读】 一文了解Linux上TCP的几个内核参数调优 一文剖析Linux内核中内存管理 分析linux启动内核源码 文件系统是操作系统用于明确存储设备&#xff08;常见的是磁盘&#xff0c;也有基于NAND Flash的固态硬盘&#xff09;或分区上的文件的方法和数据结构&#xff0c;…

传奇列表上传登录器公告小窗口怎么修改

传奇列表上传登录器公告小窗口怎么修改 很多小伙伴不会上传列表&#xff0c;我是艾西今天给大家分享下怎么上传列表 我们开始实操&#xff08;纯教学分享&#xff09; 在我们的网站文件夹里创建一个列表.txt 在浏览器里找一个列表模板例&#xff1a;www.pkp123.cn:88&#xff…

1570_AURIX_TC275_SCU_ERU

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 从系统的逻辑图看&#xff0c;能够很清楚看到这个模块的功能处理过程&#xff1a;首先是边沿信号的识别&#xff0c;接着是根据判断进行置位处理&#xff0c;最后进行工作触发。如果设置的…

使用Nordic的nRF52840 Dongle配合Wireshark对蓝牙设备抓包(BLE)

硬件准备&#xff1a; 1&#xff09;nRF52840 Dongle 2&#xff09;待抓包的蓝牙设备 软件准备&#xff1a; 1&#xff09;Python 2&#xff09;Wireshark 3&#xff09;nRF Sniffer for Bluetooth LE Python安装 需要注意的是下载的nRF Sniffer for Bluetooth LE版本是否…

K8s 之 Deployment 应用案例

目录一、YAML 配置文件二、运行服务三、更新 Deployment3.1 动态伸缩容3.2 触发上线四、故障自动转移五、指定节点运行 Pod六、删除 deployment一、YAML 配置文件 我们要清楚&#xff0c;在 K8s 中有两种创建资源的方式&#xff1a; &#xff08;1&#xff09;命令行方式&…

【GO】 K8s 管理系统项目[API部分--Pod]

K8s 管理系统项目[API部分–Pod] 前端: Vueelement plus 后端: gogin 1. 功能设计 2. 初始化 2.1创建项目 2.2 配置goproxy GOPROXYhttps://goproxy.cn 2.3 添加格式化工具 2.4 安装模块 go get k8s.io/client-go/tools/clientcmd go get k8s.io/api/core/v1 go get k8s.i…

使用 docker buildx 构建跨平台 Go 镜像

目录 前提 docker buildx 启用 Buildx builder 实例 构建驱动 buildx 的跨平台构建策略 一次构建多个架构 Go 镜像实践 源代码和 Dockerfile 执行跨平台构建 验证构建结果 如何交叉编译 Golang 的 CGO 项目 准备交叉编译环境和依赖 交叉编译 CGO 示例 总结 参考链接…

供水设备远程监控客户案例

一、客户介绍 客户积累多年的技术研发和工程运维经验&#xff0c;对传统的恒压供水工程所面临的维护难、维修难、运维效率低和能耗管控弱等诸多问题有深刻的体会&#xff0c;经过广泛调研&#xff0c;客户最终选择使用蓝蜂物联网的云平台和边缘计算产品对恒压供水设备和工程进行…

数据处理指令(一)—— 搬移指令MOV、MVN

数据处理指令指的是和数学运算、逻辑运算相关的指令&#xff0c;比如加减乘、与或非、赋值比较等 目录 1、MOV —— 直接搬移 (1) MOV 指令格式 (2) MOV生成指令的策略&#xff08;MOV的优点&#xff09; (3) MOV 只能搬移“立即数”的原因&#xff08;MOV的缺点&#x…

问卷设计一:问卷题目哪些有类型和注意要点?

问卷法常被人们应用于社会调查中&#xff0c;它能反馈出最真实的社会信息。所以&#xff0c;很多企业为了最大程度地了解市场&#xff0c;也经常使用问卷调查法进行研究。不过&#xff0c;想要发挥出问卷法的最大用处&#xff0c;前提是要将问卷设计规范并且可量化。 想要设计…

用ArkTs在鸿蒙系统上画一个世界杯海报

偶然看到了CSDN关于世界杯的征文活动&#xff1a; 用代码画一个足球&#xff1f; 哈哈很有意思&#xff01; 想了想&#xff0c;画一个自定义View&#xff08;足球&#xff09;&#xff0c;当然是使用Canvas了&#xff0c;但除了Canvas还有没有其它方法呢&#xff1f;那是必须…

c语言算数转换 操作符

【题目名称】下面代码的结果是&#xff1a;( b)#include <stdio.h> int main() {int a, b, c;a 5;c a;b c, c, a, a;//逗号表达式从左向右以此计算 表达式结果是最后一个表达式b a c; //a9 先算加后算加等printf("a %d b %d c %d\n:", a, b, c);retu…

【软件测试】工作瓶颈?测试的出路在哪?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 3年软件测试经验&am…

【内网安全-隧道技术】SMB、ICMP、DNS隧道、SSH协议

目录 一、基础知识 二、隧道技术 1、简介&#xff1a; 2、SMB隧道 3、ICMP隧道 4、DNS隧道 5、SSH协议 6、控制上线-插件 一、基础知识 【内网安全-基础】基础知识、信息收集、工具https://blog.csdn.net/qq_53079406/article/details/128292587?spm1001.2014.3001.55…