国际化配置
概述
ruoyi-admin 提供了完善的国际化(i18n)支持,实现了系统消息的多语言切换:
- 多语言支持 - 内置中文和英文,支持扩展其他语言
- 消息分类管理 - 按功能模块组织消息Key
- 参数化消息 - 支持动态参数插值
- 自动切换 - 根据请求自动选择语言
- 默认回退 - 未翻译消息自动回退到默认语言
文件结构
资源文件位置
src/main/resources/i18n/
├── messages.properties # 默认消息(中文)
├── messages_zh_CN.properties # 中文消息
└── messages_en_US.properties # 英文消息文件命名规范
| 文件名 | 说明 |
|---|---|
messages.properties | 默认语言,当找不到对应语言时使用 |
messages_zh_CN.properties | 简体中文 |
messages_en_US.properties | 美式英语 |
messages_zh_TW.properties | 繁体中文(可扩展) |
messages_ja_JP.properties | 日语(可扩展) |
命名格式:messages_{语言代码}_{国家代码}.properties
应用配置
Spring配置
yaml
spring:
messages:
# 消息文件基础名
basename: i18n/messages
# 消息编码
encoding: UTF-8配置说明
| 配置项 | 说明 | 默认值 |
|---|---|---|
| basename | 消息文件基础路径 | i18n/messages |
| encoding | 文件编码格式 | UTF-8 |
| cache-duration | 消息缓存时间 | - |
| fallback-to-system-locale | 是否回退到系统语言 | true |
消息分类
系统消息按功能模块进行分类管理:
通用验证消息(common.*)
properties
# 通用验证消息
common.required=* 必须填写
common.length.invalid=长度必须在{min}到{max}个字符之间
common.id.required=主键ID不能为空| Key | 中文 | English |
|---|---|---|
| common.required | * 必须填写 | * Required |
| common.length.invalid | 长度必须在{min}到{max}个字符之间 | Length must be between {min} and {max} characters |
| common.id.required | 主键ID不能为空 | Primary key ID cannot be empty |
操作结果消息(operation.*)
properties
# 通用操作结果消息
operation.success=操作成功
operation.fail=操作失败
operation.query.success=查询成功
operation.add.success=新增成功
operation.update.success=修改成功
operation.delete.success=删除成功
operation.import.success=导入成功
operation.export.success=导出成功| Key | 中文 | English |
|---|---|---|
| operation.success | 操作成功 | Operation successful |
| operation.fail | 操作失败 | Operation failed |
| operation.query.success | 查询成功 | Query successful |
| operation.add.success | 新增成功 | Create successful |
| operation.update.success | 修改成功 | Update successful |
| operation.delete.success | 删除成功 | Delete successful |
| operation.import.success | 导入成功 | Import successful |
| operation.export.success | 导出成功 | Export successful |
用户认证消息(user.*)
账号状态
properties
user.account.not.exists=对不起, 您的账号:{0} 不存在
user.password.mismatch=用户不存在/密码错误
user.account.disabled=对不起,您的账号:{0} 已禁用,请联系管理员| Key | 中文 | English |
|---|---|---|
| user.account.not.exists | 对不起, 您的账号:{0} 不存在 | Sorry, your account: {0} does not exist |
| user.password.mismatch | 用户不存在/密码错误 | User does not exist or password is incorrect |
| user.account.disabled | 对不起,您的账号:{0} 已禁用,请联系管理员 | Sorry, your account: {0} has been disabled. Please contact the administrator |
登录注册
properties
user.login.success=登录成功
user.logout.success=退出成功
user.register.success=注册成功
user.register.account.exists=保存用户 {0} 失败,注册账号已存在
user.register.failed=注册失败,请联系系统管理人员| Key | 中文 | English |
|---|---|---|
| user.login.success | 登录成功 | Login successful |
| user.logout.success | 退出成功 | Logout successful |
| user.register.success | 注册成功 | Registration successful |
| user.register.account.exists | 保存用户 {0} 失败,注册账号已存在 | Failed to save user {0}, the account already exists |
| user.register.failed | 注册失败,请联系系统管理人员 | Registration failed, please contact system administrator |
会话状态
properties
user.session.expired=会话已过期,请重新登录
user.admin.force.logout=管理员强制退出,请重新登录
user.system.error.relogin=系统异常,请重新登录| Key | 中文 | English |
|---|---|---|
| user.session.expired | 会话已过期,请重新登录 | Session has expired, please login again |
| user.admin.force.logout | 管理员强制退出,请重新登录 | Administrator forced logout, please login again |
| user.system.error.relogin | 系统异常,请重新登录 | System error, please login again |
密码重试限制
properties
user.password.retry.count=密码输入错误{0}次
user.password.retry.locked=密码输入错误{0}次,帐户锁定{1}分钟| Key | 中文 | English |
|---|---|---|
| user.password.retry.count | 密码输入错误{0}次 | Password input error {0} times |
| user.password.retry.locked | 密码输入错误{0}次,帐户锁定{1}分钟 | Password input error {0} times, account locked for {1} minutes |
用户信息验证
properties
user.username.required=用户名不能为空
user.username.format.invalid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
user.username.length.invalid=账户长度必须在{min}到{max}个字符之间
user.password.required=用户密码不能为空
user.password.length.invalid=用户密码长度必须在{min}到{max}个字符之间
user.password.format.invalid=* 密码格式不正确,5-50个字符
user.password.complexity.invalid=密码必须同时包含字母和数字
user.email.format.invalid=邮箱格式错误
user.email.required=邮箱不能为空
user.phone.required=用户手机号不能为空
user.phone.format.invalid=手机号格式错误认证方式消息(auth.*)
properties
auth.type.unsupported=认证方式不支持
auth.type.disabled=认证方式已禁用
auth.type.required=认证方式不能为空| Key | 中文 | English |
|---|---|---|
| auth.type.unsupported | 认证方式不支持 | Authentication type is not supported |
| auth.type.disabled | 认证方式已禁用 | Authentication type is disabled |
| auth.type.required | 认证方式不能为空 | Authentication type cannot be blank |
验证码消息(verify.code.*)
图形验证码
properties
verify.code.captcha.required=图形验证码不能为空
verify.code.captcha.invalid=图形验证码错误
verify.code.captcha.expired=图形验证码已失效| Key | 中文 | English |
|---|---|---|
| verify.code.captcha.required | 图形验证码不能为空 | Captcha cannot be blank |
| verify.code.captcha.invalid | 图形验证码错误 | Captcha is invalid |
| verify.code.captcha.expired | 图形验证码已失效 | Captcha has expired |
短信验证码
properties
verify.code.sms.required=短信验证码不能为空
verify.code.sms.retry.count=短信验证码输入错误{0}次
verify.code.sms.retry.locked=短信验证码输入错误{0}次,帐户锁定{1}分钟
verify.code.sms.invalid=短信验证码错误
verify.code.sms.expired=短信验证码已过期| Key | 中文 | English |
|---|---|---|
| verify.code.sms.required | 短信验证码不能为空 | SMS verification code cannot be blank |
| verify.code.sms.invalid | 短信验证码错误 | SMS verification code is invalid |
| verify.code.sms.expired | 短信验证码已过期 | SMS verification code has expired |
邮箱验证码
properties
verify.code.email.required=邮箱验证码不能为空
verify.code.email.retry.count=邮箱验证码输入错误{0}次
verify.code.email.retry.locked=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
verify.code.email.invalid=邮箱验证码错误
verify.code.email.expired=邮箱验证码已过期| Key | 中文 | English |
|---|---|---|
| verify.code.email.required | 邮箱验证码不能为空 | Email verification code cannot be blank |
| verify.code.email.invalid | 邮箱验证码错误 | Email verification code is invalid |
| verify.code.email.expired | 邮箱验证码已过期 | Email verification code has expired |
第三方登录消息(social.*)
properties
social.login.source.required=第三方登录平台[source]不能为空
social.login.auth.code.required=第三方登录平台[code]不能为空
social.login.state.required=第三方登录平台[state]不能为空| Key | 中文 | English |
|---|---|---|
| social.login.source.required | 第三方登录平台[source]不能为空 | Social login platform [source] cannot be blank |
| social.login.auth.code.required | 第三方登录平台[code]不能为空 | Social login platform [code] cannot be blank |
| social.login.state.required | 第三方登录平台[state]不能为空 | Social login platform [state] cannot be blank |
文件上传消息(file.*)
properties
file.upload.size.exceed.limit=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
file.upload.filename.too.long=上传的文件名最长{0}个字符
file.upload.type.not.supported=文件类型不支持
file.upload.failed=文件上传失败| Key | 中文 | English |
|---|---|---|
| file.upload.size.exceed.limit | 上传的文件大小超出限制!允许最大:{0}MB | The uploaded file size exceeds the limit! Maximum: {0}MB |
| file.upload.filename.too.long | 上传的文件名最长{0}个字符 | Maximum filename length is {0} characters |
| file.upload.type.not.supported | 文件类型不支持 | File type is not supported |
| file.upload.failed | 文件上传失败 | File upload failed |
权限控制消息(permission.*)
properties
permission.no.access=没有访问权限,请联系管理员添加权限 {0} [{1}]
permission.no.create=您没有创建数据的权限,请联系管理员添加权限 [{0}]
permission.no.update=您没有修改数据的权限,请联系管理员添加权限 [{0}]
permission.no.delete=您没有删除数据的权限,请联系管理员添加权限 [{0}]
permission.no.export=您没有导出数据的权限,请联系管理员添加权限 [{0}]
permission.no.view=您没有查看数据的权限,请联系管理员添加权限 [{0}]| Key | 中文 | English |
|---|---|---|
| permission.no.access | 没有访问权限,请联系管理员添加权限 | Access denied, please contact administrator |
| permission.no.create | 您没有创建数据的权限 | You do not have permission to create data |
| permission.no.update | 您没有修改数据的权限 | You do not have permission to update data |
| permission.no.delete | 您没有删除数据的权限 | You do not have permission to delete data |
| permission.no.export | 您没有导出数据的权限 | You do not have permission to export data |
| permission.no.view | 您没有查看数据的权限 | You do not have permission to view data |
请求控制消息(request.*)
properties
request.control.duplicate.submit=不允许重复提交,请稍候再试
request.control.rate.limit.exceeded=访问过于频繁,请稍候再试| Key | 中文 | English |
|---|---|---|
| request.control.duplicate.submit | 不允许重复提交,请稍候再试 | Duplicate submission is not allowed, please try again later |
| request.control.rate.limit.exceeded | 访问过于频繁,请稍候再试 | Too many requests, please try again later |
租户管理消息(tenant.*)
properties
tenant.id.required=租户ID不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
tenant.disabled=对不起,您的租户已禁用,请联系管理员
tenant.expired=对不起,您的租户已过期,请联系管理员
tenant.mode.not.enabled=当前未开启租户模式
tenant.sync.package.success=同步租户套餐成功
tenant.sync.roles.success=同步租户角色成功
tenant.sync.dicts.success=同步租户字典成功| Key | 中文 | English |
|---|---|---|
| tenant.id.required | 租户ID不能为空 | Tenant ID cannot be blank |
| tenant.not.exists | 对不起,您的租户不存在 | Sorry, your tenant does not exist |
| tenant.disabled | 对不起,您的租户已禁用 | Sorry, your tenant is disabled |
| tenant.expired | 对不起,您的租户已过期 | Sorry, your tenant has expired |
| tenant.mode.not.enabled | 当前未开启租户模式 | Tenant mode is not enabled |
| tenant.sync.package.success | 同步租户套餐成功 | Tenant package synchronized successfully |
| tenant.sync.roles.success | 同步租户角色成功 | Tenant roles synchronized successfully |
| tenant.sync.dicts.success | 同步租户字典成功 | Tenant dictionaries synchronized successfully |
消息Key命名规范
命名结构
{模块}.{子模块}.{功能}.{状态}命名示例
| Key | 模块 | 子模块 | 功能 | 状态 |
|---|---|---|---|---|
| user.account.not.exists | user | account | - | not.exists |
| verify.code.sms.invalid | verify | code.sms | - | invalid |
| permission.no.access | permission | - | - | no.access |
| operation.add.success | operation | - | add | success |
命名建议
- 使用小写字母:所有Key使用小写字母
- 点号分隔:使用
.分隔层级 - 语义清晰:Key名应能表达消息含义
- 状态后缀:操作结果使用success/fail等后缀
- 错误前缀:错误消息可使用invalid/not等前缀
参数化消息
占位符语法
使用 {index} 作为参数占位符:
properties
# 单个参数
user.account.not.exists=对不起, 您的账号:{0} 不存在
# 多个参数
user.password.retry.locked=密码输入错误{0}次,帐户锁定{1}分钟使用示例
java
// 单个参数
String message = MessageUtils.message("user.account.not.exists", "admin");
// 输出:对不起, 您的账号:admin 不存在
// 多个参数
String message = MessageUtils.message("user.password.retry.locked", 5, 10);
// 输出:密码输入错误5次,帐户锁定10分钟参数类型支持
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | {0} | 直接替换 |
| 数字 | {0} | 自动转换为字符串 |
| 日期 | {0,date} | 支持日期格式化 |
| 数字格式 | {0,number} | 支持数字格式化 |
代码中使用国际化
MessageUtils工具类
框架提供了MessageUtils工具类简化国际化消息的获取:
java
import plus.ruoyi.common.core.utils.MessageUtils;
// 获取消息(无参数)
String msg = MessageUtils.message("operation.success");
// 获取消息(带参数)
String msg = MessageUtils.message("user.account.not.exists", username);
// 获取消息(多个参数)
String msg = MessageUtils.message("user.password.retry.locked", retryCount, lockTime);在Service中使用
java
@Service
public class SysUserServiceImpl implements ISysUserService {
@Override
public void checkUserAllowed(SysUser user) {
if (user.isAdmin()) {
throw new ServiceException(MessageUtils.message("permission.no.update", "管理员"));
}
}
}在Controller中使用
java
@RestController
public class AuthController {
@PostMapping("/login")
public R<LoginVo> login(@RequestBody LoginBody loginBody) {
// 业务处理...
return R.ok(loginVo, MessageUtils.message("user.login.success"));
}
}在异常中使用
java
// 抛出带国际化消息的异常
throw new ServiceException(MessageUtils.message("user.account.disabled", username));
// 全局异常处理器会自动处理语言切换机制
请求头方式
通过HTTP请求头 Accept-Language 切换语言:
http
GET /api/user/info HTTP/1.1
Accept-Language: en-US请求参数方式
通过URL参数切换语言:
GET /api/user/info?lang=en_USCookie方式
通过Cookie存储语言偏好:
http
Cookie: lang=en_US优先级顺序
- URL参数
lang - Cookie
lang - 请求头
Accept-Language - 系统默认语言
添加新语言
步骤一:创建语言文件
复制默认语言文件,创建新语言版本:
bash
cp messages.properties messages_ja_JP.properties步骤二:翻译消息内容
编辑新语言文件,翻译所有消息:
properties
# messages_ja_JP.properties
common.required=* 必須
operation.success=操作成功
user.login.success=ログイン成功步骤三:配置语言支持
如需添加语言选择器,在前端配置可用语言列表:
javascript
const locales = [
{ code: 'zh_CN', name: '简体中文' },
{ code: 'en_US', name: 'English' },
{ code: 'ja_JP', name: '日本語' }
]最佳实践
1. 统一使用工具类
java
// 推荐:使用MessageUtils
throw new ServiceException(MessageUtils.message("user.account.disabled", username));
// 不推荐:硬编码中文
throw new ServiceException("账号已禁用");2. 消息Key分模块管理
properties
# 按模块组织,便于维护
user.login.success=登录成功
user.logout.success=退出成功
# 避免混乱的命名
login.ok=登录成功 # 不推荐
success.login=登录成功 # 不推荐3. 保持各语言文件同步
确保所有语言文件包含相同的Key:
bash
# 检查Key一致性
diff <(grep "^[^#]" messages_zh_CN.properties | cut -d= -f1 | sort) \
<(grep "^[^#]" messages_en_US.properties | cut -d= -f1 | sort)4. 参数化而非拼接
properties
# 推荐:使用参数
user.welcome=欢迎您,{0}
# 不推荐:需要多个消息拼接
user.welcome.prefix=欢迎您,5. 提供清晰的错误提示
properties
# 推荐:具体明确
user.password.complexity.invalid=密码必须同时包含字母和数字
# 不推荐:含糊不清
user.password.invalid=密码无效常见问题
1. 消息显示Key而非内容
问题原因:
- 消息文件路径配置错误
- Key拼写错误
- 消息文件编码问题
解决方案:
yaml
# 确保配置正确
spring:
messages:
basename: i18n/messages
encoding: UTF-8java
// 检查Key是否存在
log.debug("消息Key: {}", MessageUtils.message("user.login.success"));2. 中文显示乱码
问题原因:
- 文件编码不是UTF-8
- IDE编码设置问题
解决方案:
- 确保properties文件使用UTF-8编码保存
- 在IDE中设置properties文件编码为UTF-8
- 或使用Unicode转义:
properties
# Unicode转义形式
user.login.success=\u767b\u5f55\u6210\u529f3. 参数替换不生效
问题原因:
- 占位符格式错误
- 参数数量不匹配
解决方案:
properties
# 正确的占位符格式
user.account.not.exists=账号:{0} 不存在
# 确保传递参数
MessageUtils.message("user.account.not.exists", username);4. 语言切换不生效
问题原因:
- LocaleResolver未配置
- 请求头/参数格式错误
解决方案:
java
// 确保LocaleResolver已配置
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver resolver = new CookieLocaleResolver();
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver;
}5. 新增消息Key未生效
问题原因:
- 消息文件未重新加载
- 缓存问题
解决方案:
yaml
# 开发环境禁用缓存
spring:
messages:
cache-duration: 0或重启应用使新消息生效。
消息Key完整列表
消息统计
| 分类 | 数量 | 前缀 |
|---|---|---|
| 通用验证 | 3 | common.* |
| 操作结果 | 8 | operation.* |
| 用户认证 | 18 | user.* |
| 认证方式 | 3 | auth.* |
| 验证码 | 11 | verify.code.* |
| 第三方登录 | 3 | social.* |
| 文件上传 | 4 | file.* |
| 权限控制 | 6 | permission.* |
| 请求控制 | 2 | request.* |
| 租户管理 | 8 | tenant.* |
| 总计 | 66 | - |
扩展建议
如需添加新的消息,建议按照以下分类:
| 新模块 | 前缀建议 | 示例 |
|---|---|---|
| 商城模块 | mall.* | mall.order.create.success |
| 支付模块 | pay.* | pay.balance.insufficient |
| 工作流 | workflow.* | workflow.task.completed |
| 通知消息 | notify.* | notify.send.success |
