当前位置: 首页 > news >正文

《Techporters架构搭建》-Day06 国际化

什么是国际化?

国际化,也叫i18n,为什么叫i18n呢?

"i18n"是国际化(internationalization)的缩写,数字18代表了国际化这个单词中间的字母数量。类似这样的缩写还有k8s(kubernetes)等

为什么使用国际化?

简单理解就是开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据,例如,为中国用户提供汉语界面显示,为美国用户提供提供英语界面显示。

国际化细分

国际化需要分成两个部分:
前端国际化

前端国际化主要关注页面的显示和用户界面的本地化。它涉及将应用程序的界面元素,如文本、标签、按钮等,根据用户的语言和地区进行翻译和适配。前端国际化通常使用资源文件、语言包或翻译服务来存储和管理不同语言的文本。前端开发人员可以通过使用国际化框架或库,如React Intl、Vue I18n或Angular i18n等,来实现前端国际化功能

后端国际化

后端国际化主要关注处理与业务逻辑和数据相关的国际化问题。这包括但不限于日期和时间格式、货币符号、数字格式、排序规则、接口提示信息等。后端国际化的目标是确保应用程序能够适应不同的语言和地区,并提供正确的本地化数据。后端国际化可以通过使用国际化库或框架,如SpringBoot I18n,来实现后端国际化功能

总之,前端国际化主要关注页面显示和用户界面的本地化,而后端国际化则处理与业务逻辑和数据相关的国际化问题。两者通常需要协同工作,以实现完整的国际化功能

国际化相关知识

Locale对象

需要支持国际化,得先知道选择的是哪种地区的哪种语言,java中使用java.util.Locale来表示地区语言,这个对象内部包含了国家和语言的信息。
Locale中最常用的构造方法:

public Locale(String language, String country) {this(language, country, "");
}

构造方法有两个参数:language:语言、country:国家

这两个参数的值不是乱写的,国际上有统一的标准,如:zh-CN表示中国大陆地区的中文,zh-TW表示中国台湾地区的中文,en-US表示美国地区的英文,en-GB表示英国地区的英文等等。通过语言和国家构造Locale对象,比如Locale locale = new Locale(“zh”, “CN”);,表示中国大陆地区的中文。

MessageSource接口

这是 Spring 国际化的核心接口,其定义如下:

public interface MessageSource {/*** 获取国际化信息*/@NullableString 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;
}

MessageSource接口提供了三个获取国际化消息的方法,其主要是根据 Locale 信息获取对应的国际化消息的集合,然后根据 code 获取对应的消息,并且通过提供的参数 args 还可以对获取后的消息进行格式化。
具体参数含义如下:

参数名含义
code表示国际化资源中的属性名
args为消息中的参数填充的值
defaultMessage默认的消息,如果没有找到将返回默认消息
resolvable消息参数,封装了 code、args、defaultMessage
locale表示本地化对象

类图结构
在这里插入图片描述
常见3个实现类:

类名含义
ResourceBundleMessageSource这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化文件
ReloadableResourceBundleMessageSource这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新国际化文件的信息
StaticMessageSource它允许通过编程的方式提供国际化信息。

重点:我们在项目中会创建 MessageSource接口,但不管使用哪个实现类或者我们自定义的类,都要将Bean名称设置为messageSource

加载ApplicationContext时,自动搜索上下文中定义的MessagesSource Bean(名称必须为messageSource)。
如果无法找到消息的任何源,则实例化一个空的messageSource。

LocaleResolver接口

这个接口是用来设置当前会话默认的国际化语言的,其定义如下:

public interface LocaleResolver {/*** 根据当前请求解析当前请求的本地化信息*/Locale resolveLocale(HttpServletRequest request);/*** 设置当前请求、响应的本地化信息*/void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}

resolveLocale方法用于从当前request中解析对应出对应的Locale对象,场景如:
常见4个实现类:

类名含义
AcceptHeaderLocalResovler通过请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8来决定使用具体的Locale实例,其实AcceptHeaderLocalResovler就是使用HttpServletRequest实例中的Locale实例,在进入DispatcherServlet的时候HttpServletRequest实例里面就已经有Locale实例了,可以通过request.getLocale();来获取Locale实例,HttpServletRequest里面的Locale实例就是使用请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8来构建的,比如请求头里面的Accept-Language:zh,en;q=0.9,zh-HK;q=0.8 就表示zh的权重是1,en;q=0.9表示en的权重是0.9,zh-HK;q=0.8就表示zh-HK的权重是0.8,那么我么通过request.getLocale();获取到的就是权重最高的zh,然后就是构建一个zh的Locale实例,那么AcceptHeaderLocalResovler在解析Locale的时候就会返回zh的Locale实例
CookieLocaleResovler根据用户在Cookie中设置的某参数来进行确定具体的本地化Locale实例
SessionLocaleResovler根据用户在HttpSession中设置某参数来进行确定具体的本地化Locale实例
FixedLocalResovler使用jdk自带的默认的Locale实例

国际化文件

