本项目采用 React-Intl 库实现国际化(i18n)支持,通过 Redux 状态管理维护多语言状态。本文档详细介绍多语言资源的添加流程、技术实现原理以及相关开发规范。
src/assets/i18n/messages/
├── zh.js # 中文语言资源
└── en.js # 英文语言资源
每个语言文件都是标准的 ES6 模块:
export default {
"greeting": "你好,世界!",
"login.username": "用户名",
"login.username.placeholder": "请输入用户名",
// ... 更多翻译项
};
在组件中定义翻译键,并提供默认消息:
import { FormattedMessage, useIntl } from 'react-intl';
// 方法1:使用 FormattedMessage 组件
<FormattedMessage
id="newFeature.title"
defaultMessage="新功能标题"
/>
// 方法2:使用 useIntl hook
const intl = useIntl();
const title = intl.formatMessage({
id: 'newFeature.title',
defaultMessage: '新功能标题'
});
// src/assets/i18n/messages/zh.js
export default {
// ... 现有翻译
"newFeature.title": "新功能标题",
"newFeature.description": "这是新功能的详细描述",
};
// src/assets/i18n/messages/en.js
export default {
// ... 现有翻译
"newFeature.title": "New Feature Title",
"newFeature.description": "This is the detailed description of the new feature",
};
node scripts/extract-i18n-json.js
脚本执行流程:
zh.js, en.js)export default 语法和结尾分号scripts/output/i18n/ 目录转换后的文件格式:
{
"greeting": "你好,世界!",
"login.username": "用户名",
"newFeature.title": "新功能标题"
}
脚本执行后会自动询问服务器配置:
📤 SCP配置 (按Enter使用默认值):
IP地址 (默认: 192.168.110.245):
用户名 (默认: root):
中文文件路径 (默认: /root/gydr/trans/zh_CN/zh.js):
英文文件路径 (默认: /root/gydr/trans/en_US/en.js):
确认配置后自动通过 SCP 上传:
scp zh.js root@192.168.110.245:/root/gydr/trans/zh_CN/zh.js
scp en.js root@192.168.110.245:/root/gydr/trans/en_US/en.js
// src/app.tsx
import { IntlProvider } from 'react-intl';
<IntlProvider
locale={currentLocale ? currentLocale.split('_')[0] : 'en'}
messages={messages || {}}
>
{/* 应用内容 */}
</IntlProvider>
// src/states/i18nSlice.ts
interface I18nState {
messages: I18nMessages; // 当前语言的翻译字典
currentLocale: string; // 当前语言代码 (en_US/zh_CN)
supportedLocales: string[]; // 支持的语言列表
availableLanguages: LanguageItem[]; // 后端返回的语言选项
loading: boolean; // 加载状态
error: string | null; // 错误信息
}
// 获取语言列表
GET /dr/api/v1/pub/language
// 更新系统语言
POST /api/v1/auth/manage/language
{
"lang": "zh_CN.UTF-8" // 或 "en_US.UTF-8"
}
function extractJsonFromJs(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
// 移除 export default 语句
let jsonString = content
.replace(/export\s+default\s+/g, '')
.trim();
// 移除末尾分号
if (jsonString.endsWith(';')) {
jsonString = jsonString.slice(0, -1);
}
// 安全解析 JSON
const jsonObj = eval('(' + jsonString + ')');
return jsonObj;
}
脚本集成了完整的部署流程:
采用点号分隔的多级命名结构,产品相关命名放在第一级:
[产品.]模块.子模块.元素[.状态]
根据产品类型在第一级添加产品标识符:
animal. 前缀animal.login.username - 宠物产品登录模块用户名animal.register.patientId - 宠物注册模块宠物编号animal.register.patientName.placeholder - 宠物注册模块宠物昵称占位符animal.exam.action.resetParams - 宠物检查模块重置参数动作login.username - 登录模块用户名register.patientId - 注册模块患者编号register.patientName.placeholder - 注册模块患者姓名占位符exam.action.resetParams - 检查模块重置参数动作login.* - 登录相关register.* - 注册相关exam.* - 检查相关worklist.* - 工作清单相关output.* - 输出相关actionPanel.* - 操作面板相关animal.* - 宠物相关功能animal.register.* - 宠物注册animal.worklist.* - 宠物工作清单animal.actionPanel.* - 宠物操作面板.placeholder - 输入框占位符.required - 必填验证消息.success - 成功提示.failed - 失败提示.loading - 加载状态.empty - 空状态.error - 错误状态animal. 前缀,人医产品直接从模块开始// 消息文件
"welcome.user": "欢迎,{name}!"
// 使用方式
<FormattedMessage
id="welcome.user"
values={{ name: userName }}
/>
// 消息文件
"item.count": "有 {count, plural, =0 {没有项目} one {1 个项目} other {# 个项目}}"
// 使用方式
<FormattedMessage
id="item.count"
values={{ count: itemCount }}
/>
const intl = useIntl();
// 日期格式化
const formattedDate = intl.formatDate(date, {
year: 'numeric',
month: 'long',
day: 'numeric'
});
// 数字格式化
const formattedNumber = intl.formatNumber(value, {
style: 'currency',
currency: 'CNY'
});
defaultMessage 作为降级现象: 界面显示翻译键而非翻译文本 原因: 翻译键不存在或语言文件未正确加载 解决:
现象: "Could not find required intl object"
原因: 组件渲染时 IntlProvider 还未挂载
解决: 确保 IntlProvider 始终存在,提供默认值
现象: 脚本执行报错 原因: Node.js 环境问题或文件权限问题 解决:
现象: SCP 上传失败 原因: 网络连接或服务器配置问题 解决:
本项目的多语言实现方案提供了完整的开发到部署流程:
通过标准化的流程和自动化工具,确保了多语言功能的开发效率和维护质量。开发者可以专注于业务功能开发,而翻译部署工作可以通过规范化的脚本完成。