using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using LocalizationTool.Models;
using LocalizationTool.Services;
namespace LocalizationTool.ViewModels
{
public partial class MainViewModel
{
private void TranslateSelected()
{
var langs = _selectedLangs.Count > 0 ? _selectedLangs : _targets;
if (langs.Count == 0) { MessageBox.Show("没有可翻译的语言。"); return; }
TranslateManyAsync(langs);
}
private void TranslateAll()
{
if (_targets.Count == 0) return;
TranslateManyAsync(_targets);
}
/// 翻译指定语言列表中各语言的缺译项。
private async void TranslateManyAsync(IEnumerable langs)
{
if (!Translation.Provider.IsConfigured)
{
MessageBox.Show("请先在“翻译设置”填写 API 配置。");
return;
}
foreach (var lang in langs)
await TranslateLangAsync(lang);
}
/// 翻译单个语言的缺译(百度走批量,其余逐条)。
private async Task TranslateLangAsync(PackFile lang)
{
var missing = _rows
.Select(r => new { r, cell = r.Cells.TryGetValue(lang.LanguageCode, out var c) ? c : null })
.Where(x => x.cell != null && x.cell.IsMissing)
.ToList();
if (missing.Count == 0) { MessageBox.Show($"{lang.LanguageCode} 没有缺译项。"); return; }
SetTranslating(true, missing.Count);
int done = 0, fail = 0;
var errors = new List();
var baidu = Translation.Provider as BaiduTranslateProvider;
try
{
if (baidu != null)
{
for (int i = 0; i < missing.Count; i += BaiduTranslateProvider.BatchSize)
{
var batchRows = missing.Skip(i).Take(BaiduTranslateProvider.BatchSize)
.Select(x => x.r).ToList();
var (d, f) = await TranslateBatchWithFallbackAsync(baidu, lang, batchRows, errors);
done += d; fail += f;
ProgressValue = Math.Min(done + fail, missing.Count);
}
}
else
{
foreach (var x in missing)
{
try
{
var t = await Translation.Provider.TranslateAsync(x.r.Key, _cfg.Translation.FromLang, lang.LanguageCode);
x.cell.Value = t;
lang.Translations[x.r.Key] = t;
done++;
}
catch (Exception ex)
{
fail++;
errors.Add($"[第{x.r.Index}行] {x.r.Key}:{ex.Message}");
}
ProgressValue = done + fail;
}
}
PackRepository.Save(lang);
MessageBox.Show(AppendErrorDetail(
$"{lang.LanguageCode} 自动翻译完成:成功 {done},失败 {fail}。已保存。", errors));
}
finally
{
SetTranslating(false, 0);
RefreshMissing();
}
}
/// 仅翻译当前视图中的缺译(受搜索过滤影响),绝不覆盖已有译文。
private async void TranslateMissing()
{
if (_targets.Count == 0) { MessageBox.Show("请先加载语言包。"); return; }
if (!Translation.Provider.IsConfigured)
{
MessageBox.Show("请先在“翻译设置”填写 API 配置。");
return;
}
int totalMissing = _rows.Sum(r => r.MissingCount);
if (totalMissing == 0)
{
MessageBox.Show("当前视图没有缺译项。(提示:可点“清除”恢复全部行,或用搜索定位后再试)");
return;
}
var scope = _rows.ToList();
SetTranslating(true, totalMissing);
int totalDone = 0, totalFail = 0;
var errors = new List();
var baidu = Translation.Provider as BaiduTranslateProvider;
try
{
foreach (var lang in _targets)
{
var missing = scope
.Select(r => new { r, cell = r.Cells.TryGetValue(lang.LanguageCode, out var c) ? c : null })
.Where(x => x.cell != null && x.cell.IsMissing)
.ToList();
if (missing.Count == 0) continue;
if (baidu != null)
{
for (int i = 0; i < missing.Count; i += BaiduTranslateProvider.BatchSize)
{
var batchRows = missing.Skip(i).Take(BaiduTranslateProvider.BatchSize)
.Select(x => x.r).ToList();
var (d, f) = await TranslateBatchWithFallbackAsync(baidu, lang, batchRows, errors);
totalDone += d; totalFail += f;
ProgressValue = Math.Min(totalDone + totalFail, totalMissing);
}
}
else
{
foreach (var x in missing)
{
try
{
var t = await Translation.Provider.TranslateAsync(
x.r.Key, _cfg.Translation.FromLang, lang.LanguageCode);
x.cell.Value = t;
lang.Translations[x.r.Key] = t;
totalDone++;
}
catch (Exception ex)
{
totalFail++;
errors.Add($"[{lang.LanguageCode}][第{x.r.Index}行] {x.r.Key}:{ex.Message}");
}
ProgressValue = Math.Min(totalDone + totalFail, totalMissing);
}
}
}
foreach (var lang in _targets) PackRepository.Save(lang);
MessageBox.Show(AppendErrorDetail(
$"仅翻译缺译完成:成功 {totalDone},失败 {totalFail}。已保存全部语言包。", errors));
}
finally
{
SetTranslating(false, 0);
RefreshMissing();
}
}
///
/// 批量翻译一批词条(百度批量接口)。整批被拒时(内容安全 20003 / 限流 54003)
/// 自动降级逐条:好行照常填入,坏行记入失败明细,不拖累整批。
///
private async Task<(int done, int fail)> TranslateBatchWithFallbackAsync(
BaiduTranslateProvider baidu, PackFile lang, List rows, List errors)
{
int done = 0, fail = 0;
var cells = rows
.Select(r => new { r, cell = r.Cells.TryGetValue(lang.LanguageCode, out var c) ? c : null })
.Where(x => x.cell != null)
.ToList();
if (cells.Count == 0) return (0, 0);
try
{
var results = await baidu.TranslateBatchAsync(
cells.Select(x => x.r.Key).ToList(),
_cfg.Translation.FromLang,
lang.LanguageCode);
for (int k = 0; k < cells.Count && k < results.Count; k++)
{
if (!string.IsNullOrWhiteSpace(results[k]))
{
cells[k].cell.Value = results[k];
lang.Translations[cells[k].r.Key] = results[k];
done++;
}
else fail++;
}
return (done, fail);
}
catch (Exception)
{
// 整批被拒 → 走降级逐条
}
foreach (var x in cells)
{
try
{
await Task.Delay(200); // 逐条限速,避免 54003 QPS 超限
var one = await baidu.TranslateAsync(
x.r.Key, _cfg.Translation.FromLang, lang.LanguageCode);
if (!string.IsNullOrWhiteSpace(one))
{
x.cell.Value = one;
lang.Translations[x.r.Key] = one;
done++;
}
else fail++;
}
catch (Exception ex)
{
fail++;
errors.Add($"[第{x.r.Index}行] {x.r.Key}:{ex.Message}");
}
}
return (done, fail);
}
/// 翻译当前选中的单行(右键菜单):逐语言翻译缺译单元格,写回数据源并保存。
private async void TranslateRow(RowVm row)
{
if (row == null || string.IsNullOrEmpty(row.Key)) return;
if (!Translation.Provider.IsConfigured)
{
MessageBox.Show("请先在翻译设置中配置 API Key", "提示");
return;
}
int done = 0, fail = 0;
var errors = new List();
try
{
IsTranslating = true;
Stats = $"正在翻译: {row.Key}...";
var provider = Translation.Provider;
foreach (var lang in _targets)
{
if (!row.Cells.TryGetValue(lang.LanguageCode, out var cell) || cell == null) continue;
if (!string.IsNullOrEmpty(cell.Value)) continue;
try
{
var t = await provider.TranslateAsync(row.Key, _cfg.Translation.FromLang, lang.LanguageCode);
cell.Value = t;
lang.Translations[row.Key] = t; // 写回数据源,保存才不丢
done++;
}
catch (Exception ex)
{
fail++;
errors.Add($"[{lang.LanguageCode}] {row.Key}:{ex.Message}");
}
}
foreach (var lang in _targets) PackRepository.Save(lang);
row.Touch();
Stats = AppendErrorDetail(
$"✅ 翻译完成: {row.Key}(成功 {done},失败 {fail},已保存)", errors);
}
catch (Exception ex)
{
MessageBox.Show($"翻译失败:{ex.Message}", "错误");
Stats = "翻译失败";
}
finally
{
IsTranslating = false;
}
}
private void SetTranslating(bool on, double max)
{
IsTranslating = on;
ProgressMax = Math.Max(max, 1);
ProgressValue = 0;
}
private static string AppendErrorDetail(string message, List errors)
{
if (errors == null || errors.Count == 0) return message;
var detail = string.Join("\n", errors.Take(10));
var more = errors.Count > 10 ? $"\n… 等共 {errors.Count} 条失败明细" : "";
return $"{message}\n\n失败明细:\n{detail}{more}";
}
}
}