项目中,在resources目录下创建名为i18n的文件目录,然后我们在i18n目录创建国际化文件
格式为:名称_语言_地区.properties
我们先来创建两种语言,如:
message.properties(这个文件名称没有指定Local信息,当系统找不到的时候会使用这个默认的)

name=您的姓名
text=默认文本

message_cn_ZH.properties:中文【中国】

name=姓名
text=文本

messages_en_US.properties 英文【美国】

name=name
text=text

我们通过MessageSource接口的getMessage方法传入对应的key(如name、text),便可以从国际化文件中取值。同时我们还可以指定Locale对象,便能找到对应的国际化文件然后取值。

国际化一般实现

一般让前端在请求头中, 添加 { “Accept-Language”: “zh” }来标识,用户使用的语言
然后我们添加拦截器, 将这个值取出来, 这一步springboot已经帮我们做了(默认配置)
所以一般的单体springboot项目中, 直接在配置一下国际化资源文件即可

@Configuration
public class I18nConfig implements WebMvcConfigurer {@Beanpublic MessageSource messageSource() {// 多语言文件地址Locale.setDefault(Locale.CHINA);ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();//设置国际化文件存储路径   resources目录下 可以设置多个messageSource.addBasenames("i18n/common/messages","i18n/system/messages","i18n/device/messages");//设置根据key如果没有获取到对应的文本信息,则返回key作为信息messageSource.setUseCodeAsDefaultMessage(true);//设置字符编码messageSource.setDefaultEncoding(StandardCharsets.UTF_8.toString());return messageSource;}
}

然后在对应的目录文件(/i18n/common/)下定义国际化资源文件
美式英语 messages_en_US.properties

user.login.username=User name
user.login.password=Password
user.login.code=Security code
user.login.remember=Remember me
user.login.submit=Sign In

中文简体 messages_zh_CN.properties

user.login.username=用户名
user.login.password=密码
user.login.code=验证码
user.login.remember=记住我
user.login.submit=登录

MessageUtils工具类

public class MessageUtils
{/*** 根据消息键和参数 获取消息 委托给spring messageSource** @param code 消息键* @param args 参数* @return 获取国际化翻译值*/public static String message(String code, Object... args){MessageSource messageSource = SpringUtils.getBean(MessageSource.class);return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());}
}

然后直接这样使用就行

MessageUtils.message("user.login.username")
MessageUtils.message("user.login.password")

国际化改进版

下面看网上的两种国际化
通过数据库实现国际化
基于若依-cloud的国际化方案

框架中国际化

目前我们国际化应用场景1.接口抛出的异常国际化 2.参数校验实现国际化
① 因为每个服务都有国际化配置文件,所以关于加载国际化配置的配置类我们放在common包中,首选创建加载MessageSource实现类的I18nConfig 配置类,这个配置类有以下作用:

  1. 实例化ReloadableResourceBundleMessageSource
  2. MessageSourceAccessor是对MessageSource的封装,提供了更便捷的方法来获取消息,免去我们封装MessageUtils类。
package com.tps.cloud.config;import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.context.annotation.Bean;@AutoConfiguration
public class I18nConfig {private static final String BASE_NAME = "classpath:i18n/messages";@Beanpublic MessageSource messageSource() {ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource();reloadableResourceBundleMessageSource.setBasenames(BASE_NAME);reloadableResourceBundleMessageSource.setCacheSeconds(5);reloadableResourceBundleMessageSource.setDefaultEncoding("UTF-8");return  reloadableResourceBundleMessageSource;}@Beanpublic MessageSourceAccessor messageSourceAccessor(MessageSource messageSource) {MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource) ;return accessor ;}}

ValidatorConfig配置类,此类作用:

  • 注入MessageSource,用于国际化配置。
  • 实例化工厂LocalValidatorFactoryBean,设置:
    • 设置国际化:将MessageSource设置到ValidationMessageSource
    • 设置提供者类(校验器):HibernateValidator
    • 设置属性:实例化Properties,配置Hibernate的快速异常返回,hibernate.validator.fail_fast,加入到工厂配置。(快速返回指的是遇到一个不合法的,就不继续往下校验。)
  • 加载配置:调用factoryBean的afterPropertiesSet
  • 返回工厂方法的Validator

这里可以顺便谈谈关于Spring的FactoryBean:

  • FactoryBean是接口,实现该接口的类可以自定义创建Bean。一般在框架中用来创建复杂的Bean。
  • 这里的FactoryBean,实现InitializingBean接口,在afterPropertiesSet方法中创建bean,会在bean 实例化后调用。
  • FactoryBean让Bean构建过程更灵活,可以理解为一种策略模式,我们需要生成什么样的
    bean,可以通过实现接口来自定义。
package com.tps.cloud.config;import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;import java.util.Properties;@AutoConfiguration
public class ValidatorConfig {@Autowiredprivate MessageSource messageSource;/*** 配置校验框架 快速返回模式*/@Beanpublic Validator validator() {LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();// 国际化factoryBean.setValidationMessageSource(messageSource);// 设置使用 HibernateValidator 校验器factoryBean.setProviderClass(HibernateValidator.class);Properties properties = new Properties();// 设置 快速异常返回properties.setProperty("hibernate.validator.fail_fast", "true");factoryBean.setValidationProperties(properties);// 加载配置factoryBean.afterPropertiesSet();return factoryBean.getValidator();}
}

③ 在对应的服务下创建国际化资源文件
在这里插入图片描述
④ 在接口入参对象上添加占位符,然后测试保存用户接口

