升鮮寶生鮮配送供應(yīng)鏈管理系統(tǒng),輔助開發(fā)工具,《多語言自動翻譯與導(dǎo)出工具(WinForms版)》開發(fā)文檔 及 阿里云機器翻譯,數(shù)據(jù)庫Mysql .net 全部源代碼
首先展示一下工具的界面

多語言自動翻譯與導(dǎo)出工具(WinForms版)開發(fā)文檔
一、系統(tǒng)簡介
本工具是一款用于自動翻譯多語言字段并導(dǎo)出國際化數(shù)據(jù)的桌面應(yīng)用,支持從 MySQL 數(shù)據(jù)庫讀取中文內(nèi)容,調(diào)用阿里云機器翻譯 API 自動生成 繁體中文 (zh-TW)、英文 (en-US)、日文 (ja-JP) 等多語言版本,并將結(jié)果同步回數(shù)據(jù)庫或?qū)С鰹?Excel 文件。
二、功能結(jié)構(gòu)
數(shù)據(jù)庫配置區(qū):輸入 MySQL 服務(wù)器地址、端口、數(shù)據(jù)庫名、用戶名、密碼,并可測試連接與保存配置。
阿里云配置區(qū):輸入 AccessKeyId 與 AccessKeySecret,選擇需要翻譯的目標(biāo)語言(支持多選)。
翻譯控制區(qū):一鍵開始多線程翻譯、支持中斷與進度顯示、自動限流與斷點緩存。
導(dǎo)出區(qū):將數(shù)據(jù)庫中的多語言結(jié)果導(dǎo)出為 Excel 文件(支持自定義保存路徑與打開文件夾)。
緩存機制:所有翻譯結(jié)果寫入本地 cache.json,下次啟動自動跳過已翻譯內(nèi)容。
三、界面設(shè)計
主界面包含兩個主要分組:數(shù)據(jù)庫配置區(qū)與阿里云配置區(qū)。前者用于設(shè)置 MySQL 連接信息,后者用于輸入阿里云 API 憑證、勾選目標(biāo)語言、啟動翻譯與導(dǎo)出操作。
四、核心邏輯架構(gòu)
系統(tǒng)包含 MainForm 主窗體、LangRecord 數(shù)據(jù)實體、TranslateWorker 線程池調(diào)度器、CacheManager 緩存管理器與 ExcelExporter 導(dǎo)出模塊。其中 MainForm 提供圖形界面與交互邏輯,TranslateWorker 負(fù)責(zé)并發(fā)任務(wù)分發(fā)。
五、數(shù)據(jù)庫結(jié)構(gòu)
sys_language 表字段包括表名、主鍵ID、字段名、字段值、語言等,主鍵為(table_name, table_id, field_name, language)。
六、核心功能說明
1. 數(shù)據(jù)庫連接測試:檢測服務(wù)器可用性并提示詳細(xì)錯誤。
2. 翻譯邏輯:基于 AlibabaCloud.SDK.Alimt20181012 官方
SDK 調(diào)用阿里云機器翻譯服務(wù)。
3. 只翻譯缺失語言:自動跳過已有翻譯值的字段,節(jié)省調(diào)用次數(shù)。
4. Excel 導(dǎo)出:支持選擇保存路徑、防止 BigInt 精度丟失、自動樣式化輸出。
5. 緩存機制:使用 cache.json 文件緩存已翻譯文本以避免重復(fù)調(diào)用。
七、依賴庫
MySql.Data 6.9.12 - MySQL 8.0 連接庫
EPPlus 4.5.3.3 - Excel 導(dǎo)出
AlibabaCloud.SDK.Alimt20181012 - 翻譯 SDK
Newtonsoft.Json - JSON 緩存文件讀寫
八、性能優(yōu)化
使用多線程并發(fā)翻譯、自動限流機制與緩存避免重復(fù)調(diào)用,分頁查詢減少內(nèi)存壓力。
九、常見問題
400 錯誤請求:語言參數(shù)錯誤
403 無權(quán)限:AccessKey 無效或未開通服務(wù)
精度丟失:Excel 科學(xué)計數(shù)法,已通過字符串格式解決
十、部署與運行
1. 安裝依賴包(MySql.Data、EPPlus、AlibabaCloud.SDK.Alimt20181012
等)
2. 編譯并運行程序。
3. 填寫數(shù)據(jù)庫與阿里云配置,測試連接后開始翻譯。
4. 翻譯完成后點擊“導(dǎo)出Excel”。
十一、未來擴展方向
可擴展支持更多語言、批量翻譯多個表、導(dǎo)入Excel回寫、集成DeepL等翻譯服務(wù)。
主要的C#源代碼:
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using OfficeOpenXml;
using Org.BouncyCastle.Asn1.Cmp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Web.Script.Serialization;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace AliyunTranslator40
{
public partial class MainForm : Form
{
private string host = "", port = "3306", database = "", user = "", password = "";
private string AccessKeyId = "", AccessKeySecret = "";
private const string ConfigFile = "config.json";
private const string CacheFile = "cache.json";
private string ConnStr = "";
private volatile bool stopRequested = false;
private Dictionary<string, string> cache = new Dictionary<string, string>();
private int total = 0, done = 0;
private Semaphore sema = new Semaphore(4, 4);
private Stopwatch sw = new Stopwatch();
public MainForm()
{
InitializeComponent();
LoadConfig();
}
#region 配置管理
private void btnSaveDb_Click(object sender, EventArgs e)
{
host = txtHost.Text.Trim();
port = txtPort.Text.Trim();
database = txtDb.Text.Trim();
user = txtUser.Text.Trim();
password = txtPwd.Text.Trim();
BuildConnStr();
SaveConfig();
Log("? 數(shù)據(jù)庫配置已保存。");
}
private void btnSaveKey_Click(object sender, EventArgs e)
{
AccessKeyId = txtKeyId.Text.Trim();
AccessKeySecret = txtKeySecret.Text.Trim();
SaveConfig();
Log("? 阿里云密鑰已保存。");
}
private void BuildConnStr()
{
ConnStr = $"Server={host};Port={port};Database={database};Uid={user};Pwd={password};Charset=utf8mb4;SslMode=None;";
}
private void LoadConfig()
{
if (!File.Exists(ConfigFile)) return;
var js = new JavaScriptSerializer();
var cfg = js.Deserialize<Dictionary<string, string>>(File.ReadAllText(ConfigFile));
if (cfg == null) return;
host = cfg.ContainsKey("Host") ? cfg["Host"] : "";
port = cfg.ContainsKey("Port") ? cfg["Port"] : "3306";
database = cfg.ContainsKey("Database") ? cfg["Database"] : "";
user = cfg.ContainsKey("User") ? cfg["User"] : "";
password = cfg.ContainsKey("Password") ? cfg["Password"] : "";
AccessKeyId = cfg.ContainsKey("AccessKeyId") ? cfg["AccessKeyId"] : "";
AccessKeySecret = cfg.ContainsKey("AccessKeySecret") ? cfg["AccessKeySecret"] : "";
txtHost.Text = host;
txtPort.Text = port;
txtDb.Text = database;
txtUser.Text = user;
txtPwd.Text = password;
txtKeyId.Text = AccessKeyId;
txtKeySecret.Text = AccessKeySecret;
BuildConnStr();
Log("? 已加載配置。");
}
private void SaveConfig()
{
var js = new JavaScriptSerializer();
var cfg = new Dictionary<string, string>
{
{"Host", host}, {"Port", port}, {"Database", database},
{"User", user}, {"Password", password},
{"AccessKeyId", AccessKeyId}, {"AccessKeySecret", AccessKeySecret}
};
File.WriteAllText(ConfigFile, js.Serialize(cfg));
}
#endregion
#region 翻譯流程
private void btnStart_Click(object sender, EventArgs e)
{
stopRequested = false;
ThreadPool.QueueUserWorkItem(_ => RunTranslate());
}
private void btnStop_Click(object sender, EventArgs e)
{
stopRequested = true;
Log("?? 已請求停止。");
}
private void RunTranslate()
{
try
{
LoadCache();
List<LangRecord> list = new List<LangRecord>();
UpdateStatus("正在讀取數(shù)據(jù)...");
using (MySqlConnection conn = new MySqlConnection(ConnStr))
{
conn.Open();
var cmd = new MySqlCommand("SELECT table_name, table_id, field_name, field_value FROM sys_language WHERE language='zh-CN';", conn);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
list.Add(new LangRecord
{
TableName = reader["table_name"].ToString(),
TableId = Convert.ToInt64(reader["table_id"]),
FieldName = reader["field_name"].ToString(),
FieldValue = reader["field_value"].ToString()
});
}
reader.Close();
}
total = list.Count;
done = 0;
sw.Restart();
UpdateStatus($"共 {total} 條數(shù)據(jù),開始翻譯...");
foreach (var rec in list)
{
if (stopRequested) break;
sema.WaitOne();
ThreadPool.QueueUserWorkItem(state => ProcessRecord(rec));
}
while (done < total && !stopRequested)
Thread.Sleep(500);
SaveCache();
ExportToExcel();
UpdateStatus("? 翻譯完成。");
}
catch (Exception ex)
{
Log("? 翻譯錯誤:" + ex.Message);
}
}
private void ProcessRecord(LangRecord rec)
{
try
{
if (stopRequested) return;
string cn = rec.FieldValue.Trim();
if (chkEnUs.Checked && !HasTranslation(rec, "en-US"))
SaveTranslation(rec, "en-US", TranslateAliyun(cn, "zh", "en"));
if (chkZhTw.Checked && !HasTranslation(rec, "zh-TW"))
SaveTranslation(rec, "zh-TW", TranslateAliyun(cn, "zh", "zh-tw"));
if (chkJaJp.Checked && !HasTranslation(rec, "ja-JP"))
SaveTranslation(rec, "ja-JP", TranslateAliyun(cn, "zh", "ja"));
}
catch (Exception ex)
{
Log("?? 單條失敗: " + ex.Message);
}
finally
{
Interlocked.Increment(ref done);
UpdateProgress();
sema.Release();
}
}
#endregion
#region 阿里云簽名翻譯
private string TranslateAliyun(string text, string from, string to)
{
if (string.IsNullOrWhiteSpace(text))
return text;
if (string.IsNullOrWhiteSpace(AccessKeyId) || string.IsNullOrWhiteSpace(AccessKeySecret))
{
Log("?? 阿里云AccessKey未配置");
return text;
}
try
{
string endpoint = "https://mt.aliyuncs.com";
string version = "2018-10-12";
string action = "TranslateGeneral";
// 構(gòu)建參數(shù)(按字母順序排序,這是阿里云的要求)
var parameters = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
["AccessKeyId"] = AccessKeyId,
["Action"] = action,
["Format"] = "JSON",
["FormatType"] = "text",
["Scene"] = "general",
["SignatureMethod"] = "HMAC-SHA1",
["SignatureNonce"] = Guid.NewGuid().ToString(),
["SignatureVersion"] = "1.0",
["SourceLanguage"] = from,
["SourceText"] = text,
["TargetLanguage"] = to,
["Timestamp"] = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
["Version"] = version
};
// 構(gòu)建查詢字符串
string queryString = BuildQueryString(parameters);
// 計算簽名
string signature = CalculateSignature("GET", parameters);
// 構(gòu)建最終URL
string finalUrl = $"{endpoint}/?{queryString}&Signature={Uri.EscapeDataString(signature)}";
return ExecuteTranslationRequest(finalUrl, text, from, to);
}
catch (Exception ex)
{
Log($"?? 翻譯異常: {ex.Message}");
return text;
}
}
/// <summary>
/// 構(gòu)建查詢字符串(URL編碼)
/// </summary>
private string BuildQueryString(SortedDictionary<string, string> parameters)
{
var encodedParams = parameters.Select(p =>
$"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}");
return string.Join("&", encodedParams);
}
/// <summary>
/// 計算阿里云簽名
/// </summary>
private string CalculateSignature(string method, SortedDictionary<string, string> parameters)
{
// 1. 構(gòu)建規(guī)范化查詢字符串
string canonicalizedQueryString = BuildQueryString(parameters);
// 2. 構(gòu)建待簽名字符串
string stringToSign = $"{method}&{Uri.EscapeDataString("/")}&{Uri.EscapeDataString(canonicalizedQueryString)}";
// 3. 計算HMAC-SHA1簽名
string key = $"{AccessKeySecret}&";
using (var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(key)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
return Convert.ToBase64String(hash);
}
}
/// <summary>
/// 執(zhí)行翻譯請求并解析結(jié)果
/// </summary>
private string ExecuteTranslationRequest(string url, string originalText, string from, string to)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.Timeout = 15000; // 15秒超時
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
{
string responseText = reader.ReadToEnd();
if (response.StatusCode != HttpStatusCode.OK)
{
Log($"?? API返回錯誤狀態(tài)碼: {(int)response.StatusCode}");
return originalText;
}
return ParseTranslationResult(responseText, originalText, from, to, JsonDocument.Parse(responseText));
}
}
/// <summary>
/// 解析翻譯結(jié)果
/// </summary>
private string ParseTranslationResult(string jsonResponse, string originalText, string from, string to, JsonDocument doc)
{
try
{
JsonElement root = doc.RootElement;
// 檢查是否有錯誤
if (root.TryGetProperty("Code", out JsonElement codeElement))
{
string errorCode = codeElement.GetString();
if (!string.IsNullOrEmpty(errorCode) && errorCode != "200")
{
string errorMessage = root.TryGetProperty("Message", out JsonElement messageElement)
? messageElement.GetString()
: "未知錯誤";
Log($"?? 翻譯API錯誤: {errorCode} - {errorMessage}");
return originalText;
}
}
// 提取翻譯結(jié)果
if (root.TryGetProperty("Data", out JsonElement dataElement) &&
dataElement.TryGetProperty("Translated", out JsonElement translatedElement))
{
string translatedText = translatedElement.GetString();
if (!string.IsNullOrEmpty(translatedText))
{
Log($"?? [{from}->{to}] {originalText} => {translatedText}");
return translatedText;
}
}
Log($"?? 無法解析翻譯結(jié)果: {jsonResponse}");
return originalText;
}
catch (Newtonsoft.Json.JsonException ex)
{
Log($"?? JSON解析失敗: {ex.Message}");
return originalText;
}
}
#endregion
#region 數(shù)據(jù)保存與導(dǎo)出
private void SaveTranslation(LangRecord rec, string lang, string val)
{
using (MySqlConnection conn = new MySqlConnection(ConnStr))
{
conn.Open();
string sql = "INSERT INTO sys_language (table_name, table_id, field_name, language, field_value) VALUES (@t,@id,@f,@lang,@val) ON DUPLICATE KEY UPDATE field_value=VALUES(field_value)";
MySqlCommand cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@t", rec.TableName);
cmd.Parameters.AddWithValue("@id", rec.TableId);
cmd.Parameters.AddWithValue("@f", rec.FieldName);
cmd.Parameters.AddWithValue("@lang", lang);
cmd.Parameters.AddWithValue("@val", val);
cmd.ExecuteNonQuery();
}
}
private void btnTestDb_Click(object sender, EventArgs e)
{
try
{
// 獲取輸入
host = txtHost.Text.Trim();
port = txtPort.Text.Trim();
database = txtDb.Text.Trim();
user = txtUser.Text.Trim();
password = txtPwd.Text.Trim();
BuildConnStr();
// 檢查輸入有效性
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(database) || string.IsNullOrEmpty(user))
{
MessageBox.Show("請輸入完整的數(shù)據(jù)庫連接信息(服務(wù)器、數(shù)據(jù)庫、用戶名)!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 顯示測試中狀態(tài)
lblStatus.Text = "狀態(tài): 正在連接數(shù)據(jù)庫...";
lblStatus.Refresh();
DateTime start = DateTime.Now;
using (var conn = new MySqlConnection(ConnStr))
{
conn.Open();
MySqlCommand cmd = new MySqlCommand("SELECT VERSION();", conn);
string version = Convert.ToString(cmd.ExecuteScalar());
TimeSpan elapsed = DateTime.Now - start;
MessageBox.Show(
$"? 數(shù)據(jù)庫連接成功!\n\n服務(wù)器: {host}\n數(shù)據(jù)庫: {database}\n版本: {version}\n耗時: {elapsed.TotalMilliseconds:F0} ms",
"連接成功",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
Log($"? 成功連接數(shù)據(jù)庫 [{database}] (版本: {version}),耗時 {elapsed.TotalMilliseconds:F0} ms");
lblStatus.Text = "狀態(tài): 數(shù)據(jù)庫連接成功 ?";
}
}
catch (MySqlException ex)
{
string msg;
switch (ex.Number)
{
case 1045: msg = "用戶名或密碼錯誤"; break;
case 1042: msg = "無法連接到指定主機"; break;
case 1049: msg = "數(shù)據(jù)庫不存在"; break;
default: msg = "MySQL 錯誤: " + ex.Message; break;
}
MessageBox.Show($"? 連接失敗: {msg}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
Log("? 連接失敗: " + msg);
lblStatus.Text = "狀態(tài): 連接失敗 ?";
}
catch (Exception ex)
{
MessageBox.Show($"? 未知錯誤: {ex.Message}", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
Log("? 未知錯誤: " + ex.Message);
lblStatus.Text = "狀態(tài): 連接異常 ?";
}
}
private void btnExport_Click(object sender, EventArgs e)
{
ExportToExcel();
}
private void ExportToExcel()
{
try
{
UpdateStatus("正在準(zhǔn)備導(dǎo)出 Excel...");
// 1?? 彈出保存文件對話框
SaveFileDialog sfd = new SaveFileDialog();
sfd.Title = "選擇導(dǎo)出路徑";
sfd.Filter = "Excel 文件 (*.xlsx)|*.xlsx";
sfd.FileName = $"translate_result_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
sfd.InitialDirectory = AppDomain.CurrentDomain.BaseDirectory;
if (sfd.ShowDialog() != DialogResult.OK)
{
Log("?? 用戶取消導(dǎo)出。");
UpdateStatus("已取消導(dǎo)出");
return;
}
string file = sfd.FileName;
Log("?? 導(dǎo)出路徑:" + file);
UpdateStatus("正在導(dǎo)出 Excel,請稍候...");
// 2?? 創(chuàng)建 Excel
using (var pkg = new OfficeOpenXml.ExcelPackage())
{
var ws = pkg.Workbook.Worksheets.Add("Translations");
// 表頭
string[] headers = { "表名", "主鍵ID", "字段名", "中文(zh-CN)", "繁體(zh-TW)", "英文(en-US)", "日文(ja-JP)" };
for (int i = 0; i < headers.Length; i++)
{
ws.Cells[1, i + 1].Value = headers[i];
ws.Cells[1, i + 1].Style.Font.Bold = true;
ws.Cells[1, i + 1].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);
ws.Cells[1, i + 1].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
}
// 3?? 查詢數(shù)據(jù)并寫入
using (MySqlConnection conn = new MySqlConnection(ConnStr))
{
conn.Open();
string sql = @"
SELECT
table_name,
table_id,
field_name,
MAX(CASE WHEN language='zh-CN' THEN field_value END) AS zhCN,
MAX(CASE WHEN language='zh-TW' THEN field_value END) AS zhTW,
MAX(CASE WHEN language='en-US' THEN field_value END) AS enUS,
MAX(CASE WHEN language='ja-JP' THEN field_value END) AS jaJP
FROM sys_language
GROUP BY table_name, table_id, field_name
ORDER BY table_name, table_id, field_name;";
MySqlCommand cmd = new MySqlCommand(sql, conn);
var reader = cmd.ExecuteReader();
int row = 2;
while (reader.Read())
{
string tableName = Convert.ToString(reader["table_name"]);
string tableId = Convert.ToString(reader["table_id"]); // ?? BigInt → string
string fieldName = Convert.ToString(reader["field_name"]);
ws.Cells[row, 1].Value = tableName;
ws.Cells[row, 2].Value = tableId;
ws.Cells[row, 3].Value = fieldName;
ws.Cells[row, 4].Value = Convert.ToString(reader["zhCN"]);
ws.Cells[row, 5].Value = Convert.ToString(reader["zhTW"]);
ws.Cells[row, 6].Value = Convert.ToString(reader["enUS"]);
ws.Cells[row, 7].Value = Convert.ToString(reader["jaJP"]);
// 設(shè)置 ID 列為文本格式(防止科學(xué)計數(shù)法)
ws.Cells[row, 2].Style.Numberformat.Format = "@";
row++;
}
reader.Close();
}
// 4?? 自動列寬 + 邊框
ws.Cells.AutoFitColumns();
var range = ws.Cells[1, 1, ws.Dimension.End.Row, ws.Dimension.End.Column];
range.Style.Border.Top.Style = OfficeOpenXml.Style.ExcelBorderStyle.Thin;
range.Style.Border.Left.Style = OfficeOpenXml.Style.ExcelBorderStyle.Thin;
range.Style.Border.Right.Style = OfficeOpenXml.Style.ExcelBorderStyle.Thin;
range.Style.Border.Bottom.Style = OfficeOpenXml.Style.ExcelBorderStyle.Thin;
// 5?? 保存文件
pkg.SaveAs(new FileInfo(file));
}
Log("? Excel 導(dǎo)出完成:" + file);
UpdateStatus("? Excel 導(dǎo)出完成");
MessageBox.Show($"? 導(dǎo)出成功!\n文件已保存至:\n{file}", "導(dǎo)出成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 6?? 詢問是否打開文件夾
if (MessageBox.Show("是否打開文件所在文件夾?", "完成", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
try
{
System.Diagnostics.Process.Start("explorer.exe", "/select,\"" + file + "\"");
}
catch { }
}
}
catch (Exception ex)
{
Log("? 導(dǎo)出失敗: " + ex.Message);
MessageBox.Show("? 導(dǎo)出失敗: " + ex.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
UpdateStatus("? 導(dǎo)出失敗");
}
}
#endregion
#region UI 控制
private void UpdateProgress()
{
if (InvokeRequired) { Invoke(new Action(UpdateProgress)); return; }
double pct = total > 0 ? done * 100.0 / total : 0;
progressBar.Value = (int)Math.Min(100, pct);
lblStatus.Text = $"進度: {pct:F1}% ({done}/{total})";
}
private void UpdateStatus(string msg)
{
if (InvokeRequired) { Invoke(new Action<string>(UpdateStatus), msg); return; }
lblStatus.Text = "狀態(tài): " + msg;
}
private void Log(string msg)
{
if (InvokeRequired) { Invoke(new Action<string>(Log), msg); return; }
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {msg}\r\n");
}
private void LoadCache()
{
if (File.Exists(CacheFile))
{
var js = new JavaScriptSerializer();
cache = js.Deserialize<Dictionary<string, string>>(File.ReadAllText(CacheFile));
if (cache == null) cache = new Dictionary<string, string>();
Log($"?? 加載緩存:{cache.Count} 條");
}
}
private void SaveCache()
{
var js = new JavaScriptSerializer();
File.WriteAllText(CacheFile, js.Serialize(cache));
Log($"?? 保存緩存:{cache.Count} 條");
}
#endregion
private bool HasTranslation(LangRecord rec, string lang)
{
try
{
using (MySqlConnection conn = new MySqlConnection(ConnStr))
{
conn.Open();
string sql = "SELECT COUNT(*) FROM sys_language WHERE table_name=@t AND table_id=@id AND field_name=@f AND language=@lang AND field_value IS NOT NULL AND field_value <> '';";
MySqlCommand cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@t", rec.TableName);
cmd.Parameters.AddWithValue("@id", rec.TableId);
cmd.Parameters.AddWithValue("@f", rec.FieldName);
cmd.Parameters.AddWithValue("@lang", lang);
object count = cmd.ExecuteScalar();
return Convert.ToInt32(count) > 0;
}
}
catch (Exception ex)
{
Log("?? 檢查翻譯狀態(tài)出錯:" + ex.Message);
return false;
}
}
}
public class LangRecord
{
public string TableName;
public long TableId;
public string FieldName;
public string FieldValue;
}
}
升鮮寶多語言翻譯工具V1.0--使用幫助文檔
多語言自動翻譯與導(dǎo)出工具 使用幫助文檔
一、軟件簡介
本軟件用于自動翻譯系統(tǒng)中 sys_language 表的多語言字段,支持將中文內(nèi)容批量翻譯為繁體中文、英文、日文,并自動寫入數(shù)據(jù)庫或?qū)С?Excel。適合用于多語言網(wǎng)站、供應(yīng)鏈管理系統(tǒng)、零售POS、多租戶SaaS 等項目中批量國際化場景。
二、軟件界面說明
主界面分為數(shù)據(jù)庫配置區(qū)、阿里云配置區(qū)、操作區(qū)和日志區(qū)。
數(shù)據(jù)庫配置區(qū):輸入 MySQL 連接信息。
阿里云配置區(qū):輸入 AccessKeyId 與 AccessKeySecret 并勾選翻譯目標(biāo)語言。
操作區(qū):包含測試連接、保存配置、開始翻譯、停止和導(dǎo)出Excel按鈕。
日志區(qū):顯示實時進度與狀態(tài)。
三、安裝與運行
系統(tǒng)要求:Windows 7/10/11,.NET Framework 4.0,MySQL 8.0。
步驟:
1. 啟動程序 MultiLangTranslator.exe。
2. 輸入數(shù)據(jù)庫信息并點擊“測試連接”。
3. 填寫阿里云 AccessKey 并勾選語言。
4. 點擊“開始翻譯”執(zhí)行任務(wù)。
5. 完成后點擊“導(dǎo)出Excel”生成文件。
四、翻譯功能說明
從 sys_language 表讀取中文(zh-CN)字段,通過阿里云 API 翻譯為 zh-TW、en-US、ja-JP。支持多線程并發(fā)、自動限流(429時降速)和緩存機制。只翻譯缺失語言功能可跳過已有翻譯記錄。
五、Excel 導(dǎo)出功能
點擊“導(dǎo)出Excel”后選擇保存路徑,生成包含中、繁、英、日的多語言對照表。
BigInt 主鍵轉(zhuǎn)為字符串避免科學(xué)計數(shù)法問題。
導(dǎo)出完成后可一鍵打開文件所在目錄。
六、日志與進度
日志區(qū)實時顯示連接信息、翻譯進度和錯誤提示。
狀態(tài)欄顯示當(dāng)前任務(wù)階段:連接中、翻譯中、導(dǎo)出中、完成。
七、配置文件
配置文件 config.json 自動保存數(shù)據(jù)庫和阿里云憑證。
{
'Host': '127.0.0.1',
'Port': '3306',
'Database': 'sxbscm',
'User': 'root',
'Password': '123456',
'AccessKeyId': 'your_key',
'AccessKeySecret': 'your_secret'
}
八、常見問題
連接失敗:檢查數(shù)據(jù)庫網(wǎng)絡(luò)或密碼。
400錯誤請求:檢查翻譯參數(shù)語言代碼。
429限流:阿里云請求過多,系統(tǒng)會自動降低并發(fā)。
Excel精度丟失:程序已將BigInt強制為字符串。
九、安全提示
妥善保管 AccessKeyId 與 AccessKeySecret。
建議使用權(quán)限受限的阿里云子賬號。
程序僅調(diào)用阿里云翻譯接口,不上傳數(shù)據(jù)庫內(nèi)容。
十、版本更新
v1.0:初版發(fā)布,支持中/繁/英/日翻譯與導(dǎo)出。
v1.1(規(guī)劃):新增進度條、DeepL支持、任務(wù)暫停恢復(fù)。
十一、聯(lián)系與定制
如需擴展功能(新增語言、支持DeepL或Google翻譯、企業(yè)私有部署),請聯(lián)系系統(tǒng)開發(fā)團隊(升鮮寶 余東升 微信:sxbscm2012) 進行二次定制。

浙公網(wǎng)安備 33010602011771號