wordpress知识库系统,上海网站seo外包,做网站选哪个语言,网站曝光率#x1f4c1; 项目结构i18n-manager/
├── backend/ # 后端服务
│ ├── src/
│ │ ├── config/ # 配置文件
│ │ ├── models/ # 数据模型
│ │ ├── routes/ # API路由
│ │ ├── … 项目结构i18n-manager/ ├── backend/ # 后端服务 │ ├── src/ │ │ ├── config/ # 配置文件 │ │ ├── models/ # 数据模型 │ │ ├── routes/ # API路由 │ │ ├── controllers/ # 控制器 │ │ ├── services/ # 业务逻辑 │ │ ├── middleware/ # 中间件 │ │ └── utils/ # 工具函数 │ ├── package.json │ └── server.js ├── frontend/ # 前端应用 │ ├── src/ │ │ ├── components/ # React组件 │ │ ├── pages/ # 页面 │ │ ├── services/ # API服务 │ │ ├── hooks/ # 自定义Hooks │ │ ├── utils/ # 工具函数 │ │ └── App.jsx │ ├── package.json │ └── vite.config.js ├── docker-compose.yml # Docker配置 ├── README.md # 项目文档 └── BLOG.md # CSDN博客文章 后端代码1.backend/package.json{ name: i18n-manager-backend, version: 1.0.0, description: i18n Manager Backend API, main: server.js, scripts: { start: node server.js, dev: nodemon server.js, test: jest }, dependencies: { express: ^4.18.2, mongoose: ^8.0.0, cors: ^2.8.5, dotenv: ^16.3.1, bcryptjs: ^2.4.3, jsonwebtoken: ^9.0.2, multer: ^1.4.5-lts.1, openai: ^4.20.0, axios: ^1.6.0, joi: ^17.11.0, winston: ^3.11.0, express-rate-limit: ^7.1.5, helmet: ^7.1.0, compression: ^1.7.4 }, devDependencies: { nodemon: ^3.0.1, jest: ^29.7.0 } }2.backend/server.jsconst express require(express); const mongoose require(mongoose); const cors require(cors); const helmet require(helmet); const compression require(compression); const rateLimit require(express-rate-limit); require(dotenv).config(); const authRoutes require(./src/routes/auth.routes); const projectRoutes require(./src/routes/project.routes); const translationRoutes require(./src/routes/translation.routes); const aiRoutes require(./src/routes/ai.routes); const logger require(./src/utils/logger); const app express(); // ═══════════════════════════════════════════════════════════════ // 中间件配置 // ═══════════════════════════════════════════════════════════════ // 安全中间件 app.use(helmet()); app.use(compression()); // CORS配置 app.use(cors({ origin: process.env.FRONTEND_URL || http://localhost:5173, credentials: true })); // 请求解析 app.use(express.json({ limit: 10mb })); app.use(express.urlencoded({ extended: true, limit: 10mb })); // 速率限制 const limiter rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100, // 限制100个请求 message: Too many requests from this IP, please try again later. }); app.use(/api/, limiter); // 请求日志 app.use((req, res, next) { logger.info(${req.method} ${req.path}, { ip: req.ip, userAgent: req.get(user-agent) }); next(); }); // ═══════════════════════════════════════════════════════════════ // 路由配置 // ═══════════════════════════════════════════════════════════════ app.use(/api/auth, authRoutes); app.use(/api/projects, projectRoutes); app.use(/api/translations, translationRoutes); app.use(/api/ai, aiRoutes); // 健康检查 app.get(/health, (req, res) { res.json({ status: OK, timestamp: new Date().toISOString(), uptime: process.uptime() }); }); // 404处理 app.use((req, res) { res.status(404).json({ error: Not Found, message: Route ${req.method} ${req.path} not found }); }); // 错误处理 app.use((err, req, res, next) { logger.error(Server Error:, err); res.status(err.status || 500).json({ error: err.message || Internal Server Error, ...(process.env.NODE_ENV development { stack: err.stack }) }); }); // ═══════════════════════════════════════════════════════════════ // 数据库连接和服务器启动 // ═══════════════════════════════════════════════════════════════ const PORT process.env.PORT || 3000; const MONGODB_URI process.env.MONGODB_URI || mongodb://localhost:27017/i18n-manager; mongoose.connect(MONGODB_URI) .then(() { logger.info(✅ Connected to MongoDB); app.listen(PORT, () { logger.info( Server running on port ${PORT}); logger.info( Environment: ${process.env.NODE_ENV || development}); }); }) .catch((err) { logger.error(❌ MongoDB connection error:, err); process.exit(1); }); // 优雅关闭 process.on(SIGTERM, () { logger.info(SIGTERM signal received: closing HTTP server); mongoose.connection.close(); process.exit(0); }); module.exports app;3.backend/src/models/Project.model.jsconst mongoose require(mongoose); const projectSchema new mongoose.Schema({ name: { type: String, required: true, trim: true, maxlength: 100 }, description: { type: String, trim: true, maxlength: 500 }, sourceLanguage: { type: String, required: true, default: en }, targetLanguages: [{ type: String, required: true }], owner: { type: mongoose.Schema.Types.ObjectId, ref: User, required: true }, members: [{ user: { type: mongoose.Schema.Types.ObjectId, ref: User }, role: { type: String, enum: [admin, translator, reviewer, viewer], default: translator }, addedAt: { type: Date, default: Date.now } }], settings: { autoTranslate: { type: Boolean, default: false }, requireReview: { type: Boolean, default: true }, allowMachineTranslation: { type: Boolean, default: true } }, stats: { totalKeys: { type: Number, default: 0 }, translatedKeys: { type: Number, default: 0 }, reviewedKeys: { type: Number, default: 0 }, progress: { type: Number, default: 0 } }, status: { type: String, enum: [active, archived, deleted], default: active } }, { timestamps: true }); // 索引 projectSchema.index({ owner: 1, status: 1 }); projectSchema.index({ members.user: 1 }); projectSchema.index({ name: text, description: text }); // 虚拟字段翻译进度 projectSchema.virtual(translationProgress).get(function() { if (this.stats.totalKeys 0) return 0; return Math.round((this.stats.translatedKeys / this.stats.totalKeys) * 100); }); // 方法检查用户权限 projectSchema.methods.hasPermission function(userId, requiredRole) { if (this.owner.toString() userId.toString()) return true; const member this.members.find(m m.user.toString() userId.toString()); if (!member) return false; const roleHierarchy { viewer: 1, translator: 2, reviewer: 3, admin: 4 }; return roleHierarchy[member.role] roleHierarchy[requiredRole]; }; // 方法更新统计信息 projectSchema.methods.updateStats async function() { const Translation mongoose.model(Translation); const stats await Translation.aggregate([ { $match: { project: this._id } }, { $group: { _id: null, totalKeys: { $sum: 1 }, translatedKeys: { $sum: { $cond: [{ $ne: [$status, pending] }, 1, 0] } }, reviewedKeys: { $sum: { $cond: [{ $eq: [$status, approved] }, 1, 0] } } } } ]); if (stats.length 0) { this.stats stats[0]; this.stats.progress this.translationProgress; await this.save(); } }; module.exports mongoose.model(Project, projectSchema);4.backend/src/models/Translation.model.jsconst mongoose require(mongoose); const translationSchema new mongoose.Schema({ project: { type: mongoose.Schema.Types.ObjectId, ref: Project, required: true }, key: { type: String, required: true, trim: true }, namespace: { type: String, default: common, trim: true }, sourceText: { type: String, required: true }, translations: { type: Map, of: { text: String, status: { type: String, enum: [pending, translated, reviewed, approved], default: pending }, translator: { type: mongoose.Schema.Types.ObjectId, ref: User }, reviewer: { type: mongoose.Schema.Types.ObjectId, ref: User }, translatedAt: Date, reviewedAt: Date, isMachineTranslated: { type: Boolean, default: false }, confidence: { type: Number, min: 0, max: 1 } } }, context: { type: String, trim: true }, tags: [{ type: String, trim: true }], metadata: { type: Map, of: mongoose.Schema.Types.Mixed }, version: { type: Number, default: 1 }, history: [{ version: Number, language: String, text: String, changedBy: { type: mongoose.Schema.Types.ObjectId, ref: User }, changedAt: { type: Date, default: Date.now } }] }, { timestamps: true }); // 复合索引 translationSchema.index({ project: 1, key: 1, namespace: 1 }, { unique: true }); translationSchema.index({ project: 1, translations.status: 1 }); translationSchema.index({ tags: 1 }); // 方法获取特定语言的翻译 translationSchema.methods.getTranslation function(language) { return this.translations.get(language); }; // 方法更新翻译 translationSchema.methods.updateTranslation async function(language, text, userId, isMachine false) { const translation this.translations.get(language) || {}; // 保存历史记录 this.history.push({ version: this.version, language, text: translation.text, changedBy: userId, changedAt: new Date() }); // 更新翻译 this.translations.set(language, { text, status: isMachine ? translated : reviewed, translator: userId, translatedAt: new Date(), isMachineTranslated: isMachine, confidence: isMachine ? 0.8 : 1.0 }); this.version 1; await this.save(); // 更新项目统计 const Project mongoose.model(Project); const project await Project.findById(this.project); if (project) { await project.updateStats(); } }; // 方法批量导出 translationSchema.statics.exportTranslations async function(projectId, language, format json) { const translations await this.find({ project: projectId }) .select(key namespace translations) .lean(); const result {}; translations.forEach(t { const translation t.translations.get(language); if (translation translation.text) { const key t.namespace ? ${t.namespace}.${t.key} : t.key; result[key] translation.text; } }); if (format json) { return JSON.stringify(result, null, 2); } else if (format flat) { return result; } return result; }; module.exports mongoose.model(Translation, translationSchema);5.backend/src/models/User.model.jsconst mongoose require(mongoose); const bcrypt require(bcryptjs); const userSchema new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true, trim: true }, password: { type: String, required: true, minlength: 6 }, name: { type: String, required: true, trim: true }, avatar: { type: String }, role: { type: String, enum: [user, admin], default: user }, languages: [{ code: String, proficiency: { type: String, enum: [native, fluent, intermediate, beginner] } }], preferences: { theme: { type: String, enum: [light, dark, auto], default: auto }, notifications: { email: { type: Boolean, default: true }, inApp: { type: Boolean, default: true } } }, stats: { translationsCount: { type: Number, default: 0 }, reviewsCount: { type: Number, default: 0 }, projectsCount: { type: Number, default: 0 } }, lastLogin: Date, isActive: { type: Boolean, default: true } }, { timestamps: true }); // 索引 userSchema.index({ email: 1 }); // 密码加密中间件 userSchema.pre(save, async function(next) { if (!this.isModified(password)) return next(); try { const salt await bcrypt.genSalt(10); this.password await bcrypt.hash(this.password, salt); next(); } catch (error) { next(error); } }); // 方法验证密码 userSchema.methods.comparePassword async function(candidatePassword) { return await bcrypt.compare(candidatePassword, this.password); }; // 方法生成JWT token userSchema.methods.generateAuthToken function() { const jwt require(jsonwebtoken); return jwt.sign( { id: this._id, email: this.email, role: this.role }, process.env.JWT_SECRET || your-secret-key, { expiresIn: 7d } ); }; // 方法获取公开信息 userSchema.methods.toPublicJSON function() { return { id: this._id, email: this.email, name: this.name, avatar: this.avatar, role: this.role, languages: this.languages, stats: this.stats }; }; module.exports mongoose.model(User, userSchema);6.backend/src/services/ai.service.jsconst OpenAI require(openai); const logger require(../utils/logger); class AIService { constructor() { this.openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); this.model process.env.OPENAI_MODEL || gpt-4-turbo-preview; } /** * AI辅助翻译 */ async translate(text, sourceLang, targetLang, context ) { try { const prompt this._buildTranslationPrompt(text, sourceLang, targetLang, context); const response await this.openai.chat.completions.create({ model: this.model, messages: [ { role: system, content: You are a professional translator. Provide accurate, natural translations while preserving the original meaning and tone. }, { role: user, content: prompt } ], temperature: 0.3, max_tokens: 1000 }); const translation response.choices[0].message.content.trim(); return { translation, confidence: this._calculateConfidence(response), model: this.model, usage: response.usage }; } catch (error) { logger.error(AI Translation Error:, error); throw new Error(AI translation failed: error.message); } } /** * 批量翻译 */ async batchTranslate(texts, sourceLang, targetLang, context ) { try { const results await Promise.all( texts.map(text this.translate(text, sourceLang, targetLang, context)) ); return results; } catch (error) { logger.error(Batch Translation Error:, error); throw error; } } /** * 翻译质量检查 */ async checkTranslationQuality(sourceText, translatedText, sourceLang, targetLang) { try { const prompt Evaluate the translation quality: Source (${sourceLang}): ${sourceText} Translation (${targetLang}): ${translatedText} Provide a score from 0-100 and identify any issues: - Accuracy - Fluency - Tone preservation - Grammar Return JSON format: { score: number, issues: [string], suggestions: [string] } ; const response await this.openai.chat.completions.create({ model: this.model, messages: [ { role: system, content: You are a translation quality expert. Evaluate translations objectively. }, { role: user, content: prompt } ], temperature: 0.2, response_format: { type: json_object } }); return JSON.parse(response.choices[0].message.content); } catch (error) { logger.error(Quality Check Error:, error); throw error; } } /** * 生成翻译建议 */ async suggestTranslations(text, sourceLang, targetLang, count 3) { try { const prompt Provide ${count} different translation options for: Text: ${text} From: ${sourceLang} To: ${targetLang} Return JSON array with translations and their contexts: [ { translation: text, context: when to use, formality: formal|informal|neutral } ] ; const response await this.openai.chat.completions.create({ model: this.model, messages: [ { role: system, content: You are a multilingual expert providing translation alternatives. }, { role: user, content: prompt } ], temperature: 0.7, response_format: { type: json_object } }); const result JSON.parse(response.choices[0].message.content); return result.suggestions || []; } catch (error) { logger.error(Suggestion Generation Error:, error); throw error; } } /** * 构建翻译提示词 */ _buildTranslationPrompt(text, sourceLang, targetLang, context) { let prompt Translate the following text from ${sourceLang} to ${targetLang}:\n\n${text}; if (context) { prompt \n\nContext: ${context}; } prompt \n\nProvide only the translation, without explanations.; return prompt; } /** * 计算翻译置信度 */ _calculateConfidence(response) { // 基于finish_reason和其他因素计算置信度 if (response.choices[0].finish_reason stop) { return 0.9; } else if (response.choices[0].finish_reason length) { return 0.7; } return 0.5; } } module.exports new AIService();7.backend/src/controllers/translation.controller.jsconst Translation require(../models/Translation.model); const Project require(../models/Project.model); const aiService require(../services/ai.service); const logger require(../utils/logger); class TranslationController { /** * 获取项目的所有翻译 */ async getTranslations(req, res) { try { const { projectId } req.params; const { language, status, search, page 1, limit 50 } req.query; // 验证项目访问权限 const project await Project.findById(projectId); if (!project) { return res.status(404).json({ error: Project not found }); } if (!project.hasPermission(req.user.id, viewer)) { return res.status(403).json({ error: Access denied }); } // 构建查询 const query { project: projectId }; if (status) { query[translations.${language}.status] status; } if (search) { query.$or [ { key: new RegExp(search, i) }, { sourceText: new RegExp(search, i) } ]; } // 分页查询 const skip (page - 1) * limit; const translations await Translation.find(query) .sort({ key: 1 }) .skip(skip) .limit(parseInt(limit)) .populate(translations.translator, name email) .populate(translations.reviewer, name email); const total await Translation.countDocuments(query); res.json({ translations, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); } catch (error) { logger.error(Get Translations Error:, error); res.status(500).json({ error: error.message }); } } /** * 创建翻译条目 */ async createTranslation(req, res) { try { const { projectId } req.params; const { key, namespace, sourceText, context, tags } req.body; // 验证项目权限 const project await Project.findById(projectId); if (!project) { return res.status(404).json({ error: Project not found }); } if (!project.hasPermission(req.user.id, translator)) { return res.status(403).json({ error: Access denied }); } // 检查key是否已存在 const existing await Translation.findOne({ project: projectId, key, namespace: namespace || common }); if (existing) { return res.status(400).json({ error: Translation key already exists }); } // 创建翻译 const translation new Translation({ project: projectId, key, namespace: namespace || common, sourceText, context, tags, translations: new Map() }); // 如果启用自动翻译 if (project.settings.autoTranslate) { for (const targetLang of project.targetLanguages) { try { const result await aiService.translate( sourceText, project.sourceLanguage, targetLang, context ); translation.translations.set(targetLang, { text: result.translation, status: translated, translator: req.user.id, translatedAt: new Date(), isMachineTranslated: true, confidence: result.confidence }); } catch (error) { logger.error(Auto-translate failed for ${targetLang}:, error); } } } await translation.save(); await project.updateStats(); res.status(201).json(translation); } catch (error) { logger.error(Create Translation Error:, error); res.status(500).json({ error: error.message }); } } /** * 更新翻译 */ async updateTranslation(req, res) { try { const { translationId } req.params; const { language, text } req.body; const translation await Translation.findById(translationId) .populate(project); if (!translation) { return res.status(404).json({ error: Translation not found }); } // 验证权限 if (!translation.project.hasPermission(req.user.id, translator)) { return res.status(403).json({ error: Access denied }); } await translation.updateTranslation(language, text, req.user.id, false); res.json(translation); } catch (error) { logger.error(Update Translation Error:, error); res.status(500).json({ error: error.message }); } } /** * AI辅助翻译 */ async aiTranslate(req, res) { try { const { translationId } req.params; const { language } req.body; const translation await Translation.findById(translationId) .populate(project); if (!translation) { return res.status(404).json({ error: Translation not found }); } // 验证权限 if (!translation.project.hasPermission(req.user.id, translator)) { return res.status(403).json({ error: Access denied }); } // AI翻译 const result await aiService.translate( translation.sourceText, translation.project.sourceLanguage, language, translation.context ); await translation.updateTranslation( language, result.translation, req.user.id, true ); res.json({ translation: translation.translations.get(language), confidence: result.confidence, usage: result.usage }); } catch (error) { logger.error(AI Translate Error:, error); res.status(500).json({ error: error.message }); } } /** * 批量AI翻译 */ async batchAiTranslate(req, res) { try { const { projectId } req.params; const { language, filter {} } req.body; const project await Project.findById(projectId); if (!project) { return res.status(404).json({ error: Project not found }); } if (!project.hasPermission(req.user.id, translator)) { return res.status(403).json({ error: Access denied }); } // 查找待翻译的条目 const query { project: projectId, [translations.${language}]: { $exists: false } }; if (filter.namespace) { query.namespace filter.namespace; } const translations await Translation.find(query).limit(100); const results []; for (const translation of translations) { try { const result await aiService.translate( translation.sourceText, project.sourceLanguage, language, translation.context ); await translation.updateTranslation( language, result.translation, req.user.id, true ); results.push({ key: translation.key, status: success, translation: result.translation }); } catch (error) { results.push({ key: translation.key, status: failed, error: error.message }); } } res.json({ total: translations.length, results }); } catch (error) { logger.error(Batch AI Translate Error:, error); res.status(500).json({ error: error.message }); } } /** * 导出翻译 */ async exportTranslations(req, res) { try { const { projectId } req.params; const { language, format json } req.query; const project await Project.findById(projectId); if (!project) { return res.status(404).json({ error: Project not found }); } if (!project.hasPermission(req.user.id, viewer)) { return res.status(403).json({ error: Access denied }); } const data await Translation.exportTranslations(projectId, language, format); res.setHeader(Content-Type, application/json); res.setHeader(Content-Disposition, attachment; filename${project.name}-${language}.json); res.send(data); } catch (error) { logger.error(Export Translations Error:, error); res.status(500).json({ error: error.message }); } } /** * 导入翻译 */ async importTranslations(req, res) { try { const { projectId } req.params; const { language, data, namespace common } req.body; const project await Project.findById(projectId); if (!project) { return res.status(404).json({ error: Project not found }); } if (!project.hasPermission(req.user.id, translator)) { return res.status(403).json({ error: Access denied }); } const results { created: 0, updated: 0, failed: 0, errors: [] }; for (const [key, text] of Object.entries(data)) { try { let translation await Translation.findOne({ project: projectId, key, namespace }); if (translation) { await translation.updateTranslation(language, text, req.user.id, false); results.updated; } else { translation new Translation({ project: projectId, key, namespace, sourceText: text, translations: new Map() }); translation.translations.set(language, { text, status: translated, translator: req.user.id, translatedAt: new Date() }); await translation.save(); results.created; } } catch (error) { results.failed; results.errors.push({ key, error: error.message }); } } await project.updateStats(); res.json(results); } catch (error) { logger.error(Import Translations Error:, error); res.status(500).json({ error: error.message }); } } } module.exports new TranslationController(); 前端代码8.frontend/package.json{ name: i18n-manager-frontend, version: 1.0.0, type: module, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { react: ^18.2.0, react-dom: ^18.2.0, react-router-dom: ^6.20.0, axios: ^1.6.0, tanstack/react-query: ^5.12.0, zustand: ^4.4.7, lucide-react: ^0.294.0, react-hot-toast: ^2.4.1, framer-motion: ^10.16.0, recharts: ^2.10.0, headlessui/react: ^1.7.17 }, devDependencies: { vitejs/plugin-react: ^4.2.0, vite: ^5.0.0, tailwindcss: ^3.3.5, autoprefixer: ^10.4.16, postcss: ^8.4.32 } }9.frontend/src/App.jsximport React from react; import { BrowserRouter, Routes, Route, Navigate } from react-router-dom; import { QueryClient, QueryClientProvider } from tanstack/react-query; import { Toaster } from react-hot-toast; // Pages import Login from ./pages/Login; import Dashboard from ./pages/Dashboard; import ProjectList from ./pages/ProjectList; import ProjectDetail from ./pages/ProjectDetail; import TranslationEditor from ./pages/TranslationEditor; // Components import PrivateRoute from ./components/PrivateRoute; import Layout from ./components/Layout; // Hooks import { useAuthStore } from ./stores/authStore; const queryClient new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 1, staleTime: 5 * 60 * 1000, // 5分钟 }, }, }); function App() { const { isAuthenticated } useAuthStore(); return ( QueryClientProvider client{queryClient} BrowserRouter div classNamemin-h-screen bg-gray-50 Routes {/* 公开路由 */} Route path/login element{ isAuthenticated ? Navigate to/dashboard / : Login / } / {/* 受保护的路由 */} Route element{PrivateRoute /} Route element{Layout /} Route path/dashboard element{Dashboard /} / Route path/projects element{ProjectList /} / Route path/projects/:projectId element{ProjectDetail /} / Route path/projects/:projectId/translate/:language element{TranslationEditor /} / /Route /Route {/* 默认重定向 */} Route path/ element{Navigate to/dashboard /} / Route path* element{Navigate to/dashboard /} / /Routes Toaster positiontop-right toastOptions{{ duration: 3000, style: { background: #363636, color: #fff, }, }} / /div /BrowserRouter /QueryClientProvider ); } export default App;10.frontend/src/pages/TranslationEditor.jsximport React, { useState, useEffect } from react; import { useParams, useNavigate } from react-router-dom; import { Save, Sparkles, CheckCircle, AlertCircle, Search, Filter, Download, Upload, ArrowLeft } from lucide-react; import { motion, AnimatePresence } from framer-motion; function TranslationEditor() { const { projectId, language } useParams(); const navigate useNavigate(); const [translations, setTranslations] useState([]); const [selectedKey, setSelectedKey] useState(null); const [editingText, setEditingText] useState(); const [searchQuery, setSearchQuery] useState(); const [filterStatus, setFilterStatus] useState(all); const [loading, setLoading] useState(true); const [aiLoading, setAiLoading] useState(false); // 模拟数据加载 useEffect(() { setTimeout(() { setTranslations([ { id: 1, key: welcome.title, namespace: common, sourceText: Welcome to i18n Manager, translation: { text: , status: pending, isMachineTranslated: false } }, { id: 2, key: welcome.subtitle, namespace: common, sourceText: Manage your translations efficiently, translation: { text: 高效管理您的翻译, status: translated, isMachineTranslated: true, confidence: 0.9 } }, { id: 3, key: button.save, namespace: common, sourceText: Save, translation: { text: 保存, status: approved, isMachineTranslated: false } }, { id: 4, key: button.cancel, namespace: common, sourceText: Cancel, translation: { text: 取消, status: approved, isMachineTranslated: false } }, { id: 5, key: error.required, namespace: validation, sourceText: This field is required, translation: { text: 此字段为必填项, status: reviewed, isMachineTranslated: false } } ]); setLoading(false); }, 500); }, [projectId, language]); const handleSelectTranslation (translation) { setSelectedKey(translation.id); setEditingText(translation.translation?.text || ); }; const handleSaveTranslation () { setTranslations(prev prev.map(t t.id selectedKey ? { ...t, translation: { ...t.translation, text: editingText, status: translated } } : t )); }; const handleAiTranslate async () { setAiLoading(true); // 模拟AI翻译 setTimeout(() { const selected translations.find(t t.id selectedKey); if (selected) { // 简单的模拟翻译 const mockTranslation [AI翻译] ${selected.sourceText}; setEditingText(mockTranslation); } setAiLoading(false); }, 1500); }; const filteredTranslations translations.filter(t { const matchesSearch t.key.toLowerCase().includes(searchQuery.toLowerCase()) || t.sourceText.toLowerCase().includes(searchQuery.toLowerCase()); const matchesFilter filterStatus all || t.translation?.status filterStatus; return matchesSearch matchesFilter; }); const stats { total: translations.length, translated: translations.filter(t t.translation?.status ! pending).length, approved: translations.filter(t t.translation?.status approved).length, pending: translations.filter(t t.translation?.status pending).length }; const progress stats.total 0 ? (stats.translated / stats.total) * 100 : 0; if (loading) { return ( div classNameflex items-center justify-center h-screen div classNameanimate-spin rounded-full h-12 w-12 border-b-2 border-blue-600/div /div ); } return ( div classNameh-screen flex flex-col bg-gray-50 {/* Header */} div classNamebg-white border-b border-gray-200 px-6 py-4 div classNameflex items-center justify-between div classNameflex items-center space-x-4 button onClick{() navigate(/projects/${projectId})} classNamep-2 hover:bg-gray-100 rounded-lg transition-colors ArrowLeft classNamew-5 h-5 text-gray-600 / /button div h1 classNametext-2xl font-bold text-gray-900 Translation Editor /h1 p classNametext-sm text-gray-600 mt-1 Language: span classNamefont-medium{language.toUpperCase()}/span /p /div /div div classNameflex items-center space-x-3 button classNameflex items-center space-x-2 px-4 py-2 text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors Upload classNamew-4 h-4 / spanImport/span /button button classNameflex items-center space-x-2 px-4 py-2 text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors Download classNamew-4 h-4 / spanExport/span /button /div /div {/* Progress Bar */} div classNamemt-4 div classNameflex items-center justify-between text-sm text-gray-600 mb-2 spanTranslation Progress/span span classNamefont-medium{progress.toFixed(1)}%/span /div div classNamew-full bg-gray-200 rounded-full h-2 motion.div classNamebg-blue-600 h-2 rounded-full initial{{ width: 0 }} animate{{ width: ${progress}% }} transition{{ duration: 0.5 }} / /div div classNameflex items-center justify-between mt-2 text-xs text-gray-500 span{stats.translated} / {stats.total} translated/span span{stats.approved} approved/span /div /div /div div classNameflex-1 flex overflow-hidden {/* Translation List */} div classNamew-1/2 border-r border-gray-200 bg-white flex flex-col {/* Search and Filter */} div classNamep-4 border-b border-gray-200 space-y-3 div classNamerelative Search classNameabsolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 / input typetext placeholderSearch translations... value{searchQuery} onChange{(e) setSearchQuery(e.target.value)} classNamew-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent / /div div classNameflex items-center space-x-2 Filter classNamew-4 h-4 text-gray-500 / select value{filterStatus} onChange{(e) setFilterStatus(e.target.value)} classNameflex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent option valueallAll Status/option option valuependingPending/option option valuetranslatedTranslated/option option valuereviewedReviewed/option option valueapprovedApproved/option /select /div /div {/* Translation Items */} div classNameflex-1 overflow-y-auto AnimatePresence {filteredTranslations.map((translation) ( motion.div key{translation.id} initial{{ opacity: 0, y: 20 }} animate{{ opacity: 1, y: 0 }} exit{{ opacity: 0, y: -20 }} onClick{() handleSelectTranslation(translation)} className{p-4 border-b border-gray-200 cursor-pointer transition-colors ${ selectedKey translation.id ? bg-blue-50 border-l-4 border-l-blue-600 : hover:bg-gray-50 }} div classNameflex items-start justify-between mb-2 div classNameflex-1 div classNameflex items-center space-x-2 span classNametext-xs font-mono text-gray-500 bg-gray-100 px-2 py-1 rounded {translation.namespace} /span span classNametext-sm font-medium text-gray-900 {translation.key} /span /div /div {translation.translation?.status approved ( CheckCircle classNamew-5 h-5 text-green-500 flex-shrink-0 / )} {translation.translation?.status pending ( AlertCircle classNamew-5 h-5 text-yellow-500 flex-shrink-0 / )} /div p classNametext-sm text-gray-700 mb-2 {translation.sourceText} /p {translation.translation?.text ( div classNameflex items-start space-x-2 p classNametext-sm text-blue-600 flex-1 {translation.translation.text} /p {translation.translation.isMachineTranslated ( Sparkles classNamew-4 h-4 text-purple-500 flex-shrink-0 / )} /div )} /motion.div ))} /AnimatePresence {filteredTranslations.length 0 ( div classNameflex flex-col items-center justify-center h-64 text-gray-400 Search classNamew-12 h-12 mb-4 / pNo translations found/p /div )} /div /div {/* Editor Panel */} div classNamew-1/2 bg-white flex flex-col {selectedKey ? ( div classNameflex-1 p-6 overflow-y-auto {(() { const selected translations.find(t t.id selectedKey); return ( div classNamemb-6 label classNameblock text-sm font-medium text-gray-700 mb-2 Translation Key /label div classNameflex items-center space-x-2 span classNametext-xs font-mono text-gray-500 bg-gray-100 px-3 py-2 rounded {selected.namespace} /span span classNametext-sm font-medium text-gray-900 bg-gray-100 px-3 py-2 rounded flex-1 {selected.key} /span /div /div div classNamemb-6 label classNameblock text-sm font-medium text-gray-700 mb-2 Source Text (English) /label div classNamep-4 bg-gray-50 rounded-lg border border-gray-200 p classNametext-gray-900{selected.sourceText}/p /div /div div classNamemb-6 div classNameflex items-center justify-between mb-2 label classNameblock text-sm font-medium text-gray-700 Translation ({language.toUpperCase()}) /label button onClick{handleAiTranslate} disabled{aiLoading} classNameflex items-center space-x-2 px-3 py-1.5 text-sm text-purple-600 bg-purple-50 rounded-lg hover:bg-purple-100 transition-colors disabled:opacity-50 Sparkles className{w-4 h-4 ${aiLoading ? animate-spin : }} / span{aiLoading ? Translating... : AI Translate}/span /button /div textarea value{editingText} onChange{(e) setEditingText(e.target.value)} rows{6} classNamew-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none placeholderEnter translation... / /div {selected.translation?.isMachineTranslated ( div classNamemb-6 p-4 bg-purple-50 border border-purple-200 rounded-lg div classNameflex items-start space-x-3 Sparkles classNamew-5 h-5 text-purple-600 flex-shrink-0 mt-0.5 / div p classNametext-sm font-medium text-purple-900 AI-Generated Translation /p p classNametext-sm text-purple-700 mt-1 Confidence: {((selected.translation.confidence || 0) * 100).toFixed(0)}% /p p classNametext-xs text-purple-600 mt-2 Please review and edit if necessary /p /div /div /div )} / ); })()} /div div classNamep-6 border-t border-gray-200 bg-gray-50 div classNameflex items-center justify-between div classNameflex items-center space-x-2 text-sm text-gray-600 spanStatus:/span span className{px-2 py-1 rounded-full text-xs font-medium ${ translations.find(t t.id selectedKey)?.translation?.status approved ? bg-green-100 text-green-800 : translations.find(t t.id selectedKey)?.translation?.status translated ? bg-blue-100 text-blue-800 : bg-yellow-100 text-yellow-800 }} {translations.find(t t.id selectedKey)?.translation?.status || pending} /span /div button onClick{handleSaveTranslation} classNameflex items-center space-x-2 px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors Save classNamew-4 h-4 / spanSave Translation/span /button /div /div / ) : ( div classNameflex-1 flex items-center justify-center text-gray-400 div classNametext-center AlertCircle classNamew-16 h-16 mx-auto mb-4 / p classNametext-lgSelect a translation to edit/p /div /div )} /div /div /div ); } export default TranslationEditor; CSDN博客文章BLOG.md# 从零打造企业级国际化管理平台 i18n Manager ## 项目概述 i18n Manager 是一个现代化的国际化(i18n)管理平台专为产品团队和翻译团队设计提供高效的多语言翻译工作流、AI辅助翻译、版本控制和协作功能。 ### 核心特性 - ✨ **可视化翻译编辑器** - 直观的界面提升翻译效率 - **AI辅助翻译** - 集成OpenAI GPT-4智能翻译建议 - **实时进度追踪** - 可视化翻译进度和统计 - **团队协作** - 多角色权限管理支持协作翻译 - **批量操作** - 批量导入导出批量AI翻译 - **版本控制** - 完整的翻译历史记录 - **智能搜索** - 快速查找和过滤翻译条目 --- ## 技术架构 ### 后端技术栈Node.js Express.js - 服务器框架MongoDB Mongoose - 数据库OpenAI API - AI翻译服务JWT - 身份认证Winston - 日志系统### 前端技术栈React 18 - UI框架Vite - 构建工具TailwindCSS - 样式框架Framer Motion - 动画库React Query - 数据管理Zustand - 状态管理--- ## 核心功能实现 ### 1. 数据模型设计 #### Translation Model javascript const translationSchema new mongoose.Schema({ project: { type: ObjectId, ref: Project, required: true }, key: { type: String, required: true }, namespace: { type: String, default: common }, sourceText: { type: String, required: true }, translations: { type: Map, of: { text: String, status: { type: String, enum: [pending, translated, reviewed, approved] }, translator: { type: ObjectId, ref: User }, isMachineTranslated: Boolean, confidence: Number } }, history: [HistorySchema] });设计亮点使用 Map 类型存储多语言翻译灵活高效完整的状态流转pending → translated → reviewed → approved记录翻译历史支持版本回溯区分人工翻译和机器翻译2. AI翻译服务class AIService { async translate(text, sourceLang, targetLang, context) { const response await this.openai.chat.completions.create({ model: gpt-4-turbo-preview, messages: [ { role: system, content: You are a professional translator... }, { role: user, content: this._buildPrompt(text, sourceLang, targetLang, context) } ], temperature: 0.3 }); return { translation: response.choices[0].message.content, confidence: this._calculateConfidence(response) }; } }核心优化低温度(0.3)确保翻译一致性上下文感知提高翻译质量置信度评估标记需要人工审核的翻译3. 批量翻译优化async batchAiTranslate(req, res) { const translations await Translation.find({ project: projectId, [translations.${language}]: { $exists: false } }).limit(100); const results await Promise.all( translations.map(async (t) { const result await aiService.translate( t.sourceText, project.sourceLanguage, language, t.context ); await t.updateTranslation(language, result.translation, userId, true); return { key: t.key, status: success }; }) ); return results; }性能优化限制批量数量(100条)避免超时并发处理提升速度错误隔离单条失败不影响整体4. 实时进度追踪projectSchema.methods.updateStats async function() { const stats await Translation.aggregate([ { $match: { project: this._id } }, { $group: { _id: null, totalKeys: { $sum: 1 }, translatedKeys: { $sum: { $cond: [{ $ne: [$status, pending] }, 1, 0] } }, reviewedKeys: { $sum: { $cond: [{ $eq: [$status, approved] }, 1, 0] } } } } ]); this.stats stats[0]; await this.save(); };实现要点MongoDB聚合管道高效统计实时更新项目统计信息支持多维度进度展示5. 权限管理系统projectSchema.methods.hasPermission function(userId, requiredRole) { // Owner拥有所有权限 if (this.owner.toString() userId.toString()) return true; // 检查成员角色 const member this.members.find(m m.user.toString() userId.toString() ); if (!member) return false; // 角色层级viewer translator reviewer admin const roleHierarchy { viewer: 1, translator: 2, reviewer: 3, admin: 4 }; return roleHierarchy[member.role] roleHierarchy[requiredRole]; };权限设计4级角色viewer → translator → reviewer → adminOwner拥有最高权限灵活的权限检查机制前端实现亮点1. 翻译编辑器组件function TranslationEditor() { const [translations, setTranslations] useState([]); const [selectedKey, setSelectedKey] useState(null); const [editingText, setEditingText] useState(); const handleAiTranslate async () { setAiLoading(true); const result await api.aiTranslate(selectedKey, language); setEditingText(result.translation); setAiLoading(false); }; return ( div classNameflex {/* 翻译列表 */} TranslationList translations{filteredTranslations} onSelect{handleSelectTranslation} / {/* 编辑面板 */} EditorPanel translation{selectedTranslation} text{editingText} onTextChange{setEditingText} onAiTranslate{handleAiTranslate} onSave{handleSaveTranslation} / /div ); }UI/UX优化双栏布局左侧列表右侧编辑实时搜索和过滤一键AI翻译动画过渡提升体验2. 进度可视化div classNamew-full bg-gray-200 rounded-full h-2 motion.div classNamebg-blue-600 h-2 rounded-full initial{{ width: 0 }} animate{{ width: ${progress}% }} transition{{ duration: 0.5 }} / /div视觉反馈平滑动画展示进度变化多维度统计信息颜色编码状态部署指南Docker部署version: 3.8 services: mongodb: image: mongo:7 ports: - 27017:27017 volumes: - mongo-data:/data/db backend: build: ./backend ports: - 3000:3000 environment: - MONGODB_URImongodb://mongodb:27017/i18n-manager - OPENAI_API_KEY${OPENAI_API_KEY} depends_on: - mongodb frontend: build: ./frontend ports: - 80:80 depends_on: - backend环境变量配置# Backend .env PORT3000 MONGODB_URImongodb://localhost:27017/i18n-manager JWT_SECRETyour-secret-key OPENAI_API_KEYsk-... NODE_ENVproduction # Frontend .env VITE_API_URLhttp://localhost:3000/api性能优化1. 数据库优化// 复合索引 translationSchema.index({ project: 1, key: 1, namespace: 1 }, { unique: true }); // 查询优化 Translation.find(query) .select(key sourceText translations) .lean() // 返回普通对象提升性能 .limit(50);2. API优化// 请求速率限制 const limiter rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); // 响应压缩 app.use(compression()); // 缓存策略 app.use(cache(5 minutes));3. 前端优化// React Query缓存 const queryClient new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, cacheTime: 10 * 60 * 1000 } } }); // 虚拟滚动 import { useVirtualizer } from tanstack/react-virtual;最佳实践1. 翻译工作流1. 创建项目 → 设置源语言和目标语言 2. 导入翻译key → 批量或单个添加 3. AI批量翻译 → 快速生成初稿 4. 人工审核 → 翻译团队精修 5. 审核批准 → 质量把关 6. 导出使用 → 集成到应用2. 团队协作- Owner: 项目创建者完全控制 - Admin: 管理成员审核翻译 - Reviewer: 审核翻译质量 - Translator: 翻译和编辑 - Viewer: 只读访问3. 质量保证- AI翻译 人工审核双保险 - 版本控制可回溯历史 - 翻译记忆保持一致性 - 上下文提示提高准确性扩展功能1. 翻译记忆(TM)class TranslationMemory { async findSimilar(text, language, threshold 0.8) { // 使用向量相似度搜索 const embedding await this.getEmbedding(text); const similar await this.vectorDB.search(embedding, threshold); return similar; } }2. 术语库管理const glossarySchema new mongoose.Schema({ term: String, translations: Map, domain: String, notes: String });3. 质量评分async function scoreTranslation(source, translation) { const result await aiService.checkQuality(source, translation); return { score: result.score, issues: result.issues, suggestions: result.suggestions }; }商业化思路开源 企业版开源版本基础翻译管理单项目支持社区版AI配额企业版无限项目高级AI功能优先技术支持私有部署SLA保证SaaS服务定价方案 - 免费版: 1个项目1000 keys - 专业版: $29/月10个项目无限keys - 团队版: $99/月无限项目高级功能 - 企业版: 定制价格私有部署总结i18n Manager 是一个功能完整、架构清晰的国际化管理平台通过以下特点脱颖而出✅技术先进- 采用最新技术栈性能优异✅用户友好- 直观的界面流畅的体验✅AI赋能- 智能翻译提升效率10倍✅企业级- 完整的权限管理和协作功能✅可扩展- 模块化设计易于定制作者信息DREAMVFIA开源编程大师 V1.1创建者: 王森冉 (SENRAN WANG)组织: DREAMVFIA UNION邮箱: dreamvifauniongmail.com如果这个项目对您有帮助请给个⭐Star支持一下#国际化 #i18n #翻译管理 #AI翻译 #React #Node.js #MongoDB #开源项目--- ## 完整项目文件清单i18n-manager/├── backend/│ ├── src/│ │ ├── config/│ │ │ └── database.js│ │ ├── models/│ │ │ ├── User.model.js ✅│ │ │ ├── Project.model.js ✅│ │ │ └── Translation.model.js ✅│ │ ├── controllers/│ │ │ ├── auth.controller.js│ │ │ ├── project.controller.js│ │ │ └── translation.controller.js ✅│ │ ├── services/│ │ │ └── ai.service.js ✅│ │ ├── routes/│ │ │ ├── auth.routes.js│ │ │ ├── project.routes.js│ │ │ ├── translation.routes.js│ │ │ └── ai.routes.js│ │ ├── middleware/│ │ │ ├── auth.middleware.js│ │ │ └── validation.middleware.js│ │ └── utils/│ │ └── logger.js│ ├── package.json ✅│ └── server.js ✅├── frontend/│ ├── src/│ │ ├── components/│ │ │ ├── Layout.jsx│ │ │ ├── PrivateRoute.jsx│ │ │ └── TranslationList.jsx│ │ ├── pages/│ │ │ ├── Login.jsx│ │ │ ├── Dashboard.jsx│ │ │ ├── ProjectList.jsx│ │ │ ├── ProjectDetail.jsx│ │ │ └── TranslationEditor.jsx ✅│ │ ├── services/│ │ │ └── api.js│ │ ├── stores/│ │ │ └── authStore.js│ │ └── App.jsx ✅│ ├── package.json ✅│ └── vite.config.js├── docker-compose.yml├── README.md└── BLOG.md ✅--- ## 快速开始 ### 1. 克隆项目 bash git clone https://github.com/dreamvfia/i18n-manager.git cd i18n-manager2. 启动后端cd backend npm install cp .env.example .env # 编辑 .env 文件填入必要配置 npm run dev3. 启动前端cd frontend npm install npm run dev4. 访问应用打开浏览器访问http://localhost:5173