    @NotBlank(message = "{SystemUserDto.Username.NotEmpty}")@UniqueUsernameprivate String username;
@PostMappingpublic Result<Long> save(@RequestBody @Validated SystemUserDto systemUserDto) {Long id=systemUserService.createUser(systemUserDto);return Result.ok(id);}

⑤ 创建异常处理器,用于捕获参数验证异常,并返回统一数据结构

package com.tps.cloud.handler;import com.tps.cloud.response.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.List;/*** 全局异常处理器MethodArgumentNotValidException*/
@RestControllerAdvice
public class GlobalExceptionHandler {/*** validation Exception (以form-data形式传参)* @param exception* @return Result*/@ExceptionHandler({ BindException.class })@ResponseStatus(HttpStatus.BAD_REQUEST)public Result bindExceptionHandler(BindException exception) {List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();return Result.failed(fieldErrors.get(0).getDefaultMessage());}/*** validation Exception* @param exception* @return Result*/@ExceptionHandler({ MethodArgumentNotValidException.class })public Result handleBodyValidException(MethodArgumentNotValidException exception) {FieldError fieldError = exception.getBindingResult().getFieldError();return Result.failed(String.format("%s", fieldError.getDefaultMessage()));}
}

⑥通过Apifox测试,通过Header传递国际化参数Accept-Language参数(en-US,zh-CN),模拟参数username为空状态
在这里插入图片描述
在这里插入图片描述
⑦ 现在模拟异常抛出国际化,首先在保存接口添加用户账号唯一校验(记得去除掉上节添加的@UniqueUsername注解,防止被影响)

  private final MessageSourceAccessor messages;@PostMappingpublic Result<Long> save(@RequestBody @Validated SystemUserDto systemUserDto) {//验证用户唯一SystemUserVo systemUserVo=systemUserService.findByUsername(systemUserDto.getUsername());if(systemUserVo!=null){return Result.failed(messages.getMessage("SystemUserDto.Username.Exist"));}Long id=systemUserService.createUser(systemUserDto);return Result.ok(id);}

国际化配置文件添加SystemUserDto.Username.Exist:
在这里插入图片描述
通过Apifox测试,通过Header传递国际化参数Accept-Language参数(en-US,zh-CN),模拟参数username重复存在的情况。
在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 怎样在 SQL 中对一个包含销售数据的表按照销售额进行降序排序?
  • Ubuntu安装Anaconda3
  • Bug定义及生命周期(七)
  • Java中Maven打包方式pom、jar、war的区别
  • 对象切片(Object Slicing)
  • Data Harmonizer(数据协调器)------线程
  • 拥有一个公网固定IP,既然如此简单、HTTP 虚拟专线:为您开启专属网络访问新时代
  • 【STM32 FreeRTOS】事件标志组
  • C语言-使用数组法,指针法实现将一个5X5的矩阵中最大的元素放在中心,四个角分别放四个最小的元素(顺序为从左到右,从上到下,从小到大存放),写一函数实现之。
  • Java垃圾收集器工作原理
  • Docker三剑客之Docker Engine
  • 深入理解 Kibana 配置文件:一份详尽的指南
  • MySQL表的增删改查(基础)
  • Leetcode 70.爬楼梯
  • 使用 Python 解密加密的 PDF 文件
  • [NodeJS] 关于Buffer
  • 【RocksDB】TransactionDB源码分析
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • ECMAScript入门(七)--Module语法
  • JavaScript 奇技淫巧
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • Redash本地开发环境搭建
  • vue的全局变量和全局拦截请求器
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 大快搜索数据爬虫技术实例安装教学篇
  • 分布式事物理论与实践
  • 前端技术周刊 2019-02-11 Serverless
  • 一些css基础学习笔记
  • 用mpvue开发微信小程序
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • ![CDATA[ ]] 是什么东东
  • #define与typedef区别
  • #微信小程序:微信小程序常见的配置传旨
  • (Java企业 / 公司项目)点赞业务系统设计-批量查询点赞状态(二)
  • (阿里云万网)-域名注册购买实名流程
  • (数据大屏)(Hadoop)基于SSM框架的学院校友管理系统的设计与实现+文档
  • (五十)第 7 章 图(有向图的十字链表存储)
  • (一)基于IDEA的JAVA基础12
  • (转)Linq学习笔记
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .Net Core 中间件验签
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作
  • .net SqlSugarHelper
  • .net 反编译_.net反编译的相关问题
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .net6+aspose.words导出word并转pdf
  • .net6使用Sejil可视化日志
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • .NET开发者必备的11款免费工具
  • .net通过类组装数据转换为json并且传递给对方接口
  • .py文件应该怎样打开?
  • @synthesize和@dynamic分别有什么作用?