feat(i18n): 添加国际化(i18n)基础设施和多语言支持
- 新增 I18nUtils 工具类提供多语言消息获取功能 - 创建多语言技术方案设计文档 MULTILANG_I18N_DESIGN.md - 编写国际化战略文章 HEALTHLINK_HIS_INTERNATIONALIZATION_STRATEGY.md - 添加国际化战略概述文档 HEALTHLINK_HIS_INTL_STRATEGY.md - 实现基于 MessageSource 的多语言消息处理机制 - 设计支持中英越三语的国际化架构方案 - 规划前端 vue-i18n 和后端 MessageSource 集成方案 - 定义数据库多语言表结构支持菜单和字典国际化 - 制定硬编码消息迁移策略和实施计划 - 建立多语言工作量评估和风险应对措施
This commit is contained in:
81
healthlink-his-ui/scripts/extract-keys-by-line.cjs
Normal file
81
healthlink-his-ui/scripts/extract-keys-by-line.cjs
Normal file
@@ -0,0 +1,81 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const BASE = path.join(__dirname, '..', 'src', 'views', 'medicationmanagement');
|
||||
const CWD = path.join(__dirname, '..', '..');
|
||||
|
||||
function walkDir(dir) {
|
||||
const files = [];
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) files.push(...walkDir(fullPath));
|
||||
else if (entry.name.endsWith('.vue')) files.push(fullPath);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function getOriginalContent(filePath) {
|
||||
try {
|
||||
const relPath = path.relative(CWD, filePath).replace(/\\/g, '/');
|
||||
return execSync(`git show HEAD:"${relPath}"`, { encoding: 'utf8', cwd: CWD });
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const vueFiles = walkDir(BASE);
|
||||
const translations = {};
|
||||
|
||||
for (const file of vueFiles) {
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
const original = getOriginalContent(file);
|
||||
if (!original) continue;
|
||||
|
||||
const currentLines = content.split('\n');
|
||||
const originalLines = original.split('\n');
|
||||
|
||||
for (let i = 0; i < currentLines.length; i++) {
|
||||
const line = currentLines[i];
|
||||
// Find t('key') calls in this line
|
||||
const tMatch = line.match(/t\('(medication\.[^']+)'\)/);
|
||||
if (!tMatch) continue;
|
||||
|
||||
const key = tMatch[1];
|
||||
if (translations[key]) continue; // Already mapped
|
||||
|
||||
// Look at the same line in the original file
|
||||
if (i < originalLines.length) {
|
||||
const origLine = originalLines[i];
|
||||
// Find Chinese text in the original line
|
||||
const chineseMatch = origLine.match(/"([^"]*[\u4e00-\u9fff][^"]*)"/);
|
||||
if (chineseMatch) {
|
||||
translations[key] = chineseMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Extracted ${Object.keys(translations).length} translation keys`);
|
||||
|
||||
// Write flat keys
|
||||
const flatPath = path.join(__dirname, '..', 'src', 'i18n', 'locales', 'medication_keys_flat.json');
|
||||
fs.writeFileSync(flatPath, JSON.stringify(translations, null, 2), 'utf8');
|
||||
|
||||
// Write structured keys
|
||||
const outputPath = path.join(__dirname, '..', 'src', 'i18n', 'locales', 'medication_keys.json');
|
||||
const output = {};
|
||||
for (const [key, value] of Object.entries(translations)) {
|
||||
const parts = key.split('.');
|
||||
let current = output;
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
if (!current[parts[i]]) current[parts[i]] = {};
|
||||
current = current[parts[i]];
|
||||
}
|
||||
current[parts[parts.length - 1]] = value;
|
||||
}
|
||||
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf8');
|
||||
|
||||
console.log(`Wrote ${flatPath}`);
|
||||
console.log(`Wrote ${outputPath}`);
|
||||
503
healthlink-his-ui/scripts/i18n-medication-final.cjs
Normal file
503
healthlink-his-ui/scripts/i18n-medication-final.cjs
Normal file
@@ -0,0 +1,503 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BASE = path.join(__dirname, '..', 'src', 'views', 'medicationmanagement');
|
||||
const ZHCN_PATH = path.join(__dirname, '..', 'src', 'i18n', 'locales', 'zhCN.json');
|
||||
|
||||
const zhcn = JSON.parse(fs.readFileSync(ZHCN_PATH, 'utf8'));
|
||||
|
||||
function walkDir(dir) {
|
||||
const files = [];
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) files.push(...walkDir(fullPath));
|
||||
else if (entry.name.endsWith('.vue')) files.push(fullPath);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
const ATTR_PATTERNS = [
|
||||
{ attr: 'placeholder', regex: /(\s+)placeholder="([^"]*[\u4e00-\u9fff][^"]*)"/g },
|
||||
{ attr: 'label', regex: /(\s+)label="([^"]*[\u4e00-\u9fff][^"]*)"/g },
|
||||
{ attr: 'title', regex: /(\s+)title="([^"]*[\u4e00-\u9fff][^"]*)"/g },
|
||||
{ attr: 'start-placeholder', regex: /(\s+)start-placeholder="([^"]*[\u4e00-\u9fff][^"]*)"/g },
|
||||
{ attr: 'end-placeholder', regex: /(\s+)end-placeholder="([^"]*[\u4e00-\u9fff][^"]*)"/g },
|
||||
];
|
||||
|
||||
const COMMON_MAP = {
|
||||
'操作': 'common.operation', '状态': 'common.status', '创建时间': 'common.createTime',
|
||||
'更新时间': 'common.updateTime', '备注': 'common.remark', '请选择': 'common.pleaseSelect',
|
||||
'请输入': 'common.pleaseEnter', '查询': 'common.search', '重置': 'common.reset',
|
||||
'确认': 'common.confirm', '取消': 'common.cancel', '保存': 'common.save',
|
||||
'删除': 'common.delete', '编辑': 'common.edit', '添加': 'common.add',
|
||||
'查看': 'common.view', '详情': 'common.detail', '导出': 'common.export',
|
||||
'导入': 'common.import', '关闭': 'common.close', '提交': 'common.submit',
|
||||
'刷新': 'common.refresh', '男': 'common.male', '女': 'common.female',
|
||||
'是': 'common.yes', '否': 'common.no', '正常': 'common.normal',
|
||||
'停用': 'common.stop', '提示': 'common.tip', '警告': 'common.warning',
|
||||
'错误': 'common.error', '成功': 'common.success', '消息': 'common.message',
|
||||
'开始时间': 'common.startTime', '结束时间': 'common.endTime',
|
||||
'开始日期': 'common.startDate', '结束日期': 'common.endDate',
|
||||
'性别': 'common.gender', '年龄': 'common.age', '姓名': 'common.name',
|
||||
'编码': 'common.code', '门诊号': 'common.outpatientNo',
|
||||
'患者姓名': 'common.patientName', '无数据': 'common.noData',
|
||||
};
|
||||
|
||||
// Comprehensive term mapping
|
||||
const TERM = {
|
||||
'药品名称': 'drugName', '药品编码': 'drugCode', '药品类型': 'drugType',
|
||||
'药品规格': 'drugSpec', '药品批号': 'drugBatchNo', '药品库存': 'drugStock',
|
||||
'药品单价': 'drugUnitPrice', '药品数量': 'drugQty', '药品金额': 'drugAmount',
|
||||
'药品单位': 'drugUnit', '药品分类': 'drugCategory', '药品信息': 'drugInfo',
|
||||
'药品列表': 'drugList', '药品详情': 'drugDetail', '药品汇总': 'drugSummary',
|
||||
'品名': 'itemName', '项目名称': 'itemName', '项目编码': 'itemCode',
|
||||
'项目类型': 'itemType', '项目类别': 'itemCategory', '项目总价': 'itemTotalPrice',
|
||||
'项目信息': 'itemInfo', '项目列表': 'itemList', '项目详情': 'itemDetail',
|
||||
'项目汇总': 'itemSummary', '项目明细': 'itemDetail', '项目': 'item',
|
||||
'规格型号': 'specModel', '规格': 'spec', '单位': 'unit', '数量': 'qty',
|
||||
'单价': 'unitPrice', '金额': 'amount', '总价': 'totalPrice',
|
||||
'合计金额': 'totalAmount', '小计': 'subtotal', '总计': 'grandTotal',
|
||||
'合计': 'total', '差额': 'diff', '盈亏': 'profitLoss',
|
||||
'盘盈': 'surplus', '盘亏': 'shortage', '盈亏数量': 'profitLossQty',
|
||||
'盈亏金额': 'profitLossAmount', '已审核': 'approved', '未审核': 'pending',
|
||||
'待审核': 'pendingApproval', '已退回': 'returned', '已作废': 'voided',
|
||||
'已结清': 'settled', '未结清': 'unsettled', '全部': 'all',
|
||||
'西药': 'westernMedicine', '中成药': 'chinesePatentMedicine',
|
||||
'中草药': 'chineseHerbalMedicine', '中药': 'chineseMedicine',
|
||||
'耗材': 'consumable', '医疗器械': 'medicalDevice', '类别': 'category',
|
||||
'分类': 'classification', '剂型': 'dosageForm', '用法': 'usage',
|
||||
'用量': 'dosage', '频次': 'frequency', '天数': 'days',
|
||||
'总量': 'totalQty', '单量': 'dosePerTime', '库存下限': 'minStock',
|
||||
'库存上限': 'maxStock', '安全库存': 'safeStock', '批号': 'batchNo',
|
||||
'有效期': 'expiryDate', '有效期至': 'expiryDate', '产地': 'origin',
|
||||
'生产厂家': 'manufacturer', '厂家/产地': 'manufacturer', '商品名': 'tradeName',
|
||||
'通用名': 'genericName', '拼音码': 'pinyinCode', '五笔码': 'wubiCode',
|
||||
'医保编码': 'insuranceCode', '自费': 'selfPay', '甲类': 'classA',
|
||||
'乙类': 'classB', '丙类': 'classC', '处方量': 'prescriptionQty',
|
||||
'发药量': 'dispenseQty', '采购量': 'purchaseQty', '入库量': 'inboundQty',
|
||||
'出库量': 'outboundQty', '退货量': 'returnQty', '调拨量': 'transferQty',
|
||||
'盘点量': 'stocktakeQty', '报损量': 'lossQty', '消耗量': 'consumptionQty',
|
||||
'收费员': 'cashier', '挂号员': 'registrar', '医师': 'doctor',
|
||||
'药房': 'pharmacy', '药库': 'drugStorehouse', '门诊': 'outpatient',
|
||||
'住院': 'inpatient', '急诊': 'emergency', '日期': 'date', '时间': 'time',
|
||||
'年': 'year', '月': 'month', '日': 'day', '明细': 'detail', '汇总': 'summary',
|
||||
'清单': 'list', '报表': 'report', '统计': 'statistics', '分析': 'analysis',
|
||||
'预警': 'warning', '提醒': 'reminder', '通知': 'notification', '设置': 'settings',
|
||||
'配置': 'config', '管理': 'management', '维护': 'maintenance', '审核': 'audit',
|
||||
'审批': 'approval', '签收': 'sign', '确认': 'confirm', '退回': 'return',
|
||||
'作废': 'void', '打印': 'print', '预览': 'preview', '下载': 'download',
|
||||
'上传': 'upload', '新增': 'add', '修改': 'edit', '仓库': 'warehouse',
|
||||
'货位': 'location', '请求日期': 'requestDate', '单据号': 'docNo',
|
||||
'单据日期': 'docDate', '药品类型': 'drugType', '源仓库类型': 'sourceWarehouseType',
|
||||
'源仓库': 'sourceWarehouse', '源货位': 'sourceLocation',
|
||||
'目的仓库类型': 'targetWarehouseType', '目的仓库': 'targetWarehouse',
|
||||
'目的货位': 'targetLocation', '产品批号': 'batchNo', '单价(元)': 'unitPriceYuan',
|
||||
'发放数量': 'issueQty', '库存数量': 'stockQty', '实盘数量': 'actualQty',
|
||||
'最小单位': 'minUnit', '拆零比': 'splitRatio', '序号': 'seqNo',
|
||||
'调拨单位': 'transferUnit', '源仓库库存数量': 'sourceStockQty',
|
||||
'目的仓库库存数量': 'targetStockQty', '调拨数量': 'transferQty',
|
||||
'调拨单价': 'transferPrice', '生产日期': 'productionDate',
|
||||
'调拨单据明细': 'transferDocDetail', '收费时间': 'chargeTime',
|
||||
'统计类型': 'statisticsType', '科室': 'department', '开单人': 'orderingDoctor',
|
||||
'收费人': 'cashier', '项目类型': 'itemType', '选择项目类型': 'selectItemType',
|
||||
'医保号': 'medicalInsuranceNo', '药品项目': 'drugItem',
|
||||
'医保码': 'medicalInsuranceCode', '医保类别': 'medicalInsuranceCategory',
|
||||
'医保等级': 'medicalInsuranceLevel', '科室名': 'deptName',
|
||||
'耗材和药品总金额': 'consumableDrugTotal', '产品型号': 'productModel',
|
||||
'所属科室': 'belongingDept', '发放时间': 'issueTime', '版本号': 'versionNo',
|
||||
'搜索': 'search', '请输入项目名称': 'inputItemName',
|
||||
'请选择开始时间': 'selectStartTime', '请选择结束时间': 'selectEndTime',
|
||||
'请选择目的货位': 'selectTargetLocation', '盘点仓库': 'checkWarehouse',
|
||||
'盘点单号': 'checkDocNo', '盘点日期': 'checkDate', '盘点类型': 'checkType',
|
||||
'盘点状态': 'checkStatus', '盘点人': 'stocktaker', '盘点时间': 'stocktakeTime',
|
||||
'审核状态': 'approvalStatus', '审核人': 'approver', '审核时间': 'approvalTime',
|
||||
'制单人': 'maker', '制单时间': 'makeTime', '调拨单号': 'transferDocNo',
|
||||
'调拨日期': 'transferDate', '调拨类型': 'transferType', '调拨状态': 'transferStatus',
|
||||
'申请人': 'applicant', '申请时间': 'applyTime', '审批人': 'approver',
|
||||
'审批时间': 'approvalTime', '批准文号': 'approvalNo', '供应商': 'supplier',
|
||||
'采购价': 'purchasePrice', '零售价': 'retailPrice', '进价': 'purchasePrice',
|
||||
'售价': 'sellingPrice', '库存': 'stock', '当前库存': 'currentStock',
|
||||
'可用库存': 'availableStock', '报损数量': 'lossQty', '报损原因': 'lossReason',
|
||||
'报损单号': 'lossDocNo', '报损日期': 'lossDate', '原价': 'originalPrice',
|
||||
'新价': 'newPrice', '差价': 'priceDiff', '入库单号': 'inboundNo',
|
||||
'入库日期': 'inboundDate', '入库类型': 'inboundType', '出库单号': 'outboundNo',
|
||||
'出库日期': 'outboundDate', '出库类型': 'outboundType', '退货单号': 'returnNo',
|
||||
'退货日期': 'returnDate', '退货状态': 'returnStatus',
|
||||
'对账日期': 'reconciliationDate', '对账状态': 'reconciliationStatus',
|
||||
'结余金额': 'balanceAmount', '期初金额': 'initialAmount',
|
||||
'期末金额': 'finalAmount', '入库金额': 'inboundAmount',
|
||||
'出库金额': 'outboundAmount', '科室汇总': 'deptSummary',
|
||||
'月结年月': 'settlementMonth', '月结状态': 'settlementStatus',
|
||||
'月结日期': 'settlementDate', '门诊收费统计': 'outpatientChargeStats',
|
||||
'收费项目': 'chargeItem', '收费金额': 'chargeAmount', '收费明细': 'chargeDetail',
|
||||
'收费汇总': 'chargeSummary', '患者': 'patient', '就诊日期': 'visitDate',
|
||||
'诊断': 'diagnosis', '处方号': 'prescriptionNo', '处方日期': 'prescriptionDate',
|
||||
'处方状态': 'prescriptionStatus', '发药窗口': 'dispenseWindow', '发药人': 'dispenser',
|
||||
'发药时间': 'dispenseTime', '配药人': 'preparer', '配药时间': 'prepareTime',
|
||||
'退药': 'returnDrug', '退药数量': 'returnDrugQty', '退药原因': 'returnDrugReason',
|
||||
'退药时间': 'returnDrugTime', '请选择仓库': 'selectWarehouse',
|
||||
'请选择货位': 'selectLocation', '请选择药品类型': 'selectDrugType',
|
||||
'请选择源仓库类型': 'selectSourceWarehouseType', '请选择源仓库': 'selectSourceWarehouse',
|
||||
'请选择源货位': 'selectSourceLocation', '请选择目的仓库类型': 'selectTargetWarehouseType',
|
||||
'请选择目的仓库': 'selectTargetWarehouse', '请选择目的货位': 'selectTargetLocation',
|
||||
'请输入单据号': 'inputDocNo', '请输入药品名称': 'inputDrugName',
|
||||
'请输入药品编码': 'inputDrugCode', '请输入批号': 'inputBatchNo',
|
||||
'请输入数量': 'inputQty', '请输入单价': 'inputUnitPrice',
|
||||
'请输入备注': 'inputRemark', '请输入规格': 'inputSpec',
|
||||
'请输入供应商': 'inputSupplier', '请输入生产厂家': 'inputManufacturer',
|
||||
'请输入批准文号': 'inputApprovalNo', '请选择日期': 'selectDate',
|
||||
'请选择状态': 'selectStatus', '请选择类型': 'selectType',
|
||||
'请选择分类': 'selectCategory', '请输入关键词搜索': 'inputKeyword',
|
||||
'输入项目名/拼音搜索': 'searchByItemName', '请选择审核状态': 'selectApprovalStatus',
|
||||
'请选择盘点类型': 'selectCheckType', '请选择盘点仓库': 'selectCheckWarehouse',
|
||||
'请选择结算类型': 'selectSettlementType', '请选择对账状态': 'selectReconciliationStatus',
|
||||
'请输入项目编码': 'inputItemCode', '请输入患者姓名': 'inputPatientName',
|
||||
'请输入门诊号': 'inputOutpatientNo', '请输入医保号': 'inputMedicalInsuranceNo',
|
||||
'请选择科室': 'selectDepartment', '请选择开单人': 'selectOrderingDoctor',
|
||||
'请选择收费人': 'selectCashier', '请选择发药窗口': 'selectDispenseWindow',
|
||||
'请选择发药人': 'selectDispenser', '请选择处方状态': 'selectPrescriptionStatus',
|
||||
'请选择退药原因': 'selectReturnReason', '请选择报损原因': 'selectLossReason',
|
||||
'请选择调价原因': 'selectPriceAdjReason', '请选择入库类型': 'selectInboundType',
|
||||
'请选择出库类型': 'selectOutboundType', '请选择退货状态': 'selectReturnStatus',
|
||||
'请选择供应商': 'selectSupplier', '请选择药品': 'selectDrug',
|
||||
'请选择剂型': 'selectDosageForm', '请选择用法': 'selectUsage',
|
||||
'请选择频次': 'selectFrequency', '请选择医师': 'selectDoctor',
|
||||
'请选择药房': 'selectPharmacy', '请选择药库': 'selectDrugStorehouse',
|
||||
'请选择门诊': 'selectOutpatient', '请选择住院': 'selectInpatient',
|
||||
'请选择急诊': 'selectEmergency', '请输入拼音码搜索': 'searchByPinyin',
|
||||
'请输入通用名搜索': 'searchByGenericName', '请输入商品名搜索': 'searchByTradeName',
|
||||
'请选择日期范围': 'selectDateRange', '请输入库存下限': 'inputMinStock',
|
||||
'请输入库存上限': 'inputMaxStock', '请输入安全库存': 'inputSafeStock',
|
||||
'请输入有效期': 'inputExpiryDate', '请输入产地': 'inputOrigin',
|
||||
'请输入用量': 'inputDosage', '请输入天数': 'inputDays',
|
||||
'请输入总量': 'inputTotalQty', '请输入单量': 'inputDosePerTime',
|
||||
'请输入金额': 'inputAmount', '请输入价格': 'inputPrice',
|
||||
'请输入差价': 'inputPriceDiff', '请输入原因': 'inputReason',
|
||||
'请输入说明': 'inputDescription', '请输入内容': 'inputContent',
|
||||
'请输入标题': 'inputTitle', '请输入名称': 'inputName',
|
||||
'请输入编号': 'inputCode', '请输入电话': 'inputPhone',
|
||||
'请输入地址': 'inputAddress', '请选择操作': 'selectOperation',
|
||||
'请选择时间': 'selectTime', '请选择年月': 'selectYearMonth',
|
||||
'请选择年份': 'selectYear', '请选择月份': 'selectMonth',
|
||||
'请输入单据号': 'inputDocNo', '单据类型': 'docType', '单据状态': 'docStatus',
|
||||
'采购单号': 'purchaseNo', '采购日期': 'purchaseDate', '采购类型': 'purchaseType',
|
||||
'采购状态': 'purchaseStatus', '采购人': 'buyer', '采购时间': 'purchaseTime',
|
||||
'采购数量': 'purchaseQty', '采购金额': 'purchaseAmount',
|
||||
'采购单价': 'purchaseUnitPrice', '采购单位': 'purchaseUnit',
|
||||
'采购批号': 'purchaseBatchNo', '采购有效期': 'purchaseExpiryDate',
|
||||
'采购产地': 'purchaseOrigin', '采购生产厂家': 'purchaseManufacturer',
|
||||
'采购商品名': 'purchaseTradeName', '采购通用名': 'purchaseGenericName',
|
||||
'请领单号': 'requisitionNo', '请领日期': 'requisitionDate',
|
||||
'请领类型': 'requisitionType', '请领状态': 'requisitionStatus',
|
||||
'请领人': 'requisitioner', '请领时间': 'requisitionTime',
|
||||
'请领数量': 'requisitionQty', '请领金额': 'requisitionAmount',
|
||||
'请领单价': 'requisitionUnitPrice', '请领单位': 'requisitionUnit',
|
||||
'退回单号': 'returnNo', '退回日期': 'returnDate', '退回类型': 'returnType',
|
||||
'退回状态': 'returnStatus', '退回人': 'returnPerson', '退回时间': 'returnTime',
|
||||
'退回原因': 'returnReason', '退回数量': 'returnQty', '退回金额': 'returnAmount',
|
||||
'退回单价': 'returnUnitPrice', '退回单位': 'returnUnit', '退回批号': 'returnBatchNo',
|
||||
'调拨单号': 'transferNo', '调拨日期': 'transferDate', '调拨类型': 'transferType',
|
||||
'调拨状态': 'transferStatus', '调拨人': 'transferPerson', '调拨时间': 'transferTime',
|
||||
'调拨原因': 'transferReason', '调拨数量': 'transferQty', '调拨金额': 'transferAmount',
|
||||
'调拨单价': 'transferUnitPrice', '调拨单位': 'transferUnit',
|
||||
'调拨批号': 'transferBatchNo', '对账日期': 'reconciliationDate',
|
||||
'对账类型': 'reconciliationType', '对账状态': 'reconciliationStatus',
|
||||
'对账人': 'reconciler', '对账时间': 'reconciliationTime',
|
||||
'对账数量': 'reconciliationQty', '对账金额': 'reconciliationAmount',
|
||||
'对账单价': 'reconciliationUnitPrice', '对账单位': 'reconciliationUnit',
|
||||
'对账批号': 'reconciliationBatchNo', '结余金额': 'balanceAmount',
|
||||
'期初金额': 'initialAmount', '期末金额': 'finalAmount',
|
||||
'入库金额': 'inboundAmount', '出库金额': 'outboundAmount',
|
||||
'月结年月': 'settlementMonth', '月结状态': 'settlementStatus',
|
||||
'月结日期': 'settlementDate', '门诊收费统计': 'outpatientChargeStats',
|
||||
'收费项目': 'chargeItem', '收费金额': 'chargeAmount', '收费明细': 'chargeDetail',
|
||||
'收费汇总': 'chargeSummary', '就诊日期': 'visitDate', '诊断': 'diagnosis',
|
||||
'处方号': 'prescriptionNo', '处方日期': 'prescriptionDate',
|
||||
'处方状态': 'prescriptionStatus', '发药窗口': 'dispenseWindow',
|
||||
'发药人': 'dispenser', '发药时间': 'dispenseTime', '配药人': 'preparer',
|
||||
'配药时间': 'prepareTime', '退药': 'returnDrug', '退药数量': 'returnDrugQty',
|
||||
'退药原因': 'returnDrugReason', '退药时间': 'returnDrugTime',
|
||||
'盘点人': 'stocktaker', '盘点时间': 'stocktakeTime',
|
||||
'请选择仓库': 'selectWarehouse', '请选择货位': 'selectLocation',
|
||||
'请选择药品类型': 'selectDrugType', '请选择源仓库类型': 'selectSourceWarehouseType',
|
||||
'请选择源仓库': 'selectSourceWarehouse', '请选择源货位': 'selectSourceLocation',
|
||||
'请选择目的仓库类型': 'selectTargetWarehouseType', '请选择目的仓库': 'selectTargetWarehouse',
|
||||
'请选择目的货位': 'selectTargetLocation', '请输入单据号': 'inputDocNo',
|
||||
'请输入药品名称': 'inputDrugName', '请输入药品编码': 'inputDrugCode',
|
||||
'请输入批号': 'inputBatchNo', '请输入数量': 'inputQty',
|
||||
'请输入单价': 'inputUnitPrice', '请输入备注': 'inputRemark',
|
||||
'请输入规格': 'inputSpec', '请输入供应商': 'inputSupplier',
|
||||
'请输入生产厂家': 'inputManufacturer', '请输入批准文号': 'inputApprovalNo',
|
||||
'请选择日期': 'selectDate', '请选择状态': 'selectStatus',
|
||||
'请选择类型': 'selectType', '请选择分类': 'selectCategory',
|
||||
'请输入关键词搜索': 'inputKeyword', '输入项目名/拼音搜索': 'searchByItemName',
|
||||
'请选择审核状态': 'selectApprovalStatus', '请选择盘点类型': 'selectCheckType',
|
||||
'请选择盘点仓库': 'selectCheckWarehouse', '请选择结算类型': 'selectSettlementType',
|
||||
'请选择对账状态': 'selectReconciliationStatus', '请输入项目编码': 'inputItemCode',
|
||||
'请输入患者姓名': 'inputPatientName', '请输入门诊号': 'inputOutpatientNo',
|
||||
'请输入医保号': 'inputMedicalInsuranceNo', '请选择科室': 'selectDepartment',
|
||||
'请选择开单人': 'selectOrderingDoctor', '请选择收费人': 'selectCashier',
|
||||
'请选择发药窗口': 'selectDispenseWindow', '请选择发药人': 'selectDispenser',
|
||||
'请选择处方状态': 'selectPrescriptionStatus', '请选择退药原因': 'selectReturnReason',
|
||||
'请选择报损原因': 'selectLossReason', '请选择调价原因': 'selectPriceAdjReason',
|
||||
'请选择入库类型': 'selectInboundType', '请选择出库类型': 'selectOutboundType',
|
||||
'请选择退货状态': 'selectReturnStatus', '请选择供应商': 'selectSupplier',
|
||||
'请选择药品': 'selectDrug', '请选择剂型': 'selectDosageForm',
|
||||
'请选择用法': 'selectUsage', '请选择频次': 'selectFrequency',
|
||||
'请选择医师': 'selectDoctor', '请选择药房': 'selectPharmacy',
|
||||
'请选择药库': 'selectDrugStorehouse', '请选择门诊': 'selectOutpatient',
|
||||
'请选择住院': 'selectInpatient', '请选择急诊': 'selectEmergency',
|
||||
'请输入拼音码搜索': 'searchByPinyin', '请输入通用名搜索': 'searchByGenericName',
|
||||
'请输入商品名搜索': 'searchByTradeName', '请选择日期范围': 'selectDateRange',
|
||||
'请输入库存下限': 'inputMinStock', '请输入库存上限': 'inputMaxStock',
|
||||
'请输入安全库存': 'inputSafeStock', '请输入有效期': 'inputExpiryDate',
|
||||
'请输入产地': 'inputOrigin', '请输入用量': 'inputDosage',
|
||||
'请输入天数': 'inputDays', '请输入总量': 'inputTotalQty',
|
||||
'请输入单量': 'inputDosePerTime', '请输入金额': 'inputAmount',
|
||||
'请输入价格': 'inputPrice', '请输入差价': 'inputPriceDiff',
|
||||
'请输入原因': 'inputReason', '请输入说明': 'inputDescription',
|
||||
'请输入内容': 'inputContent', '请输入标题': 'inputTitle',
|
||||
'请输入名称': 'inputName', '请输入编号': 'inputCode',
|
||||
'请输入电话': 'inputPhone', '请输入地址': 'inputAddress',
|
||||
'请选择操作': 'selectOperation', '请选择时间': 'selectTime',
|
||||
'请选择年月': 'selectYearMonth', '请选择年份': 'selectYear',
|
||||
'请选择月份': 'selectMonth', '调价单号': 'priceAdjNo', '调价日期': 'priceAdjDate',
|
||||
'调价类型': 'priceAdjType', '调价状态': 'priceAdjStatus', '调价人': 'priceAdjuster',
|
||||
'调价时间': 'priceAdjTime', '调价原因': 'priceAdjReason', '调价数量': 'priceAdjQty',
|
||||
'调价金额': 'priceAdjAmount', '调价单价': 'priceAdjUnitPrice',
|
||||
'调价单位': 'priceAdjUnit', '调价批号': 'priceAdjBatchNo',
|
||||
'报损单号': 'lossDocNo', '报损日期': 'lossDate', '报损类型': 'lossType',
|
||||
'报损状态': 'lossStatus', '报损人': 'lossPerson', '报损时间': 'lossTime',
|
||||
'报损原因': 'lossReason', '报损数量': 'lossQty', '报损金额': 'lossAmount',
|
||||
'报损单价': 'lossUnitPrice', '报损单位': 'lossUnit', '报损批号': 'lossBatchNo',
|
||||
'入库单号': 'inboundNo', '入库日期': 'inboundDate', '入库类型': 'inboundType',
|
||||
'入库人': 'inboundPerson', '入库时间': 'inboundTime', '入库数量': 'inboundQty',
|
||||
'入库金额': 'inboundAmount', '入库单价': 'inboundUnitPrice',
|
||||
'出库单号': 'outboundNo', '出库日期': 'outboundDate', '出库类型': 'outboundType',
|
||||
'出库人': 'outboundPerson', '出库时间': 'outboundTime', '出库数量': 'outboundQty',
|
||||
'出库金额': 'outboundAmount', '出库单价': 'outboundUnitPrice',
|
||||
'退货单号': 'returnNo', '退货日期': 'returnDate', '退货类型': 'returnType',
|
||||
'退货状态': 'returnStatus', '退货人': 'returnPerson', '退货时间': 'returnTime',
|
||||
'退货原因': 'returnReason', '退货数量': 'returnQty', '退货金额': 'returnAmount',
|
||||
'退货单价': 'returnUnitPrice', '退货单位': 'returnUnit', '退货批号': 'returnBatchNo',
|
||||
'请选择开始日期': 'selectStartDate', '请选择结束日期': 'selectEndDate',
|
||||
'请输入项目名称': 'inputItemName', '请输入仓库': 'inputWarehouse',
|
||||
'请输入货位': 'inputLocation', '请输入药品类型': 'inputDrugType',
|
||||
'请输入源仓库类型': 'inputSourceWarehouseType', '请输入源仓库': 'inputSourceWarehouse',
|
||||
'请输入源货位': 'inputSourceLocation', '请输入目的仓库类型': 'inputTargetWarehouseType',
|
||||
'请输入目的仓库': 'inputTargetWarehouse', '请输入目的货位': 'inputTargetLocation',
|
||||
'请输入调拨单号': 'inputTransferDocNo', '请输入调拨日期': 'inputTransferDate',
|
||||
'请输入调拨类型': 'inputTransferType', '请输入调拨状态': 'inputTransferStatus',
|
||||
'请输入调拨人': 'inputTransferPerson', '请输入调拨时间': 'inputTransferTime',
|
||||
'请输入调拨原因': 'inputTransferReason', '请输入调拨数量': 'inputTransferQty',
|
||||
'请输入调拨金额': 'inputTransferAmount', '请输入调拨单价': 'inputTransferUnitPrice',
|
||||
'请输入调拨单位': 'inputTransferUnit', '请输入调拨批号': 'inputTransferBatchNo',
|
||||
'请输入对账日期': 'inputReconciliationDate', '请输入对账类型': 'inputReconciliationType',
|
||||
'请输入对账状态': 'inputReconciliationStatus', '请输入对账人': 'inputReconciler',
|
||||
'请输入对账时间': 'inputReconciliationTime', '请输入对账数量': 'inputReconciliationQty',
|
||||
'请输入对账金额': 'inputReconciliationAmount', '请输入对账单价': 'inputReconciliationUnitPrice',
|
||||
'请输入对账单位': 'inputReconciliationUnit', '请输入对账批号': 'inputReconciliationBatchNo',
|
||||
'请输入结余金额': 'inputBalanceAmount', '请输入期初金额': 'inputInitialAmount',
|
||||
'请输入期末金额': 'inputFinalAmount', '请输入入库金额': 'inputInboundAmount',
|
||||
'请输入出库金额': 'inputOutboundAmount', '请输入月结年月': 'inputSettlementMonth',
|
||||
'请输入月结状态': 'inputSettlementStatus', '请输入月结日期': 'inputSettlementDate',
|
||||
'请输入门诊收费统计': 'inputOutpatientChargeStats', '请输入收费项目': 'inputChargeItem',
|
||||
'请输入收费金额': 'inputChargeAmount', '请输入收费明细': 'inputChargeDetail',
|
||||
'请输入收费汇总': 'inputChargeSummary', '请输入患者': 'inputPatient',
|
||||
'请输入就诊日期': 'inputVisitDate', '请输入诊断': 'inputDiagnosis',
|
||||
'请输入处方号': 'inputPrescriptionNo', '请输入处方日期': 'inputPrescriptionDate',
|
||||
'请输入处方状态': 'inputPrescriptionStatus', '请输入发药窗口': 'inputDispenseWindow',
|
||||
'请输入发药人': 'inputDispenser', '请输入发药时间': 'inputDispenseTime',
|
||||
'请输入配药人': 'inputPreparer', '请输入配药时间': 'inputPrepareTime',
|
||||
'请输入退药': 'inputReturnDrug', '请输入退药数量': 'inputReturnDrugQty',
|
||||
'请输入退药原因': 'inputReturnDrugReason', '请输入退药时间': 'inputReturnDrugTime',
|
||||
'请输入盘点人': 'inputStocktaker', '请输入盘点时间': 'inputStocktakeTime',
|
||||
'请输入请选择仓库': 'inputSelectWarehouse', '请输入请选择货位': 'inputSelectLocation',
|
||||
'请输入请选择药品类型': 'inputSelectDrugType',
|
||||
'请输入请选择源仓库类型': 'inputSelectSourceWarehouseType',
|
||||
'请输入请选择源仓库': 'inputSelectSourceWarehouse',
|
||||
'请输入请选择源货位': 'inputSelectSourceLocation',
|
||||
'请输入请选择目的仓库类型': 'inputSelectTargetWarehouseType',
|
||||
'请输入请选择目的仓库': 'inputSelectTargetWarehouse',
|
||||
'请输入请选择目的货位': 'inputSelectTargetLocation',
|
||||
'请输入请选择日期': 'inputSelectDate', '请输入请选择状态': 'inputSelectStatus',
|
||||
'请输入请选择类型': 'inputSelectType', '请输入请选择分类': 'inputSelectCategory',
|
||||
'请输入请输入关键词搜索': 'inputKeyword', '请输入输入项目名/拼音搜索': 'searchByItemName',
|
||||
'请输入请选择审核状态': 'inputSelectApprovalStatus',
|
||||
'请输入请选择盘点类型': 'inputSelectCheckType',
|
||||
'请输入请选择盘点仓库': 'inputSelectCheckWarehouse',
|
||||
'请输入请选择结算类型': 'inputSelectSettlementType',
|
||||
'请输入请选择对账状态': 'inputSelectReconciliationStatus',
|
||||
'请输入请选择科室': 'inputSelectDepartment',
|
||||
'请输入请选择开单人': 'inputSelectOrderingDoctor',
|
||||
'请输入请选择收费人': 'inputSelectCashier',
|
||||
'请输入请选择发药窗口': 'inputSelectDispenseWindow',
|
||||
'请输入请选择发药人': 'inputSelectDispenser',
|
||||
'请输入请选择处方状态': 'inputSelectPrescriptionStatus',
|
||||
'请输入请选择退药原因': 'inputSelectReturnReason',
|
||||
'请输入请选择报损原因': 'inputSelectLossReason',
|
||||
'请输入请选择调价原因': 'inputSelectPriceAdjReason',
|
||||
'请输入请选择入库类型': 'inputSelectInboundType',
|
||||
'请输入请选择出库类型': 'inputSelectOutboundType',
|
||||
'请输入请选择退货状态': 'inputSelectReturnStatus',
|
||||
'请输入请选择供应商': 'inputSelectSupplier', '请输入请选择药品': 'inputSelectDrug',
|
||||
'请输入请选择剂型': 'inputSelectDosageForm', '请输入请选择用法': 'inputSelectUsage',
|
||||
'请输入请选择频次': 'inputSelectFrequency', '请输入请选择医师': 'inputSelectDoctor',
|
||||
'请输入请选择药房': 'inputSelectPharmacy', '请输入请选择药库': 'inputSelectDrugStorehouse',
|
||||
'请输入请选择门诊': 'inputSelectOutpatient', '请输入请选择住院': 'inputSelectInpatient',
|
||||
'请输入请选择急诊': 'inputSelectEmergency', '请输入请输入拼音码搜索': 'searchByPinyin',
|
||||
'请输入请输入通用名搜索': 'searchByGenericName',
|
||||
'请输入请输入商品名搜索': 'searchByTradeName',
|
||||
'请输入请选择日期范围': 'inputSelectDateRange',
|
||||
'请输入请选择开始时间': 'inputSelectStartTime',
|
||||
'请输入请选择结束时间': 'inputSelectEndTime',
|
||||
};
|
||||
|
||||
function getModuleName(filePath) {
|
||||
const rel = path.relative(BASE, filePath);
|
||||
const parts = rel.split(path.sep);
|
||||
if (parts.length === 1) return path.basename(filePath, '.vue');
|
||||
if (parts.length === 2 && parts[1] === 'index.vue') return parts[0];
|
||||
if (parts.length >= 3) {
|
||||
if (parts[1] === 'components') return parts[0] + '_' + path.basename(filePath, '.vue');
|
||||
return parts[1];
|
||||
}
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
function chineseToKey(text, moduleName) {
|
||||
let clean = text.replace(/[::]\s*$/, '').trim();
|
||||
if (COMMON_MAP[clean]) return COMMON_MAP[clean];
|
||||
if (TERM[clean]) return `medication.${moduleName}.${TERM[clean]}`;
|
||||
|
||||
// Try prefix decomposition
|
||||
let prefix = '';
|
||||
let remaining = clean;
|
||||
for (const [zh, en] of [['请输入', 'input'], ['请选择', 'select']]) {
|
||||
if (remaining.startsWith(zh)) {
|
||||
prefix = en;
|
||||
remaining = remaining.substring(zh.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining && TERM[remaining]) {
|
||||
const base = TERM[remaining];
|
||||
return `medication.${moduleName}.${prefix}${base.charAt(0).toUpperCase()}${base.slice(1)}`;
|
||||
}
|
||||
|
||||
// Try partial match
|
||||
for (const [zh, en] of Object.entries(TERM)) {
|
||||
if (remaining.includes(zh)) {
|
||||
return `medication.${moduleName}.${prefix}${en.charAt(0).toUpperCase()}${en.slice(1)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: use hex of Chinese
|
||||
const hex = Buffer.from(clean, 'utf8').toString('hex').substring(0, 16);
|
||||
return `medication.${moduleName}.${prefix || 'val'}_${hex}`;
|
||||
}
|
||||
|
||||
// Global translation collection
|
||||
const allTranslations = {};
|
||||
|
||||
function processFile(filePath) {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
const original = content;
|
||||
const moduleName = getModuleName(filePath);
|
||||
let hasChanges = false;
|
||||
const hasI18n = content.includes('useI18n');
|
||||
|
||||
for (const { attr, regex } of ATTR_PATTERNS) {
|
||||
const re = new RegExp(regex.source, regex.flags);
|
||||
let match;
|
||||
const replacements = [];
|
||||
|
||||
while ((match = re.exec(content)) !== null) {
|
||||
const [fullMatch, whitespace, chineseText] = match;
|
||||
const key = chineseToKey(chineseText, moduleName);
|
||||
allTranslations[key] = chineseText;
|
||||
|
||||
const replacement = `${whitespace}:${attr}="t('${key}')"`;
|
||||
replacements.push({ fullMatch, replacement, index: match.index });
|
||||
}
|
||||
|
||||
for (let i = replacements.length - 1; i >= 0; i--) {
|
||||
const { fullMatch, replacement, index } = replacements[i];
|
||||
content = content.substring(0, index) + replacement + content.substring(index + fullMatch.length);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasChanges) return;
|
||||
|
||||
if (!hasI18n) {
|
||||
const scriptSetupMatch = content.match(/(<script\s+setup[^>]*>)/);
|
||||
if (scriptSetupMatch) {
|
||||
const scriptTag = scriptSetupMatch[1];
|
||||
const insertPos = content.indexOf(scriptTag) + scriptTag.length;
|
||||
const afterScript = content.substring(insertPos);
|
||||
const firstImportMatch = afterScript.match(/\n(import\s)/);
|
||||
|
||||
if (firstImportMatch) {
|
||||
const importPos = insertPos + afterScript.indexOf(firstImportMatch[1]);
|
||||
content = content.substring(0, importPos) +
|
||||
"import { useI18n } from 'vue-i18n';\n" +
|
||||
content.substring(importPos);
|
||||
} else {
|
||||
content = content.substring(0, insertPos) +
|
||||
"\nimport { useI18n } from 'vue-i18n';" +
|
||||
content.substring(insertPos);
|
||||
}
|
||||
|
||||
const importSection = content.match(/(<script[^>]*>[\s\S]*?)(?=\n(?:const|let|var|function|\/\/|\/\*|\*|<\/script>))/);
|
||||
if (importSection) {
|
||||
const lastImportEnd = importSection[0].length + content.indexOf(importSection[0]);
|
||||
const afterImports = content.substring(lastImportEnd);
|
||||
const firstCodeMatch = afterImports.match(/\n(const|let|var|function|\/\/|\/\*|\*)/);
|
||||
if (firstCodeMatch) {
|
||||
const codePos = lastImportEnd + afterImports.indexOf(firstCodeMatch[1]);
|
||||
content = content.substring(0, codePos) +
|
||||
"const { t } = useI18n();\n" +
|
||||
content.substring(codePos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content !== original) {
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
const vueFiles = walkDir(BASE);
|
||||
console.log(`Processing ${vueFiles.length} Vue files...`);
|
||||
|
||||
for (const file of vueFiles) {
|
||||
processFile(file);
|
||||
}
|
||||
|
||||
console.log(`Collected ${Object.keys(allTranslations).length} translation keys`);
|
||||
|
||||
// Write flat keys
|
||||
const flatPath = path.join(__dirname, '..', 'src', 'i18n', 'locales', 'medication_keys_flat.json');
|
||||
fs.writeFileSync(flatPath, JSON.stringify(allTranslations, null, 2), 'utf8');
|
||||
|
||||
// Write structured keys
|
||||
const outputPath = path.join(__dirname, '..', 'src', 'i18n', 'locales', 'medication_keys.json');
|
||||
const output = {};
|
||||
for (const [key, value] of Object.entries(allTranslations)) {
|
||||
const parts = key.split('.');
|
||||
let current = output;
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
if (!current[parts[i]]) current[parts[i]] = {};
|
||||
current = current[parts[i]];
|
||||
}
|
||||
current[parts[parts.length - 1]] = value;
|
||||
}
|
||||
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf8');
|
||||
|
||||
console.log(`Wrote ${flatPath}`);
|
||||
console.log(`Wrote ${outputPath}`);
|
||||
Reference in New Issue
Block a user