fix: 术语表导入丢失中文列(BOM) + 导出改用数据库实际内容
- handleGlossaryFiles: 加 transformHeader 剥离BOM+规范化表头, 修复Excel/PapaParse读取带BOM的CSV时第一列名变成 zh 导致中文列被丢弃 - exportGlossaryCsv: 改为从 /service/glossary 拉取数据库实际内容再导出, 避免导出未保存的本地脏数据;导出文件加UTF-8 BOM保证Excel中文不乱码 - 导入成功后提示已导入条数并提醒保存 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -185,7 +185,8 @@
|
|||||||
"glossaryExportCsv": "导出CSV",
|
"glossaryExportCsv": "导出CSV",
|
||||||
"glossaryUnsaved": "未保存",
|
"glossaryUnsaved": "未保存",
|
||||||
"glossarySaved": "术语表已保存,对所有用户生效。",
|
"glossarySaved": "术语表已保存,对所有用户生效。",
|
||||||
"glossarySaveFailed": "保存失败"
|
"glossarySaveFailed": "保存失败",
|
||||||
|
"glossaryImported": "已导入 {n} 条术语,请点击【保存】使其对所有用户生效。"
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
"pageTitle": "DocuTranslate - Interactive Document Translation",
|
"pageTitle": "DocuTranslate - Interactive Document Translation",
|
||||||
@@ -373,7 +374,8 @@
|
|||||||
"glossaryExportCsv": "Export CSV",
|
"glossaryExportCsv": "Export CSV",
|
||||||
"glossaryUnsaved": "Unsaved",
|
"glossaryUnsaved": "Unsaved",
|
||||||
"glossarySaved": "Glossary saved and applied to all users.",
|
"glossarySaved": "Glossary saved and applied to all users.",
|
||||||
"glossarySaveFailed": "Save failed"
|
"glossarySaveFailed": "Save failed",
|
||||||
|
"glossaryImported": "Imported {n} terms. Click [Save] to apply to all users."
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"pageTitle": "DocuTranslate - Terjemahan Dokumen Interaktif",
|
"pageTitle": "DocuTranslate - Terjemahan Dokumen Interaktif",
|
||||||
@@ -561,6 +563,7 @@
|
|||||||
"glossaryExportCsv": "Ekspor CSV",
|
"glossaryExportCsv": "Ekspor CSV",
|
||||||
"glossaryUnsaved": "Belum disimpan",
|
"glossaryUnsaved": "Belum disimpan",
|
||||||
"glossarySaved": "Glosarium disimpan, berlaku untuk semua pengguna.",
|
"glossarySaved": "Glosarium disimpan, berlaku untuk semua pengguna.",
|
||||||
"glossarySaveFailed": "Gagal menyimpan"
|
"glossarySaveFailed": "Gagal menyimpan",
|
||||||
|
"glossaryImported": "Mengimpor {n} istilah. Klik [Simpan] untuk berlaku ke semua pengguna."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1427,15 +1427,22 @@
|
|||||||
glossarySaving.value = false;
|
glossarySaving.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const exportGlossaryCsv = () => {
|
const exportGlossaryCsv = async () => {
|
||||||
const rows = glossaryRows.value;
|
// 从服务端拉取数据库实际内容再导出(避免导出未保存的本地脏数据)
|
||||||
|
let rows = glossaryRows.value;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/service/glossary');
|
||||||
|
const data = await res.json();
|
||||||
|
if (data && Array.isArray(data.rows)) rows = data.rows;
|
||||||
|
} catch (e) { console.error('export fetch failed, using local', e); }
|
||||||
const esc = (s) => {
|
const esc = (s) => {
|
||||||
s = String(s||'');
|
s = String(s||'');
|
||||||
return /[",\n]/.test(s) ? '"' + s.replace(/"/g,'""') + '"' : s;
|
return /[",\n]/.test(s) ? '"' + s.replace(/"/g,'""') + '"' : s;
|
||||||
};
|
};
|
||||||
let csv = 'zh,en,id\n';
|
let csv = 'zh,en,id\n';
|
||||||
rows.forEach(r => { csv += [esc(r.zh), esc(r.en), esc(r.id)].join(',') + '\n'; });
|
rows.forEach(r => { csv += [esc(r.zh), esc(r.en), esc(r.id)].join(',') + '\n'; });
|
||||||
const blob = new Blob([csv], {type: 'text/csv;charset=utf-8'});
|
// 加 UTF-8 BOM,保证 Excel 正确识别中文
|
||||||
|
const blob = new Blob(['' + csv], {type: 'text/csv;charset=utf-8'});
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url; a.download = 'glossary.csv'; a.click();
|
a.href = url; a.download = 'glossary.csv'; a.click();
|
||||||
@@ -1448,8 +1455,11 @@
|
|||||||
Array.from(files).forEach(f => {
|
Array.from(files).forEach(f => {
|
||||||
Papa.parse(f, {
|
Papa.parse(f, {
|
||||||
header: true, skipEmptyLines: true,
|
header: true, skipEmptyLines: true,
|
||||||
|
// 剥离 BOM + 规范化表头(小写、去空白),兼容 Excel 另存的 CSV
|
||||||
|
transformHeader: (h) => String(h||'').replace(/^/, '').trim().toLowerCase(),
|
||||||
complete: (res) => {
|
complete: (res) => {
|
||||||
if (!res.data) return;
|
if (!res.data) return;
|
||||||
|
let added = 0;
|
||||||
res.data.forEach(r => {
|
res.data.forEach(r => {
|
||||||
const row = {zh:'', en:'', id:''};
|
const row = {zh:'', en:'', id:''};
|
||||||
if ('zh' in r || 'en' in r || 'id' in r) {
|
if ('zh' in r || 'en' in r || 'id' in r) {
|
||||||
@@ -1461,8 +1471,10 @@
|
|||||||
if (row.zh || row.en || row.id) {
|
if (row.zh || row.en || row.id) {
|
||||||
glossaryRows.value.push(row);
|
glossaryRows.value.push(row);
|
||||||
glossaryDirty.value = true;
|
glossaryDirty.value = true;
|
||||||
|
added++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (added) alert(t('glossaryImported').replace('{n}', added));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user