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}"; } } }