多语言系统化设计与实现原理

内测之家       
一款功能强大且全面的应用内测与管理平台、分发平台,专为 iOS 和 Android 开发者打造,旨在为用户提供便捷高效、安全可靠的一站式服务。无论是从资源安全到传输安全,还是从数据保护到应用管理、统计分析,内测之家都展现出卓越的能力与优势。

一、核心需求与实现目标

在多语言(i18n)设计中,需满足以下需求:。

  1. 动态适配:根据用户语言环境返回对应语言的文本。

  2. 灵活性:支持静态文本(如按钮标签)和动态内容(如商品名称)。

  3. 易维护性:语言资源可扩展、可集中管理。

  4. 性能优化:减少实时解析开销,提升响应速度。


二、三种主流实现方案对比
方案JSON字段存储字典表管理Resource Bundle
适用场景动态内容(用户生成数据、商品名)系统级配置(国家列表、状态码)静态文本(按钮标签、错误提示)
存储形式数据库字段存储JSON格式数据库表存储键值对本地文件(.properties)
优点- 灵活更新
- 支持复杂结构
- 集中管理
- 易于扩展新语言
- 加载速度快
- 语言分层清晰
缺点- 解析开销大
- 结构复杂
- 依赖数据库查询
- 性能瓶颈
- 无法动态修改
- 需重启生效
示例{"zh-CN":"商品名","en-US":"Name"}sys_i18n 表存储键值对messages_zh_CN.properties

三、JSON字段存储方案
1. 实现原理
  • 数据存储:在数据库字段中存储多语言内容的JSON结构。

  • 动态解析:根据用户语言标识提取对应值。

2. 使用场景
  • 商品多语言名称、描述。

  • 用户生成内容(如评论、动态)。

3. 代码示例(Java + Spring)
public class I18nJsonHandler implements I18nHandler {private static final String MODE = "json";@Overridepublic String fetchMode() {return MODE;}@Overridepublic String process(String fieldValue, Locale locale, I18n i18n) {Map<String, String> translations = JsonUtils.jsonToMap(fieldValue, String.class);if (translations == null || translations.size() == 0) {return null;}final String lang = locale.toLanguageTag();String value = translations.get(lang);if (StringUtils.isBlank(value)) {final String[] langItems = lang.split("-");if (langItems.length >= 2) {value = translations.get(langItems[0]);}}return value;}
}
4. 优缺点
  • 优点:适合频繁更新的动态内容,支持嵌套结构。

  • 缺点:需手动解析JSON,性能较低。

四、字典表管理方案
1. 实现原理
  • 数据库设计:创建字典表(如 sys_i18n),存储多语言键值对。

  • 键值映射:通过 模块名 + 键名 + 语言 唯一标识一条记录。

2. 使用场景
  • 国家/地区列表。

  • 系统状态码多语言描述。

3. 数据库表结构
字段说明
module模块名(如 "country")
key键名(如 "CN")
lang语言标识(如 "zh-CN")
value对应语言的值(如 "中国")
4. 代码示例
-- 查询中国的多语言名称
SELECT value FROM sys_i18n 
WHERE module = 'country' AND key = 'CN' AND lang = 'en-US';
-- 返回 "China"
5. 优缺点
  • 优点:集中管理,适合全局配置。

  • 缺点:高频查询时需优化缓存机制。

五、Resource Bundle 方案
1. 实现原理
  • 文件化管理:按语言拆分 .properties 文件,键值对存储静态文本。

  • 分层加载:根据 Locale 自动匹配最接近的资源文件。

2. 使用场景
  • 界面标签(如按钮、菜单)。

  • 预定义错误提示(如“参数无效”)。

3. 文件示例
login.mode.not.support=登录方式不支持
account.format.invalid=账号格式无效
password.not.match.reg=密码规则不匹配
password.error=密码错误
password.not.set.yet=密码未设置
username.or.password.error=账号或密码错误
captcha.error=验证码错误
verify.code.error=验证码错误
oauth.need.bind.account=授权需要绑定账号
oauth.login.error=授权登录失败
no.authority.access.app=无权访问
user.locked=账号已锁定

4. 技术实现(Java + Spring)

public class I18nMessages extends ResourceBundleMessageSource {private static final I18nMessages INSTANCE = new I18nMessages();public static I18nMessages get() {return INSTANCE;}public String getMessage(String key, Object... arguments) {return super.getMessage(key, arguments, key, LocaleContextHolder.getLocale());}
}
public class I18nCodeHandler implements I18nHandler{private static final String[] emptyArray = new String[0];private static final String MODE = "code";private final I18nMessages i18nMessages;public I18nCodeHandler(I18nMessages i18nMessages) {this.i18nMessages = i18nMessages;}@Overridepublic String fetchMode() {return MODE;}@Overridepublic String process(String fieldValue, Locale locale, I18n i18n) {return i18nMessages.getMessage(fieldValue, emptyArray, fieldValue, locale);}
}
5. 优缺点
  • 优点:性能高,语言分层清晰。

  • 缺点:修改需更新文件并重启服务。

