Files
his/healthlink-his-ui/scripts/i18n-medication-final.cjs
chenqi 27273dbb57 feat(i18n): 添加国际化(i18n)基础设施和多语言支持
- 新增 I18nUtils 工具类提供多语言消息获取功能
- 创建多语言技术方案设计文档 MULTILANG_I18N_DESIGN.md
- 编写国际化战略文章 HEALTHLINK_HIS_INTERNATIONALIZATION_STRATEGY.md
- 添加国际化战略概述文档 HEALTHLINK_HIS_INTL_STRATEGY.md
- 实现基于 MessageSource 的多语言消息处理机制
- 设计支持中英越三语的国际化架构方案
- 规划前端 vue-i18n 和后端 MessageSource 集成方案
- 定义数据库多语言表结构支持菜单和字典国际化
- 制定硬编码消息迁移策略和实施计划
- 建立多语言工作量评估和风险应对措施
2026-06-28 07:01:58 +08:00

504 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}`);