From d01aacc6d427fdc72a4fffb6c62935555728e3ff Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Mon, 25 Aug 2025 21:28:59 +0800 Subject: [PATCH 1/3] feat: add internationalization support --- package-lock.json | 78 +++++++++++++++++++- package.json | 2 + public/locales/en/translation.json | 40 +++++++++++ public/locales/zh/translation.json | 40 +++++++++++ src/App.jsx | 14 ++-- src/components/ChartContainer.jsx | 38 +++++----- src/components/ComparisonControls.jsx | 38 +++++----- src/components/FileList.jsx | 92 ++++++++++++------------ src/components/FileUpload.jsx | 38 +++++----- src/components/Header.jsx | 28 +++++++- src/components/RegexControls.jsx | 8 ++- src/components/ResizablePanel.jsx | 24 ++++--- src/components/ThemeToggle.jsx | 16 +++-- src/components/__tests__/Header.test.jsx | 24 +++++-- src/i18n.js | 20 ++++++ src/main.jsx | 1 + vitest.setup.js | 1 + 17 files changed, 368 insertions(+), 134 deletions(-) create mode 100644 public/locales/en/translation.json create mode 100644 public/locales/zh/translation.json create mode 100644 src/i18n.js diff --git a/package-lock.json b/package-lock.json index c07de6c..9d2fb4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,13 @@ "autoprefixer": "^10.4.21", "chart.js": "^4.5.0", "chartjs-plugin-zoom": "^2.2.0", + "i18next": "^25.4.2", "lucide-react": "^0.522.0", "postcss": "^8.5.6", "react": "^19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", + "react-i18next": "^15.7.2", "tailwindcss": "^3.4.4" }, "devDependencies": { @@ -319,7 +321,6 @@ "version": "7.28.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3471,6 +3472,15 @@ "dev": true, "license": "MIT" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3509,6 +3519,37 @@ "node": ">=16.17.0" } }, + "node_modules/i18next": { + "version": "25.4.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.2.tgz", + "integrity": "sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -4802,6 +4843,32 @@ "react": "^19.1.0" } }, + "node_modules/react-i18next": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.2.tgz", + "integrity": "sha512-xJxq7ibnhUlMvd82lNC4te1GxGUMoM1A05KKyqoqsBXVZtEvZg/fz/fnVzdlY/hhQ3SpP/79qCocZOtICGhd3g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 25.4.1", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -6762,6 +6829,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index b8b0f28..693739c 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,13 @@ "autoprefixer": "^10.4.21", "chart.js": "^4.5.0", "chartjs-plugin-zoom": "^2.2.0", + "i18next": "^25.4.2", "lucide-react": "^0.522.0", "postcss": "^8.5.6", "react": "^19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", + "react-i18next": "^15.7.2", "tailwindcss": "^3.4.4" }, "devDependencies": { diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json new file mode 100644 index 0000000..608e579 --- /dev/null +++ b/public/locales/en/translation.json @@ -0,0 +1,40 @@ +{ + "language": "Language", + "header.language": "Language", + "fileUpload.title": "📁 File Upload", + "fileUpload.drag": "🎯 Drag files here or click to select", + "fileUpload.support": "📄 Supports all text file formats", + "fileUpload.aria": "Select log files, supports all text formats", + "fileList.title": "📋 Loaded Files", + "fileList.empty": "📂 No files", + "fileList.loaded": "Loaded {{count}} files", + "fileList.enabled": "Enabled", + "fileList.disabled": "Disabled", + "fileList.config": "Configure file {{name}}", + "fileList.delete": "Remove file {{name}}", + "comparison.title": "⚖️ Compare Mode", + "comparison.select": "Select comparison mode", + "comparison.normal": "📊 Mean Error (normal)", + "comparison.normalDesc": "Mean error without absolute value", + "comparison.absolute": "📈 Mean Error (absolute)", + "comparison.absoluteDesc": "Mean of absolute differences", + "comparison.relativeNormal": "📉 Relative Error (normal)", + "comparison.relativeNormalDesc": "Relative error without absolute value", + "comparison.relative": "📊 Mean Relative Error (absolute)", + "comparison.relativeDesc": "Mean of absolute relative error", + "themeToggle.aria": "Toggle theme", + "chart.noData": "📊 No data", + "chart.uploadPrompt": "📁 Upload log files to begin", + "chart.selectPrompt": "🎯 Select charts to display", + "chart.diffStats": "Difference Statistics", + "chart": "chart", + "resetConfig": "Reset Config", + "useStepKeyword": "Use Step keyword", + "placeholder.step": "step:", + "resize.drag": "Drag to resize chart height", + "resize.adjust": "Adjust {{title}} chart height", + "exportPNG": "Export PNG", + "copyImage": "Copy image", + "exportCSV": "Export CSV", + "copyImageError": "Failed to copy image" +} diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json new file mode 100644 index 0000000..0298630 --- /dev/null +++ b/public/locales/zh/translation.json @@ -0,0 +1,40 @@ +{ + "language": "语言", + "header.language": "语言", + "fileUpload.title": "📁 文件上传", + "fileUpload.drag": "🎯 拖拽文件到此处或点击选择文件", + "fileUpload.support": "📄 支持所有文本格式文件", + "fileUpload.aria": "选择日志文件,支持所有文本格式", + "fileList.title": "📋 已加载文件", + "fileList.empty": "📂 暂无文件", + "fileList.loaded": "已加载 {{count}} 个文件", + "fileList.enabled": "已启用", + "fileList.disabled": "已禁用", + "fileList.config": "配置文件 {{name}}", + "fileList.delete": "删除文件 {{name}}", + "comparison.title": "⚖️ 对比模式", + "comparison.select": "选择数据对比模式", + "comparison.normal": "📊 平均误差 (normal)", + "comparison.normalDesc": "未取绝对值的平均误差", + "comparison.absolute": "📈 平均误差 (absolute)", + "comparison.absoluteDesc": "绝对值差值的平均", + "comparison.relativeNormal": "📉 相对误差 (normal)", + "comparison.relativeNormalDesc": "不取绝对值的相对误差", + "comparison.relative": "📊 平均相对误差 (absolute)", + "comparison.relativeDesc": "绝对相对误差的平均", + "themeToggle.aria": "切换主题", + "chart.noData": "📊 暂无数据", + "chart.uploadPrompt": "📁 请上传日志文件开始分析", + "chart.selectPrompt": "🎯 请选择要显示的图表", + "chart.diffStats": "差值统计", + "chart": "图表", + "resetConfig": "重置配置", + "useStepKeyword": "使用 Step 关键字", + "placeholder.step": "step:", + "resize.drag": "拖拽调整图表高度", + "resize.adjust": "调整 {{title}} 图表高度", + "exportPNG": "导出 PNG", + "copyImage": "复制图片", + "exportCSV": "导出 CSV", + "copyImageError": "复制图片失败" +} diff --git a/src/App.jsx b/src/App.jsx index f15f88c..62d14f9 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,5 @@ import React, { useState, useCallback, useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { FileUpload } from './components/FileUpload'; import { RegexControls } from './components/RegexControls'; import { FileList } from './components/FileList'; @@ -6,6 +7,7 @@ import ChartContainer from './components/ChartContainer'; import { ComparisonControls } from './components/ComparisonControls'; import { FileConfigModal } from './components/FileConfigModal'; import { ThemeToggle } from './components/ThemeToggle'; +import { Header } from './components/Header'; import { PanelLeftClose, PanelLeftOpen } from 'lucide-react'; import { mergeFilesWithReplacement } from './utils/mergeFiles.js'; @@ -30,7 +32,8 @@ export const DEFAULT_GLOBAL_PARSING_CONFIG = { }; function App() { - const [uploadedFiles, setUploadedFiles] = useState(() => { + const { t } = useTranslation(); + const [uploadedFiles, setUploadedFiles] = useState(() => { const stored = localStorage.getItem('uploadedFiles'); return stored ? JSON.parse(stored) : []; }); @@ -317,7 +320,10 @@ function App() {
平均误差 (normal): {stats.meanNormal.toFixed(6)}
平均误差 (absolute): {stats.meanAbsolute.toFixed(6)}
diff --git a/src/components/ComparisonControls.jsx b/src/components/ComparisonControls.jsx index 7447f60..7e72aa0 100644 --- a/src/components/ComparisonControls.jsx +++ b/src/components/ComparisonControls.jsx @@ -1,16 +1,18 @@ import React from 'react'; import { BarChart2 } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; -export function ComparisonControls({ - compareMode, - onCompareModeChange -}) { - const modes = [ - { value: 'normal', label: '📊 平均误差 (normal)', description: '未取绝对值的平均误差' }, - { value: 'absolute', label: '📈 平均误差 (absolute)', description: '绝对值差值的平均' }, - { value: 'relative-normal', label: '📉 相对误差 (normal)', description: '不取绝对值的相对误差' }, - { value: 'relative', label: '📊 平均相对误差 (absolute)', description: '绝对相对误差的平均' } - ]; + export function ComparisonControls({ + compareMode, + onCompareModeChange + }) { + const { t } = useTranslation(); + const modes = [ + { value: 'normal', label: t('comparison.normal'), description: t('comparison.normalDesc') }, + { value: 'absolute', label: t('comparison.absolute'), description: t('comparison.absoluteDesc') }, + { value: 'relative-normal', label: t('comparison.relativeNormal'), description: t('comparison.relativeNormalDesc') }, + { value: 'relative', label: t('comparison.relative'), description: t('comparison.relativeDesc') } + ]; return (