六、具体切面处理逻辑
 1.获取客户端的【Accept-Language】的值写入多语言上下文中
public class I18nInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestMethod = request.getMethod();if (requestMethod.equalsIgnoreCase(HttpMethod.OPTIONS.name())) {return true;}if (handler instanceof HandlerMethod) {final Locale locale = I18nUtils.getLocale();//放在线程局部变量LocaleContextHolder.setLocale(locale);}return  true;}}
 2.Body切面处理逻辑 
package com.forsoo.common.web.i18n.advice;import com.forsoo.common.domain.Result;
import com.forsoo.common.util.JsonUtils;
import com.forsoo.common.util.StringUtils;
import com.forsoo.common.web.constant.Constant;
import com.forsoo.common.web.i18n.I18nMessages;
import com.forsoo.common.web.i18n.annotations.I18n;
import com.forsoo.common.web.i18n.handler.I18nHandler;
import com.forsoo.common.web.i18n.handler.I18nHandlerManager;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;/*** @author kingchange* @title I18nAdvice* @date 2024/5/11 11:02*/
@Order(Constant.AdviceOrder.I18n)
@ControllerAdvice
public class I18nAdvice implements ResponseBodyAdvice<Result> {private static final String[] emptyArray = new String[0];@Resourceprivate I18nMessages i18nMessages;@Autowiredprivate I18nHandlerManager i18nHandlerManager;@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic Result beforeBodyWrite(Result body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {String renderMsg = null;final Locale locale = LocaleContextHolder.getLocale();//有国际化配置的才会替换if (locale != null) {// 处理 message 的多语言if (StringUtils.isNotBlank(body.getMessage())) {renderMsg = i18nMessages.getMessage(body.getMessage(), body.getArgs() == null ? emptyArray : body.getArgs(), null, locale);}// 处理 data 的多语言processI18nInData(body.getData(), locale);}// 原信息返回if (StringUtils.isBlank(renderMsg)){renderMsg = body.getMessage();}// 有额外参数,则格式化if (body.getArgs() != null && body.getArgs().length > 0 && StringUtils.isNotBlank(renderMsg)){renderMsg = MessageFormat.format(body.getMessage(), body.getArgs());}body.setMessage(renderMsg);body.setArgs(null);return body;}private Object processI18nInData(Object data, Locale locale) {if (data == null) {return data;}if (data instanceof List lst) {return lst.stream().map((element) -> {return this.processI18nInData(element, locale);}).collect(Collectors.toList());} else if (data instanceof Set set) {return set.stream().map((element) -> {return this.processI18nInData(element, locale);}).collect(Collectors.toSet());} else if (data instanceof Map ) {Map<?, ?> map = (Map)data;return map.entrySet().stream().collect(Collectors.toMap((entry) -> {return entry.getKey();}, (entry) -> {return this.processI18nInData(entry.getValue(), locale);}));}return translateObjectFields(data, locale);}private Object translateObjectFields(Object object, Locale locale) {if (object == null) {return object;}if (!object.getClass().isAnnotationPresent(I18n.class)) {return object;}Class<?> clazz = object.getClass();while (clazz != null) {for (Field field : clazz.getDeclaredFields()) {if (!field.isAnnotationPresent(I18n.class)) {continue;}// 设置私有属性可访问field.setAccessible(true);try {Object fieldValue = field.get(object);if (fieldValue instanceof String strValue) {// 对String类型字段进行国际化处理final I18n i18n = field.getAnnotation(I18n.class);String i18nValue = processI18n(strValue, locale, i18n);field.set(object, i18nValue);} else {// 如果字段是对象类型,则递归处理processI18nInData(fieldValue, locale);}// 可以根据需要处理其他类型或进行递归翻译} catch (IllegalAccessException e) {e.printStackTrace();}}// 移动到父类clazz = clazz.getSuperclass();}return object;}private String processI18n(String fieldValue, Locale locale, I18n i18n){if (StringUtils.isBlank(fieldValue)){return fieldValue;}final I18nHandler i18nHandler = i18nHandlerManager.getHandler(i18n.value());if (i18nHandler == null){return fieldValue;}return i18nHandler.process(fieldValue, locale, i18n);}
}
七、选型建议与最佳实践
1. 选型依据
项目规模推荐方案
小型项目Resource Bundle(简单高效)
中大型项目混合架构(静态文本 + 动态内容)
全球化平台字典表 + 分布式缓存(如Redis)
2. 最佳实践
  • 前后端协作

    • 前端传递 Accept-Language 头部。

    • 后端统一处理多语言逻辑。

  • 缓存优化

    • 使用内存缓存(如Caffeine)加速Resource Bundle加载。

    • 对字典表查询结果进行二级缓存(如Redis + LocalCache)。

  • 热更新支持

    • 使用 ReloadableResourceBundleMessageSource 支持文件热加载。

    • 通过消息队列通知服务更新字典表缓存。


八、总结

多语言支持需根据实际场景灵活选择方案:

  • JSON字段:适合动态、结构化的用户生成内容。

  • 字典表:适合全局配置和系统级数据。

  • Resource Bundle:适合静态文本和高性能需求场景。

通过混合架构整合三者优势,可构建高效、灵活、易维护的国际化系统。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4个 Vue 路由实现的过程

大家好&#xff0c;我是大澈&#xff01;一个喜欢结交朋友、喜欢编程技术和科技前沿的老程序员&#x1f468;&#x1f3fb;‍&#x1f4bb;&#xff0c;关注我&#xff0c;科技未来或许我能帮到你&#xff01; Vue 路由相信朋友们用的都很熟了&#xff0c;但是你知道 Vue 路由…

es-索引详解

在 Elasticsearch 中&#xff0c;**索引&#xff08;Index&#xff09;**是核心概念之一&#xff0c;类似于关系型数据库中的“表”。索引用于存储、组织和检索文档&#xff08;Document&#xff09;。以下是关于 Elasticsearch 索引的详细解析&#xff1a; 1. 索引的基本概念 …

C#实现本地Deepseek模型及其他模型的对话v1.4

前言 系 统&#xff1a;Window11 开发工具&#xff1a;Visual Studio 2022 相关技术&#xff1a;C# 、WPF .Net 8.0 1、C#实现本地AI聊天功能 WPFOllamaSharpe实现本地聊天功能,可以选择使用Deepseek 及其他模型。 新增根据聊天记录回复的功能。 优化了部分ViewModel&#xff…

若依框架-给sys_user表添加新字段并获取当前登录用户的该字段值

目录 添加字段 修改SysUser类 修改SysUserMapper.xml 修改user.js 前端获取字段值 添加字段 若依框架的sys_user表是没有age字段的&#xff0c;但由于业务需求&#xff0c;我需要新添加一个age字段&#xff1a; 修改SysUser类 添加age字段后&#xff0c;要在SysUser类 …

用Qt手搓AI助手,挑战24小时开发DeepSeek Assistant!

一、项目需求分析与技术选型 DeepSeekAssistant是一款基于深度求索&#xff08;DeepSeek&#xff09;API的智能对话助手&#xff0c;核心需求包括&#xff1a; 用户界面友好&#xff1a;支持多轮对话展示数据持久化&#xff1a;历史记录存储与检索异步网络通信&#xff1a;AP…

linux 软件扩展GPU显存

概述 共享内存可以通过 Unified Memory&#xff08;统一内存&#xff09;来实现&#xff0c;它允许 CPU 和 GPU 共享相同的内存地址空间&#xff0c;从而方便数据的传输和访问。 利用该技术可解决家用GPU 机器学习时显存不足的问题 &#xff08;注&#xff1a; 虽然解决了爆显…

Linux——进程初步

学进程前我们需要知道什么&#xff1f; 一、冯诺依曼体系结构 图中就是我们电脑运作时的大致工作流程&#xff0c;其中输入设备、输出设备我们也叫外设。其中&#xff0c;输入设备有比如键盘、鼠标、磁盘、摄像头等。输出设备有显示器、磁盘、打印机等。图中的存储器我们也叫内…

LeetCode-122. 买卖股票的最佳时机 II

其实这题画个折线图就很清晰了&#xff0c;因为我们每天都可以买卖股票&#xff0c;所有我们就只计算上升趋势的股票收益就好了&#xff0c;最小刻度为1&#xff0c;进行差值计算&#xff0c;取总和。 var maxProfit function(prices){let sum0;for(let i1;i<prices.leng…

关于前后端整合和打包成exe文件的个人的总结和思考

前言 感觉有很多东西&#xff0c;不知道写什么&#xff0c;随便写点吧。 正文 前后端合并 就不说怎么开发的&#xff0c;就说点个人感觉重要的东西。 前端用ReactViteaxios随便写一个demo&#xff0c;用于CRUD。 后端用Django REST Framework。 设置前端打包 import { …

Vue | 开学第一课!零基础教程

目录 背景介绍 安装方式 下载环境软件 NodeJS手册 如何查看node版本 镜像源 完整流程 创建根文件夹并拖进 VSCode 调用控制台 安装 vite 脚手架 配置项目 安装依赖 启动项目 查看页面 问题 创建项目失败 解决方法 权限问题 解决方法 其他问题 背景介绍 今…

泛微ecode的页面开发发送请求参数携带集合

1.在开发过程中我们难免遇见会存在需要将集合传递到后端的情况&#xff0c;那么这里就有一些如下的注意事项&#xff0c;如以下代码&#xff1a; // 新增action.boundasync addQuestion(formData) {var theList this.questionAnswerList;var questionAnswerListArray new Ar…

Tomato靶机攻略

将tomato改为NAT模式 扫描ip arp-scan -l 扫描端口&#xff0c;发现ssh服务端口从22改为2211 扫描目录 发现http://192.168.31.134/antibot_image/ 访问 查看所有php文件的源码&#xff0c;看看有什么不同的地方 在info.php的源码中发现问题 在输入后&#xff0c;成功显示…

EasyRTC嵌入式音视频通话SDK:基于纯C语言的跨平台实时通信系统设计与实践

随着物联网、移动互联网的快速发展&#xff0c;实时音视频通信技术在智能硬件、远程协作、工业控制等领域广泛应用。然而&#xff0c;跨平台兼容性差、资源占用高、定制化难等问题&#xff0c;仍是传统RTC方案的痛点。 EasyRTC嵌入式音视频通话SDK凭借纯C语言设计与全平台覆盖…

HCIP复习拓扑练习(修改版)

拓扑&#xff1a; 实际&#xff1a; 需求&#xff1a; 需求分析 1.这意味着学校内部网络能够正常解析域名并进行互联网访问。 2. PC1和PC2处于同一个内网192.168.1.0/24&#xff0c;其中PC1有权限访问外部网段3.3.3.0/24&#xff0c;而PC2没有。这涉及ACL&#xff08;访问控制…

vue-next-admin修改配置指南

目录 1.如何开启侧边栏logo 2.修改侧边栏顶部的logo与文字 3.修改侧边栏路由logo 4.浏览器标题栏图标与文字修改 5.修改侧边栏的背景颜色、顶部导航栏背景颜色、字体颜色、激活时颜色等 6.去除或添加修改右上方放大、信息、头像昵称&#xff08;登录获取之后存储进行修改图…

ruoyi-cloud-plus编译记录-1

dockerfile部署jar 添加一个run configuration ‘ruoyi-nacos’ run configuration 参考Docker - 在IntelliJ IDEA中一键部署项目 - hucat - 博客园 jar包编译不成功&#xff0c;没有jar&#xff0c;docker部署nacos就没法进行下去 参考链接 maven 构建报错 This failure was…

【算法day8】整数反转

整数反转 https://leetcode.cn/problems/reverse-integer/description/ class Solution { public:int reverse(int x) {int MAX_LENGTH 11; // 32位整数的最大数字的位数int* num (int*)calloc(sizeof(int), MAX_LENGTH); //用于保存进位每一位的数字int current x;int pos…

MySQL库和表的操作详解:从创建库到表的管理全面指南

目录 一、MySQL库的操作详解 〇、登录MySQL 一、数据库的创建与字符集设置 1. 创建数据库的语法 2. 创建数据库示例 查看创建出来的文件: bash下查看MySQL创建的文件 二、字符集与校验规则 1. 查看系统默认设置 2. 查看支持的字符集与校验规则 3. 校验规则对查询的影响…

Linux中的基本指令(上)

目录 ls指令 判断linux中文件 pwd指令 认识路径 ​编辑 绝对路径/相对路径 cd指令 简要理解用户 理解家目录 echo指令和printf指令 touch指令 mkdir指令 cat指令 tree指令 rmdir指令和rm指令 man指令 cp指令 which指令 alias 指令 date指令 cal指令 理解…

WPF 与 GMap.NET 结合实现雷达目标动态显示与地图绘制

概述 雷达上位机是雷达系统中用于数据可视化、分析和控制的核心软件。本文将介绍如何使用 C# 和 WPF 框架开发一个雷达上位机程序&#xff0c;主要功能包括&#xff1a; 显示目标轨迹&#xff1a;在界面上实时绘制雷达探测到的目标轨迹。点击显示详细信息&#xff1a;用户点击…