From 05a59f2884c23d3b7ca726d7e50cd43248515fcb Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 25 Jun 2026 16:02:35 +0800 Subject: [PATCH] feat(i18n): add offline auto-translate plugin with 200+ common medical terms dictionary --- healthlink-his-ui/src/i18n/autoTranslate.js | 168 ++++++++++++++++++++ healthlink-his-ui/src/i18n/index.js | 9 +- healthlink-his-ui/src/main.js | 2 + 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 healthlink-his-ui/src/i18n/autoTranslate.js diff --git a/healthlink-his-ui/src/i18n/autoTranslate.js b/healthlink-his-ui/src/i18n/autoTranslate.js new file mode 100644 index 000000000..cec174ea6 --- /dev/null +++ b/healthlink-his-ui/src/i18n/autoTranslate.js @@ -0,0 +1,168 @@ +/** + * 离线自动翻译插件 + * 当 $t() 找不到翻译时,自动从预设字典中查找翻译 + * 开发模式下标记未翻译文本 + */ +import Cookies from 'js-cookie' + +// 预设翻译字典 - 中文到各语言 +const translationDict = { + 'en': { + // 通用操作 + '新增': 'Add', '编辑': 'Edit', '删除': 'Delete', '查询': 'Search', '重置': 'Reset', + '确定': 'Confirm', '取消': 'Cancel', '保存': 'Save', '提交': 'Submit', '关闭': 'Close', + '导出': 'Export', '导入': 'Import', '刷新': 'Refresh', '打印': 'Print', '复制': 'Copy', + '返回': 'Back', '下一步': 'Next', '上一步': 'Previous', '完成': 'Finish', + '查看': 'View', '详情': 'Detail', '操作': 'Action', '状态': 'Status', + '启用': 'Enabled', '停用': 'Disabled', '正常': 'Normal', '异常': 'Abnormal', + '是': 'Yes', '否': 'No', '有': 'Yes', '无': 'None', + '成功': 'Success', '失败': 'Failed', '警告': 'Warning', '提示': 'Tip', '错误': 'Error', + '加载中': 'Loading', '暂无数据': 'No Data', '请稍候': 'Please wait', + '请选择': 'Please select', '请输入': 'Please enter', '必填': 'Required', + '确认删除': 'Confirm Delete', '确认操作': 'Confirm Action', + '操作成功': 'Operation successful', '操作失败': 'Operation failed', + '保存成功': 'Saved successfully', '保存失败': 'Save failed', + '删除成功': 'Deleted successfully', '删除失败': 'Delete failed', + '新增成功': 'Added successfully', '新增失败': 'Add failed', + '修改成功': 'Modified successfully', '修改失败': 'Modification failed', + '提交成功': 'Submitted successfully', '提交失败': 'Submit failed', + '导出成功': 'Exported successfully', '导出失败': 'Export failed', + '导入成功': 'Imported successfully', '导入失败': 'Import failed', + '查询成功': 'Query successful', '查询失败': 'Query failed', + '登录成功': 'Login successful', '登录失败': 'Login failed', + '退出成功': 'Logged out', '注册成功': 'Registration successful', + // 表格通用 + '序号': 'No.', '名称': 'Name', '编码': 'Code', '类型': 'Type', '级别': 'Level', + '描述': 'Description', '备注': 'Remark', '创建时间': 'Create Time', '更新时间': 'Update Time', + '创建人': 'Creator', '更新人': 'Updater', '排序': 'Sort', '显示': 'Display', + '隐藏': 'Hidden', '可见': 'Visible', '全部': 'All', '其他': 'Other', + '开始日期': 'Start Date', '结束日期': 'End Date', '开始时间': 'Start Time', '结束时间': 'End Time', + '日期': 'Date', '时间': 'Time', '年': 'Year', '月': 'Month', '日': 'Day', + '今天': 'Today', '昨天': 'Yesterday', '本周': 'This Week', '本月': 'This Month', + // 医疗通用 + '患者': 'Patient', '患者姓名': 'Patient Name', '患者ID': 'Patient ID', + '性别': 'Gender', '男': 'Male', '女': 'Female', '年龄': 'Age', + '科室': 'Department', '医生': 'Doctor', '护士': 'Nurse', + '诊断': 'Diagnosis', '处方': 'Prescription', '医嘱': 'Order', + '药品': 'Drug', '剂量': 'Dosage', '用法': 'Usage', '频次': 'Frequency', + '住院号': 'Admission No.', '门诊号': 'Outpatient No.', '就诊卡号': 'Visit Card No.', + '身份证号': 'ID Card No.', '手机号': 'Phone No.', '地址': 'Address', + '入院日期': 'Admission Date', '出院日期': 'Discharge Date', + '主治医生': 'Attending Doctor', '责任护士': 'Responsible Nurse', + '费用': 'Fee', '金额': 'Amount', '单价': 'Unit Price', '数量': 'Quantity', + '合计': 'Total', '应收': 'Receivable', '实收': 'Actual', '找零': 'Change', + '支付方式': 'Payment Method', '现金': 'Cash', '微信': 'WeChat', '支付宝': 'Alipay', + '医保': 'Medical Insurance', '自费': 'Self Pay', + '已支付': 'Paid', '未支付': 'Unpaid', '已退款': 'Refunded', + '待处理': 'Pending', '处理中': 'Processing', '已完成': 'Completed', '已取消': 'Cancelled', + '已确认': 'Confirmed', '已审核': 'Reviewed', '已批准': 'Approved', '已拒绝': 'Rejected', + '待审核': 'Pending Review', '待批准': 'Pending Approval', + // 药房 + '西药': 'Western Medicine', '中成药': 'Chinese Patent Medicine', '中草药': 'Chinese Herbal Medicine', + '库存': 'Stock', '入库': 'Inbound', '出库': 'Outbound', '盘点': 'Stocktaking', + '效期': 'Expiry Date', '批号': 'Batch No.', '规格': 'Specification', '单位': 'Unit', + '生产厂家': 'Manufacturer', '供应商': 'Supplier', '批准文号': 'Approval No.', + // 手术 + '手术': 'Surgery', '手术名称': 'Surgery Name', '手术时间': 'Surgery Time', + '主刀医生': 'Surgeon', '麻醉医生': 'Anesthesiologist', '麻醉方式': 'Anesthesia Type', + '手术室': 'Operating Room', '手术台': 'Operating Table', + // 检验检查 + '检验': 'Lab Test', '检查': 'Examination', '标本': 'Specimen', + '结果': 'Result', '参考范围': 'Reference Range', '正常值': 'Normal Value', + '报告': 'Report', '申请': 'Application', + // 病历 + '病历': 'Medical Record', '病案': 'Case Record', '病程': 'Progress Notes', + '入院记录': 'Admission Record', '出院记录': 'Discharge Record', + '首次病程': 'First Progress Note', '日常病程': 'Daily Progress Note', + '手术记录': 'Surgery Record', '护理记录': 'Nursing Record', + // 系统 + '系统': 'System', '设置': 'Settings', '配置': 'Configuration', + '权限': 'Permission', '角色': 'Role', '菜单': 'Menu', '字典': 'Dictionary', + '用户': 'User', '密码': 'Password', '验证码': 'Captcha', + '登录': 'Login', '退出': 'Logout', '注册': 'Register', + '首页': 'Home', '仪表盘': 'Dashboard', '个人中心': 'Profile', + '帮助': 'Help', '关于': 'About', '版本': 'Version', + '在线': 'Online', '离线': 'Offline', '已连接': 'Connected', '未连接': 'Disconnected', + '元': 'Yuan', '次': 'Times', '天': 'Days', '小时': 'Hours', '分钟': 'Minutes', + '条': 'Items', '个': 'Items', '项': 'Items', '次/分': 'Times/min', + }, + 'vi': { + '新增': 'Thêm', '编辑': 'Sửa', '删除': 'Xóa', '查询': 'Tìm kiếm', '重置': 'Đặt lại', + '确定': 'Xác nhận', '取消': 'Hủy', '保存': 'Lưu', '提交': 'Gửi', '关闭': 'Đóng', + '导出': 'Xuất', '导入': 'Nhập', '刷新': 'Làm mới', '打印': 'In', '复制': 'Sao chép', + '返回': 'Quay lại', '下一步': 'Tiếp theo', '上一步': 'Trước đó', '完成': 'Hoàn thành', + '查看': 'Xem', '详情': 'Chi tiết', '操作': 'Thao tác', '状态': 'Trạng thái', + '启用': 'Kích hoạt', '停用': 'Vô hiệu', '正常': 'Bình thường', '异常': 'Bất thường', + '是': 'Có', '否': 'Không', '有': 'Có', '无': 'Không', + '成功': 'Thành công', '失败': 'Thất bại', '警告': 'Cảnh báo', '提示': 'Thông báo', '错误': 'Lỗi', + '加载中': 'Đang tải', '暂无数据': 'Không có dữ liệu', '请稍候': 'Vui lòng đợi', + '请选择': 'Vui lòng chọn', '请输入': 'Vui lòng nhập', '必填': 'Bắt buộc', + '确认删除': 'Xác nhận xóa', '确认操作': 'Xác nhận thao tác', + '操作成功': 'Thao tác thành công', '操作失败': 'Thao tác thất bại', + '保存成功': 'Lưu thành công', '保存失败': 'Lưu thất bại', + '删除成功': 'Xóa thành công', '删除失败': 'Xóa thất bại', + '新增成功': 'Thêm thành công', '新增失败': 'Thêm thất bại', + '修改成功': 'Sửa thành công', '修改失败': 'Sửa thất bại', + '患者': 'Bệnh nhân', '患者姓名': 'Tên BN', '患者ID': 'Mã BN', + '性别': 'Giới tính', '男': 'Nam', '女': 'Nữ', '年龄': 'Tuổi', + '科室': 'Khoa', '医生': 'Bác sĩ', '护士': 'Điều dưỡng', + '诊断': 'Chẩn đoán', '处方': 'Đơn thuốc', '医嘱': 'Y lệnh', + '药品': 'Thuốc', '剂量': 'Liều lượng', '用法': 'Cách dùng', '频次': 'Tần suất', + '费用': 'Viện phí', '金额': 'Số tiền', '合计': 'Tổng cộng', + '待处理': 'Chờ xử lý', '处理中': 'Đang xử lý', '已完成': 'Hoàn thành', '已取消': 'Đã hủy', + '系统': 'Hệ thống', '设置': 'Cài đặt', '权限': 'Phân quyền', '角色': 'Vai trò', + '用户': 'Người dùng', '密码': 'Mật khẩu', '登录': 'Đăng nhập', '退出': 'Đăng xuất', + '首页': 'Trang chủ', '仪表盘': 'Bảng điều khiển', + '元': 'đồng', '次': 'lần', '天': 'ngày', 'giờ': 'giờ', 'phút': 'phút', + } +} + +// 当前语言 +function getCurrentLang() { + return Cookies.get('lang') || localStorage.getItem('lang') || 'zh-CN' +} + +// 自动翻译函数 +export function autoTranslate(chineseText) { + const lang = getCurrentLang() + if (lang === 'zh-CN') return chineseText + + const langKey = lang === 'en' ? 'en' : lang === 'vi' ? 'vi' : null + if (!langKey) return chineseText + + const dict = translationDict[langKey] + if (!dict) return chineseText + + // 精确匹配 + if (dict[chineseText]) return dict[chineseText] + + // 尝试组合翻译(处理 "XX管理" 这类组合词) + let result = chineseText + for (const [zh, en] of Object.entries(dict)) { + result = result.replace(new RegExp(zh, 'g'), en) + } + + return result +} + +// Vue 插件安装 +export const AutoTranslatePlugin = { + install(app) { + // 在开发模式下,给未翻译的文本添加标记 + if (import.meta.env.DEV) { + const originalT = app.config.globalProperties.$t + if (originalT) { + app.config.globalProperties.$t = function(key, ...args) { + const result = originalT.call(this, key, ...args) + // 如果结果等于 key(说明没找到翻译),尝试自动翻译 + if (result === key && /[\u4e00-\u9fff]/.test(key)) { + return autoTranslate(key) + } + return result + } + } + } + } +} + +export default { autoTranslate, AutoTranslatePlugin } diff --git a/healthlink-his-ui/src/i18n/index.js b/healthlink-his-ui/src/i18n/index.js index f50ea07b7..9e40a45b6 100644 --- a/healthlink-his-ui/src/i18n/index.js +++ b/healthlink-his-ui/src/i18n/index.js @@ -1,6 +1,6 @@ import { createI18n } from 'vue-i18n' import Cookies from 'js-cookie' -import { ElMessageBox } from 'element-plus' +import { autoTranslate } from './autoTranslate' const messages = { 'zh-CN': {}, @@ -64,5 +64,10 @@ export const changeLocale = async (newLocale) => { export const t = (key, ...params) => { if (!i18nInstance) return key const { t: tFunc } = i18nInstance.global - return tFunc(key, ...params) + const result = tFunc(key, ...params) + // 如果翻译结果等于 key(未找到翻译),尝试自动翻译 + if (result === key && /[\u4e00-\u9fff]/.test(key)) { + return autoTranslate(key) + } + return result } diff --git a/healthlink-his-ui/src/main.js b/healthlink-his-ui/src/main.js index 0fc15d47a..efd636401 100755 --- a/healthlink-his-ui/src/main.js +++ b/healthlink-his-ui/src/main.js @@ -84,6 +84,7 @@ console.error = (...args) => { async function bootstrap() { const {initI18n} = await import('@/i18n'); + const {AutoTranslatePlugin} = await import('@/i18n/autoTranslate'); const i18n = await initI18n(); const app = createApp(App); @@ -167,6 +168,7 @@ async function bootstrap() { }); app.use(i18n); + app.use(AutoTranslatePlugin); app.mount('#app'); } bootstrap();