mason369 commited on
Commit
14d1bde
·
verified ·
1 Parent(s): fa6cb6c

Localize UI status text

Browse files
Files changed (4) hide show
  1. i18n/en_US.json +77 -1
  2. i18n/zh_CN.json +77 -1
  3. tests/test_ui_language.py +87 -0
  4. ui/app.py +182 -92
i18n/en_US.json CHANGED
@@ -24,7 +24,17 @@
24
  "no_models": "(No models)",
25
  "positive_pitch_info": "Positive raises pitch, negative lowers it",
26
  "normal_volume_info": "100% keeps original volume",
27
- "reverb_info": "Adds reverb to the vocals"
 
 
 
 
 
 
 
 
 
 
28
  },
29
  "conversion": {
30
  "model_settings": "Model Settings",
@@ -156,10 +166,76 @@
156
  "conversion_failed": "Conversion failed",
157
  "download_complete": "Download complete",
158
  "download_failed": "Download failed",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  "cover_complete": "Cover complete",
 
160
  "cover_failed": "Cover failed",
 
 
 
 
 
 
 
 
161
  "separating_vocals": "Separating vocals...",
162
  "converting_vocals": "Converting vocals...",
163
  "mixing_audio": "Mixing audio..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
  }
 
24
  "no_models": "(No models)",
25
  "positive_pitch_info": "Positive raises pitch, negative lowers it",
26
  "normal_volume_info": "100% keeps original volume",
27
+ "reverb_info": "Adds reverb to the vocals",
28
+ "all_series": "All",
29
+ "unknown": "Unknown",
30
+ "enabled": "On",
31
+ "disabled": "Off",
32
+ "language_korean": "Korean",
33
+ "language_japanese": "Japanese",
34
+ "language_chinese": "Chinese",
35
+ "language_english": "English",
36
+ "character_label_meta_separator": " | ",
37
+ "character_label_template": "[{language}] {display} | {meta} [{name}]"
38
  },
39
  "conversion": {
40
  "model_settings": "Model Settings",
 
166
  "conversion_failed": "Conversion failed",
167
  "download_complete": "Download complete",
168
  "download_failed": "Download failed",
169
+ "download_network_error": "An error occurred during download. Please check your network connection.",
170
+ "download_complete_status": "✅ Download complete",
171
+ "download_warning_status": "⚠️ Some downloads failed",
172
+ "download_error_status": "❌ Download failed: {error}",
173
+ "please_select_character_to_download": "Please select a character to download",
174
+ "character_download_complete": "✅ {name} model downloaded",
175
+ "character_download_failed": "❌ {name} model download failed",
176
+ "character_download_error": "❌ Download failed: {error}",
177
+ "bulk_download_complete": "✅ Complete: {count} succeeded",
178
+ "bulk_download_failed_items": ", {count} failed: {names}",
179
+ "bulk_download_error": "❌ Batch download failed: {error}",
180
+ "please_upload_song": "Please upload a song file",
181
+ "please_select_character": "Please select a character",
182
+ "character_model_missing": "Character model not found: {name}",
183
  "cover_complete": "Cover complete",
184
+ "cover_complete_status": "✅ Cover complete!",
185
  "cover_failed": "Cover failed",
186
+ "cover_process_failed": "❌ Processing failed: {error}",
187
+ "vc_pipeline_mode_status": "VC pipeline mode: {value}",
188
+ "singing_repair_status": "Singing repair: {value}",
189
+ "source_constraint_status": "Source constraint strategy: {value}",
190
+ "model_version_status": "Model version: {value}",
191
+ "character_continuity_status": "Character ownership: {value}",
192
+ "model_source_status": "Model source: {value}",
193
+ "all_files_dir_status": "All files directory: {value}",
194
  "separating_vocals": "Separating vocals...",
195
  "converting_vocals": "Converting vocals...",
196
  "mixing_audio": "Mixing audio..."
197
+ },
198
+ "character_details": {
199
+ "downloaded_empty": "After you select a downloaded character, this panel shows version ownership, source repository, and local file paths.",
200
+ "available_empty": "After you select a character to download, this panel shows version ownership, source repository, and download source.",
201
+ "version_label": "Version tag",
202
+ "continuity": "Character ownership",
203
+ "role": "Model type",
204
+ "source": "Series source",
205
+ "distribution": "Distribution",
206
+ "repo": "Source repository",
207
+ "source_page_url": "Source page",
208
+ "download_url": "Download URL",
209
+ "local_weight": "Local weight",
210
+ "local_index": "Local index",
211
+ "internal_key": "Internal key",
212
+ "detail_code_line": "- {label}: `{value}`",
213
+ "detail_text_line": "- {label}: {value}"
214
+ },
215
+ "route_status": {
216
+ "mature_auto_preferred_suffix": "← preferred by current auto mode",
217
+ "mature_roformer_auto_download_note": " The RoFormer model is downloaded automatically by audio-separator on first run into assets/separator_models",
218
+ "mature_legacy_status_suffix": " ← status only; strict SOTA auto mode does not use this",
219
+ "mature_current_preferred": "Current learned DeEcho preference: {model}",
220
+ "mature_missing_strict": "Strict SOTA DeEcho runtime is currently missing; cover auto mode will stop instead of degrading",
221
+ "official_route_title": "Currently using the bundled official RVC implementation",
222
+ "official_route_flow": "Flow: lead vocal separation → official audio loader / official VC → mix",
223
+ "official_route_note": "Note: skips this project's custom VC preprocessing, source constraint, and silence-gate post-processing",
224
+ "strict_route_ready_title": "✅ Fixed to strict SOTA RoFormer De-Reverb",
225
+ "route_current_model": "Matched model: {model}",
226
+ "strict_route_flow": "Flow: lead vocal separation → RoFormer De-Reverb → RVC → mix",
227
+ "strict_route_unavailable_title": "⚠️ Strict SOTA RoFormer De-Reverb is selected, but the runtime is unavailable",
228
+ "strict_route_unavailable_flow": "Processing will stop and will not degrade to UVR or algorithmic dereverb",
229
+ "strict_route_unavailable_advice": "Suggestion: repair the audio-separator / RoFormer De-Reverb runtime",
230
+ "auto_route_ready_title": "✅ Auto mode currently uses strict SOTA RoFormer De-Reverb",
231
+ "auto_route_missing_title": "⚠️ Auto mode is missing the strict SOTA DeEcho runtime",
232
+ "auto_route_missing_reason": "Reason: no runnable RoFormer De-Reverb condition was detected"
233
+ },
234
+ "device_info": {
235
+ "pytorch_version": "PyTorch version: {version}",
236
+ "available_backends": "Available backends: {backends}",
237
+ "gpu_line": "GPU: {name} ({backend}) - VRAM: {memory}",
238
+ "backend_version": "{label} version: {version}",
239
+ "no_gpu_cpu": "No GPU detected; CPU will be used"
240
  }
241
  }
i18n/zh_CN.json CHANGED
@@ -24,7 +24,17 @@
24
  "no_models": "(无模型)",
25
  "positive_pitch_info": "正数升调,负数降调",
26
  "normal_volume_info": "100% 为原始音量",
27
- "reverb_info": "为人声添加混响效果"
 
 
 
 
 
 
 
 
 
 
28
  },
29
  "conversion": {
30
  "model_settings": "模型设置",
@@ -156,10 +166,76 @@
156
  "conversion_failed": "转换失败",
157
  "download_complete": "下载完成",
158
  "download_failed": "下载失败",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  "cover_complete": "翻唱完成",
 
160
  "cover_failed": "翻唱失败",
 
 
 
 
 
 
 
 
161
  "separating_vocals": "正在分离人声...",
162
  "converting_vocals": "正在转换人声...",
163
  "mixing_audio": "正在混音..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
  }
 
24
  "no_models": "(无模型)",
25
  "positive_pitch_info": "正数升调,负数降调",
26
  "normal_volume_info": "100% 为原始音量",
27
+ "reverb_info": "为人声添加混响效果",
28
+ "all_series": "全部",
29
+ "unknown": "未知",
30
+ "enabled": "开启",
31
+ "disabled": "关闭",
32
+ "language_korean": "韩文",
33
+ "language_japanese": "日文",
34
+ "language_chinese": "中文",
35
+ "language_english": "英文",
36
+ "character_label_meta_separator": "|",
37
+ "character_label_template": "【{language}】{display}|{meta} [{name}]"
38
  },
39
  "conversion": {
40
  "model_settings": "模型设置",
 
166
  "conversion_failed": "转换失败",
167
  "download_complete": "下载完成",
168
  "download_failed": "下载失败",
169
+ "download_network_error": "下载过程中出现错误,请检查网络连接",
170
+ "download_complete_status": "✅ 下载完成",
171
+ "download_warning_status": "⚠️ 下载过程中存在失败项",
172
+ "download_error_status": "❌ 下载失败: {error}",
173
+ "please_select_character_to_download": "请选择要下载的角色",
174
+ "character_download_complete": "✅ {name} 模型下载完成",
175
+ "character_download_failed": "❌ {name} 模型下载失败",
176
+ "character_download_error": "❌ 下载失败: {error}",
177
+ "bulk_download_complete": "✅ 完成: 成功 {count} 个",
178
+ "bulk_download_failed_items": ",失败 {count} 个: {names}",
179
+ "bulk_download_error": "❌ 批量下载失败: {error}",
180
+ "please_upload_song": "请上传歌曲文件",
181
+ "please_select_character": "请选择角色",
182
+ "character_model_missing": "角色模型不存在: {name}",
183
  "cover_complete": "翻唱完成",
184
+ "cover_complete_status": "✅ 翻唱完成!",
185
  "cover_failed": "翻唱失败",
186
+ "cover_process_failed": "❌ 处理失败: {error}",
187
+ "vc_pipeline_mode_status": "VC管线模式: {value}",
188
+ "singing_repair_status": "唱歌修复: {value}",
189
+ "source_constraint_status": "源约束策略: {value}",
190
+ "model_version_status": "模型版本: {value}",
191
+ "character_continuity_status": "角色归属: {value}",
192
+ "model_source_status": "模型来源: {value}",
193
+ "all_files_dir_status": "全部文件目录: {value}",
194
  "separating_vocals": "正在分离人声...",
195
  "converting_vocals": "正在转换人声...",
196
  "mixing_audio": "正在混音..."
197
+ },
198
+ "character_details": {
199
+ "downloaded_empty": "选择已下载角色后,这里会显示该模型的版本归属、来源仓库和本地文件路径。",
200
+ "available_empty": "选择待下载角色后,这里会显示该模型的版本归属、来源仓库和下载来源。",
201
+ "version_label": "版本标识",
202
+ "continuity": "角色归属",
203
+ "role": "模型类型",
204
+ "source": "作品来源",
205
+ "distribution": "分发方式",
206
+ "repo": "来源仓库",
207
+ "source_page_url": "来源页面",
208
+ "download_url": "下载链接",
209
+ "local_weight": "本地权重",
210
+ "local_index": "本地索引",
211
+ "internal_key": "内部键",
212
+ "detail_code_line": "- {label}:`{value}`",
213
+ "detail_text_line": "- {label}:{value}"
214
+ },
215
+ "route_status": {
216
+ "mature_auto_preferred_suffix": "← 当前自动模式优先使用",
217
+ "mature_roformer_auto_download_note": " RoFormer 模型由 audio-separator 首次运行时自动下载到 assets/separator_models",
218
+ "mature_legacy_status_suffix": " ← 仅保留状态显示,严格SOTA自动模式不使用",
219
+ "mature_current_preferred": "当前优先学习型 DeEcho: {model}",
220
+ "mature_missing_strict": "当前缺少严格SOTA DeEcho运行环境;翻唱自动模式将停止处理而不是降级",
221
+ "official_route_title": "当前使用内置官方 RVC 实现",
222
+ "official_route_flow": "流程:主唱分离 → 官方音频加载 / 官方 VC → 混音",
223
+ "official_route_note": "说明:跳过本项目自定义 VC 预处理、源约束与静音门限后处理",
224
+ "strict_route_ready_title": "✅ 当前固定使用严格 SOTA RoFormer De-Reverb",
225
+ "route_current_model": "当前命中模型: {model}",
226
+ "strict_route_flow": "流程: 主唱分离 → RoFormer De-Reverb → RVC → 混音",
227
+ "strict_route_unavailable_title": "⚠️ 当前设为严格 SOTA RoFormer De-Reverb,但运行环境不可用",
228
+ "strict_route_unavailable_flow": "流程会停止处理,不会降级到 UVR 或算法去混响",
229
+ "strict_route_unavailable_advice": "建议: 修复 audio-separator / RoFormer De-Reverb 运行环境",
230
+ "auto_route_ready_title": "✅ 自动模式当前使用严格 SOTA RoFormer De-Reverb",
231
+ "auto_route_missing_title": "⚠️ 自动模式缺少严格SOTA DeEcho运行环境",
232
+ "auto_route_missing_reason": "原因: 未检测到 RoFormer De-Reverb 可运行条件"
233
+ },
234
+ "device_info": {
235
+ "pytorch_version": "PyTorch 版本: {version}",
236
+ "available_backends": "可用后端: {backends}",
237
+ "gpu_line": "GPU: {name} ({backend}) - 显存: {memory}",
238
+ "backend_version": "{label} 版本: {version}",
239
+ "no_gpu_cpu": "未检测到 GPU,将使用 CPU"
240
  }
241
  }
tests/test_ui_language.py CHANGED
@@ -62,6 +62,16 @@ class UiLanguageTests(unittest.TestCase):
62
  "positive_pitch_info",
63
  "normal_volume_info",
64
  "reverb_info",
 
 
 
 
 
 
 
 
 
 
65
  }
66
  required_settings_keys = {
67
  "runtime_settings",
@@ -73,11 +83,88 @@ class UiLanguageTests(unittest.TestCase):
73
  "about_body",
74
  "model_sources",
75
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  for lang in ("zh_CN", "en_US"):
78
  data = ui_app.load_i18n(lang)
79
  self.assertTrue(required_ui_keys.issubset(data.get("ui", {})))
80
  self.assertTrue(required_settings_keys.issubset(data.get("settings", {})))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  def test_ui_exposes_language_selector_and_save_handler(self):
83
  source = Path("ui/app.py").read_text(encoding="utf-8")
 
62
  "positive_pitch_info",
63
  "normal_volume_info",
64
  "reverb_info",
65
+ "all_series",
66
+ "unknown",
67
+ "enabled",
68
+ "disabled",
69
+ "language_korean",
70
+ "language_japanese",
71
+ "language_chinese",
72
+ "language_english",
73
+ "character_label_meta_separator",
74
+ "character_label_template",
75
  }
76
  required_settings_keys = {
77
  "runtime_settings",
 
83
  "about_body",
84
  "model_sources",
85
  }
86
+ required_message_keys = {
87
+ "download_network_error",
88
+ "please_select_character_to_download",
89
+ "character_download_complete",
90
+ "bulk_download_complete",
91
+ "please_upload_song",
92
+ "please_select_character",
93
+ "character_model_missing",
94
+ "cover_complete_status",
95
+ "cover_process_failed",
96
+ "vc_pipeline_mode_status",
97
+ "all_files_dir_status",
98
+ }
99
+ required_character_detail_keys = {
100
+ "downloaded_empty",
101
+ "available_empty",
102
+ "version_label",
103
+ "continuity",
104
+ "repo",
105
+ "local_weight",
106
+ "internal_key",
107
+ "detail_code_line",
108
+ "detail_text_line",
109
+ }
110
+ required_route_status_keys = {
111
+ "mature_auto_preferred_suffix",
112
+ "mature_current_preferred",
113
+ "official_route_title",
114
+ "strict_route_ready_title",
115
+ "route_current_model",
116
+ "strict_route_flow",
117
+ "auto_route_missing_title",
118
+ }
119
+ required_device_info_keys = {
120
+ "pytorch_version",
121
+ "available_backends",
122
+ "gpu_line",
123
+ "backend_version",
124
+ "no_gpu_cpu",
125
+ }
126
 
127
  for lang in ("zh_CN", "en_US"):
128
  data = ui_app.load_i18n(lang)
129
  self.assertTrue(required_ui_keys.issubset(data.get("ui", {})))
130
  self.assertTrue(required_settings_keys.issubset(data.get("settings", {})))
131
+ self.assertTrue(required_message_keys.issubset(data.get("messages", {})))
132
+ self.assertTrue(required_character_detail_keys.issubset(data.get("character_details", {})))
133
+ self.assertTrue(required_route_status_keys.issubset(data.get("route_status", {})))
134
+ self.assertTrue(required_device_info_keys.issubset(data.get("device_info", {})))
135
+
136
+ def test_character_metadata_values_are_not_localized(self):
137
+ original_i18n = ui_app.i18n
138
+ try:
139
+ ui_app.i18n = ui_app.load_i18n("en_US")
140
+ char_info = {
141
+ "name": "rin",
142
+ "display": "Rin Hoshizora",
143
+ "source": "Love Live!",
144
+ "continuity": "μ's",
145
+ "version_label": "500 epochs·40k",
146
+ "distribution": "HuggingFace",
147
+ "repo": "trioskosmos/rvc_models",
148
+ "source_page_url": "https://huggingface.co/trioskosmos/rvc_models",
149
+ "download_url": "https://huggingface.co/trioskosmos/rvc_models/resolve/main/rin.pth",
150
+ "model_path": "assets/weights/characters/rin/rin.pth",
151
+ "index_path": "assets/weights/characters/rin/rin.index",
152
+ }
153
+
154
+ label = ui_app.format_character_label(char_info)
155
+ details = ui_app.format_character_details(char_info, downloaded=True)
156
+
157
+ finally:
158
+ ui_app.i18n = original_i18n
159
+
160
+ self.assertIn("[Japanese]", label)
161
+ self.assertIn("trioskosmos/rvc_models", label)
162
+ self.assertIn("500 epochs·40k", label)
163
+ self.assertIn("- Source repository: `trioskosmos/rvc_models`", details)
164
+ self.assertIn("- Version tag: `500 epochs·40k`", details)
165
+ self.assertIn("assets/weights/characters/rin/rin.pth", details)
166
+ self.assertNotIn("版本标识", details)
167
+ self.assertNotIn("来源仓库", details)
168
 
169
  def test_ui_exposes_language_selector_and_save_handler(self):
170
  source = Path("ui/app.py").read_text(encoding="utf-8")
ui/app.py CHANGED
@@ -77,6 +77,41 @@ def t(key: str, section: str = None) -> str:
77
  return i18n.get(key, key)
78
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  def get_configured_language(config_data: Optional[dict] = None) -> str:
81
  """Return the configured UI language, failing on unsupported values."""
82
  selected_config = config if config_data is None else config_data
@@ -262,7 +297,7 @@ def download_base_models() -> str:
262
  if success:
263
  return t("download_complete", "messages")
264
  else:
265
- return "下载过程中出现错误,请检查网络连接"
266
  except Exception as e:
267
  return f"{t('download_failed', 'messages')}: {str(e)}"
268
 
@@ -278,8 +313,8 @@ def get_downloaded_character_list() -> list:
278
  def get_downloaded_character_series() -> list:
279
  """获取已下载角色的系列列表"""
280
  characters = get_downloaded_character_list()
281
- series = sorted({c.get("series", "未知") for c in characters})
282
- return ["全部"] + series
283
 
284
 
285
  def get_available_character_list() -> list:
@@ -291,13 +326,29 @@ def get_available_character_list() -> list:
291
  def get_available_character_series() -> list:
292
  """获取可用系列列表"""
293
  from tools.character_models import list_available_series
294
- return list_available_series()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
 
297
  def format_character_label(char_info: dict) -> str:
298
  """格式化角色展示名称,明确显示版本、归属和来源。"""
299
  display = char_info.get("base_display") or char_info.get("display") or char_info.get("description") or char_info.get("name", "")
300
- source = char_info.get("source", "未知")
301
  name = char_info.get("name", "")
302
  lang_tag = get_character_language_tag(char_info)
303
  parts: List[str] = []
@@ -318,39 +369,46 @@ def format_character_label(char_info: dict) -> str:
318
  elif repo:
319
  parts.append(repo)
320
 
321
- meta = "".join(part for part in parts if part)
322
- return f"【{lang_tag}】{display}|{meta} [{name}]"
 
 
 
 
 
 
 
323
 
324
 
325
  def get_character_language_tag(char_info: dict) -> str:
326
  """推断语言类型,用于下拉前缀标签"""
327
  lang = char_info.get("lang")
328
  if lang:
329
- return lang
330
  text = " ".join(
331
  str(char_info.get(k, "")) for k in ("display", "description", "name")
332
  ).lower()
333
  if "韩" in text or "kr" in text or "korean" in text:
334
- return "韩文"
335
  if "日" in text or "jp" in text or "japanese" in text:
336
- return "日文"
337
  if "中" in text or "cn" in text or "chinese" in text:
338
- return "中文"
339
  if "en" in text or "english" in text:
340
- return "英文"
341
 
342
  source = char_info.get("source", "")
343
  if source.startswith("Love Live!") or "ホロライブ" in source or "偶像大师" in source or "赛马娘" in source:
344
- return "日文"
345
  if "原神" in source or "崩坏" in source or "明日方舟" in source or "碧蓝航线" in source:
346
- return "中文"
347
  if "VOCALOID" in source or "Project SEKAI" in source:
348
- return "日文"
349
  if "Hololive" in source:
350
- return "日文"
351
  if "蔚蓝档案" in source or "绝区零" in source:
352
- return "日文"
353
- return "中文"
354
 
355
 
356
  def _find_character_entry(selection: str, downloaded: bool) -> Optional[dict]:
@@ -366,11 +424,29 @@ def _find_character_entry(selection: str, downloaded: bool) -> Optional[dict]:
366
  return None
367
 
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  def format_character_details(char_info: Optional[dict], downloaded: bool = False) -> str:
370
  if not char_info:
371
  if downloaded:
372
- return "选择已下载角色后,这里会显示该模型的版本归属、来源仓库和本地文件路径。"
373
- return "选择待下载角色后,这里会显示该模型的版本归属、来源仓库和下载来源。"
374
 
375
  title = char_info.get("base_display") or char_info.get("display") or char_info.get("name", "")
376
  lines = [f"**{title}**"]
@@ -385,26 +461,26 @@ def format_character_details(char_info: Optional[dict], downloaded: bool = False
385
  download_url = str(char_info.get("download_url") or "").strip()
386
 
387
  if version_label:
388
- lines.append(f"- 版本标识:`{version_label}`")
389
  if continuity:
390
- lines.append(f"- 角色归属:`{continuity}`")
391
  if role:
392
- lines.append(f"- 模型类型:`{role}`")
393
  if source:
394
- lines.append(f"- 作品来源:`{source}`")
395
  if distribution:
396
- lines.append(f"- 分发方式:`{distribution}`")
397
  if repo:
398
- lines.append(f"- 来源仓库:`{repo}`")
399
  if source_page_url:
400
- lines.append(f"- 来源页面:{source_page_url}")
401
  if download_url and download_url != source_page_url:
402
- lines.append(f"- 下载链接:{download_url}")
403
  if downloaded and char_info.get("model_path"):
404
- lines.append(f"- 本地权重:`{char_info['model_path']}`")
405
  if downloaded and char_info.get("index_path"):
406
- lines.append(f"- 本地索引:`{char_info['index_path']}`")
407
- lines.append(f"- 内部键:`{char_info.get('name', '')}`")
408
  return "\n".join(lines)
409
 
410
 
@@ -419,8 +495,8 @@ def get_available_character_details(selection: str) -> str:
419
  def get_downloaded_character_choices(series: str = "全部", keyword: str = "") -> list:
420
  """获取已下载角色的下拉选项"""
421
  chars = get_downloaded_character_list()
422
- if series and series != "全部":
423
- chars = [c for c in chars if c.get("series") == series]
424
  if keyword:
425
  kw = keyword.strip().lower()
426
  if kw:
@@ -454,8 +530,8 @@ def resolve_character_name(selection: str) -> str:
454
  def get_available_character_choices(series: str = "全部", keyword: str = "") -> list:
455
  """获取可下载角色的下拉选项"""
456
  chars = get_available_character_list()
457
- if series and series != "全部":
458
- chars = [c for c in chars if c.get("series") == series]
459
  if keyword:
460
  kw = keyword.strip().lower()
461
  if kw:
@@ -474,8 +550,9 @@ def get_available_character_choices(series: str = "全部", keyword: str = "") -
474
 
475
  def _refresh_downloaded_updates(series: str, keyword: str) -> Tuple[Dict, Dict]:
476
  series_choices = get_downloaded_character_series()
 
477
  if series not in series_choices:
478
- series = "全部"
479
  return (
480
  gr.update(choices=series_choices, value=series),
481
  gr.update(choices=get_downloaded_character_choices(series, keyword))
@@ -488,27 +565,27 @@ def download_character(name: str, selected_series: str = "全部", keyword: str
488
 
489
  if not name:
490
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
491
- return "请选择要下载的角色", choices_update, series_update
492
 
493
  try:
494
  success = download_character_model(name)
495
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
496
  if success:
497
  return (
498
- f" {name} 模型下载完成",
499
  choices_update,
500
  series_update
501
  )
502
  else:
503
  return (
504
- f" {name} 模型下载失败",
505
  choices_update,
506
  series_update
507
  )
508
  except Exception as e:
509
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
510
  return (
511
- f" 下载失败: {str(e)}",
512
  choices_update,
513
  series_update
514
  )
@@ -519,17 +596,18 @@ def download_all_characters(series: str = "全部", selected_series: str = "全
519
  from tools.character_models import download_all_character_models
520
 
521
  try:
522
- result = download_all_character_models(series=series)
 
523
  ok = result.get("success", [])
524
  failed = result.get("failed", [])
525
- status = f" 完成: 成功 {len(ok)} 个"
526
  if failed:
527
- status += f",失败 {len(failed)} 个: {', '.join(failed)}"
528
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
529
  return status, choices_update, series_update
530
  except Exception as e:
531
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
532
- return f" 批量下载失败: {str(e)}", choices_update, series_update
533
 
534
 
535
  def update_download_choices(series: str, keyword: str) -> Dict:
@@ -574,10 +652,10 @@ def process_cover(
574
  """
575
  _none6 = (None, None, None, None, None, None)
576
  if audio_path is None:
577
- return *_none6, "请上传歌曲文件"
578
 
579
  if not character_name:
580
- return *_none6, "请选择角色"
581
 
582
  try:
583
  from tools.character_models import get_character_model_path, get_character_info
@@ -588,7 +666,7 @@ def process_cover(
588
  char_meta = get_character_info(resolved_name, downloaded_only=True) or {}
589
  model_info = get_character_model_path(resolved_name)
590
  if model_info is None:
591
- return *_none6, f"角色模型不存在: {resolved_name}"
592
 
593
  # 进度回调
594
  def progress_callback(msg: str, step: int, total: int):
@@ -695,20 +773,32 @@ def process_cover(
695
  progress_callback=progress_callback
696
  )
697
 
698
- status_msg = "\u2705 \u7ffb\u5531\u5b8c\u6210!"
699
  status_msg += f"\n{get_cover_vc_route_status(vc_preprocess_mode, vc_pipeline_mode).splitlines()[0]}"
700
- status_msg += f"\nVC\u7ba1\u7ebf\u6a21\u5f0f: {pipeline_value_to_label.get(vc_pipeline_mode, vc_pipeline_mode)}"
701
- status_msg += f"\n唱歌修复: {'开启' if singing_repair else '关闭'}"
702
- status_msg += f"\n\u6e90\u7ea6\u675f\u7b56\u7565: {source_value_to_label.get(source_constraint_mode, source_constraint_mode)}"
 
 
 
 
 
 
 
 
 
 
 
 
703
  if char_meta.get("version_label"):
704
- status_msg += f"\n模型版本: {char_meta['version_label']}"
705
  if char_meta.get("continuity"):
706
- status_msg += f"\n角色归属: {char_meta['continuity']}"
707
  if char_meta.get("repo"):
708
- status_msg += f"\n模型来源: {char_meta['repo']}"
709
  status_msg += f"\n{get_runtime_build_label()}"
710
  if result.get("all_files_dir"):
711
- status_msg += f"\n\u5168\u90e8\u6587\u4ef6\u76ee\u5f55: {result['all_files_dir']}"
712
 
713
  return (
714
  result["cover"],
@@ -724,7 +814,7 @@ def process_cover(
724
  import traceback
725
  error_msg = str(e) if str(e) else traceback.format_exc()
726
  log.error(f"处理失败: {error_msg}")
727
- return None, None, None, None, None, None, f" 处理失败: {error_msg}"
728
 
729
 
730
  def check_mature_deecho_status() -> str:
@@ -736,23 +826,23 @@ def check_mature_deecho_status() -> str:
736
  roformer_ready = check_roformer_available()
737
  icon = "✅" if roformer_ready else "❌"
738
  status_lines.append(
739
- f"{icon} {ROFORMER_DEREVERB_DEFAULT_MODEL} 当前自动模式优先使用"
740
  )
741
  if roformer_ready:
742
- status_lines.append(" RoFormer 模型由 audio-separator 首次运行时自动下载到 assets/separator_models")
743
 
744
  for name in MATURE_DEECHO_MODELS:
745
  exists = check_model(name)
746
  icon = "✅" if exists else "❌"
747
- suffix = " 仅保留状态显示,严格SOTA自动模式不使用"
748
  status_lines.append(f"{icon} {name}{suffix}")
749
 
750
  if roformer_ready:
751
  status_lines.append("")
752
- status_lines.append(f"当前优先学习型 DeEcho: RoFormer {ROFORMER_DEREVERB_DEFAULT_MODEL}")
753
  else:
754
  status_lines.append("")
755
- status_lines.append("当前缺少严格SOTA DeEcho运行环境;翻唱自动模式将停止处理而不是降级")
756
 
757
  return "\n".join(status_lines)
758
 
@@ -764,10 +854,10 @@ def download_mature_deecho_models_ui() -> str:
764
  try:
765
  success = download_mature_deecho_models()
766
  status = check_mature_deecho_status()
767
- prefix = " 下载完成" if success else "⚠️ 下载过程中存在失败项"
768
  return f"{prefix}\n\n{status}"
769
  except Exception as e:
770
- return f" 下载失败: {str(e)}"
771
 
772
 
773
  def get_cover_vc_route_status(
@@ -790,38 +880,38 @@ def get_cover_vc_route_status(
790
 
791
  if pipeline_mode == "official":
792
  return newline.join([
793
- "当前使用内置官方 RVC 实现",
794
- "流程:主唱分离 → 官方音频加载 / 官方 VC → 混音",
795
- "说明:跳过本项目自定义 VC 预处理、源约束与静音门限后处理",
796
  build_label,
797
  ])
798
 
799
  if mode == "uvr_deecho":
800
  if preferred:
801
  return newline.join([
802
- " 当前固定使用严格 SOTA RoFormer De-Reverb",
803
- f"当前命中模型: {preferred}",
804
- "流程: 主唱分离 → RoFormer De-Reverb → RVC → 混音",
805
  build_label,
806
  ])
807
  return newline.join([
808
- "⚠️ 当前设为严格 SOTA RoFormer De-Reverb,但运行环境不可用",
809
- "流程会停止处理,不会降级到 UVR 或算法去混响",
810
- "建议: 修复 audio-separator / RoFormer De-Reverb 运行环境",
811
  build_label,
812
  ])
813
 
814
  if preferred:
815
  return newline.join([
816
- " 自动模式当前使用严格 SOTA RoFormer De-Reverb",
817
- f"当前命中模型: {preferred}",
818
- "流程: 主唱分离 → RoFormer De-Reverb → RVC → 混音",
819
  build_label,
820
  ])
821
  return newline.join([
822
- "⚠️ 自动模式缺少严格SOTA DeEcho运行环境",
823
- "原因: 未检测到 RoFormer De-Reverb 可运行条件",
824
- "流程会停止处理,不会降级到 UVR 或算法去混响",
825
  build_label,
826
  ])
827
 
@@ -845,22 +935,22 @@ def get_device_info() -> str:
845
  from lib.device import get_device_info as _get_info, _is_rocm, _has_xpu, _has_directml, _has_mps
846
 
847
  lines = []
848
- lines.append(f"PyTorch 版本: {torch.__version__}")
849
 
850
  info = _get_info()
851
- lines.append(f"可用后端: {', '.join(info['backends'])}")
852
 
853
  for dev in info["devices"]:
854
  mem = f"{dev['total_memory_gb']} GB" if dev.get("total_memory_gb") else "N/A"
855
- lines.append(f"GPU: {dev['name']} ({dev['backend']}) - 显存: {mem}")
856
 
857
  if torch.cuda.is_available():
858
  ver = torch.version.hip if _is_rocm() else torch.version.cuda
859
  label = "ROCm" if _is_rocm() else "CUDA"
860
- lines.append(f"{label} 版本: {ver}")
861
 
862
  if not info["devices"]:
863
- lines.append("未检测到 GPU,将使用 CPU")
864
 
865
  return "\n".join(lines)
866
 
@@ -1385,7 +1475,7 @@ def create_ui() -> gr.Blocks:
1385
  """创建 Gradio 界面"""
1386
 
1387
  with gr.Blocks(
1388
- title=i18n.get("app_title", "RVC AI 翻唱"),
1389
  theme=gr.themes.Base(
1390
  primary_hue="orange",
1391
  secondary_hue="gray",
@@ -1454,11 +1544,11 @@ def create_ui() -> gr.Blocks:
1454
 
1455
  # 标题
1456
  gr.Markdown(
1457
- f"# 🎤 {i18n.get('app_title', 'RVC AI 翻唱')}",
1458
  elem_classes=["main-title"]
1459
  )
1460
  gr.Markdown(
1461
- f"<center>{i18n.get('app_description', '基于 RVC v2 的 AI 翻唱系统')}</center>"
1462
  )
1463
  gr.Markdown(
1464
  f"<div class='runtime-stamp'>{get_runtime_build_label()}</div>"
@@ -1590,7 +1680,7 @@ def create_ui() -> gr.Blocks:
1590
  downloaded_series = gr.Dropdown(
1591
  label=t("series_filter", "ui"),
1592
  choices=get_downloaded_character_series(),
1593
- value="全部",
1594
  interactive=True
1595
  )
1596
 
@@ -1602,7 +1692,7 @@ def create_ui() -> gr.Blocks:
1602
 
1603
  character_dropdown = gr.Dropdown(
1604
  label=t("character", "cover"),
1605
- choices=get_downloaded_character_choices("全部", ""),
1606
  interactive=True,
1607
  info=t("character_choice_info", "ui")
1608
  )
@@ -1620,11 +1710,11 @@ def create_ui() -> gr.Blocks:
1620
 
1621
  # 角色下载区域
1622
  with gr.Accordion(t("download_character", "cover"), open=False):
1623
- series_choices = ["全部"] + get_available_character_series()
1624
  download_series = gr.Dropdown(
1625
  label=t("series_filter", "ui"),
1626
  choices=series_choices,
1627
- value="全部",
1628
  interactive=True
1629
  )
1630
 
@@ -1636,7 +1726,7 @@ def create_ui() -> gr.Blocks:
1636
 
1637
  download_char_dropdown = gr.Dropdown(
1638
  label=t("select_to_download", "cover"),
1639
- choices=get_available_character_choices("全部", ""),
1640
  interactive=True,
1641
  info=t("download_character_info", "ui")
1642
  )
@@ -1974,7 +2064,7 @@ def create_ui() -> gr.Blocks:
1974
  )
1975
 
1976
  download_all_btn.click(
1977
- fn=lambda series, keyword: download_all_characters("全部", series, keyword),
1978
  inputs=[downloaded_series, downloaded_keyword],
1979
  outputs=[download_char_status, character_dropdown, downloaded_series]
1980
  )
 
77
  return i18n.get(key, key)
78
 
79
 
80
+ def tf(key: str, section: str = None, **kwargs) -> str:
81
+ """Format translated UI text while preserving supplied technical values."""
82
+ return t(key, section).format(**kwargs)
83
+
84
+
85
+ def _all_series_label() -> str:
86
+ return t("all_series", "ui")
87
+
88
+
89
+ def _unknown_label() -> str:
90
+ return t("unknown", "ui")
91
+
92
+
93
+ def _is_all_series(series: Optional[str]) -> bool:
94
+ text = str(series or "").strip()
95
+ return text in {"", "全部", "All", _all_series_label()}
96
+
97
+
98
+ def _normalize_series_choice(series: Optional[str]) -> str:
99
+ return _all_series_label() if _is_all_series(series) else str(series)
100
+
101
+
102
+ def _display_series_label(series: Optional[str]) -> str:
103
+ text = str(series or "").strip()
104
+ return _unknown_label() if text in {"", "未知", "Unknown"} else text
105
+
106
+
107
+ def _series_matches(char_series: Optional[str], selected_series: str) -> bool:
108
+ return _display_series_label(char_series) == _normalize_series_choice(selected_series)
109
+
110
+
111
+ def _bool_status_label(value: bool) -> str:
112
+ return t("enabled", "ui") if value else t("disabled", "ui")
113
+
114
+
115
  def get_configured_language(config_data: Optional[dict] = None) -> str:
116
  """Return the configured UI language, failing on unsupported values."""
117
  selected_config = config if config_data is None else config_data
 
297
  if success:
298
  return t("download_complete", "messages")
299
  else:
300
+ return t("download_network_error", "messages")
301
  except Exception as e:
302
  return f"{t('download_failed', 'messages')}: {str(e)}"
303
 
 
313
  def get_downloaded_character_series() -> list:
314
  """获取已下载角色的系列列表"""
315
  characters = get_downloaded_character_list()
316
+ series = sorted({_display_series_label(c.get("series")) for c in characters})
317
+ return [_all_series_label()] + series
318
 
319
 
320
  def get_available_character_list() -> list:
 
326
  def get_available_character_series() -> list:
327
  """获取可用系列列表"""
328
  from tools.character_models import list_available_series
329
+ return sorted({_display_series_label(series) for series in list_available_series()})
330
+
331
+
332
+ def _localized_language_tag(lang: str) -> str:
333
+ text = str(lang or "").strip()
334
+ if not text:
335
+ return text
336
+ lowered = text.lower()
337
+ if text in {"韩文", "韓文"} or "kr" in lowered or "korean" in lowered:
338
+ return t("language_korean", "ui")
339
+ if text in {"日文", "日本語"} or "jp" in lowered or "japanese" in lowered:
340
+ return t("language_japanese", "ui")
341
+ if text in {"中文", "汉语", "漢語"} or "cn" in lowered or "chinese" in lowered:
342
+ return t("language_chinese", "ui")
343
+ if text in {"英文", "英语", "英語"} or lowered in {"en", "english"}:
344
+ return t("language_english", "ui")
345
+ return text
346
 
347
 
348
  def format_character_label(char_info: dict) -> str:
349
  """格式化角色展示名称,明确显示版本、归属和来源。"""
350
  display = char_info.get("base_display") or char_info.get("display") or char_info.get("description") or char_info.get("name", "")
351
+ source = char_info.get("source") or _unknown_label()
352
  name = char_info.get("name", "")
353
  lang_tag = get_character_language_tag(char_info)
354
  parts: List[str] = []
 
369
  elif repo:
370
  parts.append(repo)
371
 
372
+ meta = t("character_label_meta_separator", "ui").join(part for part in parts if part)
373
+ return tf(
374
+ "character_label_template",
375
+ "ui",
376
+ language=lang_tag,
377
+ display=display,
378
+ meta=meta,
379
+ name=name,
380
+ )
381
 
382
 
383
  def get_character_language_tag(char_info: dict) -> str:
384
  """推断语言类型,用于下拉前缀标签"""
385
  lang = char_info.get("lang")
386
  if lang:
387
+ return _localized_language_tag(lang)
388
  text = " ".join(
389
  str(char_info.get(k, "")) for k in ("display", "description", "name")
390
  ).lower()
391
  if "韩" in text or "kr" in text or "korean" in text:
392
+ return t("language_korean", "ui")
393
  if "日" in text or "jp" in text or "japanese" in text:
394
+ return t("language_japanese", "ui")
395
  if "中" in text or "cn" in text or "chinese" in text:
396
+ return t("language_chinese", "ui")
397
  if "en" in text or "english" in text:
398
+ return t("language_english", "ui")
399
 
400
  source = char_info.get("source", "")
401
  if source.startswith("Love Live!") or "ホロライブ" in source or "偶像大师" in source or "赛马娘" in source:
402
+ return t("language_japanese", "ui")
403
  if "原神" in source or "崩坏" in source or "明日方舟" in source or "碧蓝航线" in source:
404
+ return t("language_chinese", "ui")
405
  if "VOCALOID" in source or "Project SEKAI" in source:
406
+ return t("language_japanese", "ui")
407
  if "Hololive" in source:
408
+ return t("language_japanese", "ui")
409
  if "蔚蓝档案" in source or "绝区零" in source:
410
+ return t("language_japanese", "ui")
411
+ return t("language_chinese", "ui")
412
 
413
 
414
  def _find_character_entry(selection: str, downloaded: bool) -> Optional[dict]:
 
424
  return None
425
 
426
 
427
+ def _character_detail_code(label_key: str, value: str) -> str:
428
+ return tf(
429
+ "detail_code_line",
430
+ "character_details",
431
+ label=t(label_key, "character_details"),
432
+ value=value,
433
+ )
434
+
435
+
436
+ def _character_detail_text(label_key: str, value: str) -> str:
437
+ return tf(
438
+ "detail_text_line",
439
+ "character_details",
440
+ label=t(label_key, "character_details"),
441
+ value=value,
442
+ )
443
+
444
+
445
  def format_character_details(char_info: Optional[dict], downloaded: bool = False) -> str:
446
  if not char_info:
447
  if downloaded:
448
+ return t("downloaded_empty", "character_details")
449
+ return t("available_empty", "character_details")
450
 
451
  title = char_info.get("base_display") or char_info.get("display") or char_info.get("name", "")
452
  lines = [f"**{title}**"]
 
461
  download_url = str(char_info.get("download_url") or "").strip()
462
 
463
  if version_label:
464
+ lines.append(_character_detail_code("version_label", version_label))
465
  if continuity:
466
+ lines.append(_character_detail_code("continuity", continuity))
467
  if role:
468
+ lines.append(_character_detail_code("role", role))
469
  if source:
470
+ lines.append(_character_detail_code("source", source))
471
  if distribution:
472
+ lines.append(_character_detail_code("distribution", distribution))
473
  if repo:
474
+ lines.append(_character_detail_code("repo", repo))
475
  if source_page_url:
476
+ lines.append(_character_detail_text("source_page_url", source_page_url))
477
  if download_url and download_url != source_page_url:
478
+ lines.append(_character_detail_text("download_url", download_url))
479
  if downloaded and char_info.get("model_path"):
480
+ lines.append(_character_detail_code("local_weight", char_info["model_path"]))
481
  if downloaded and char_info.get("index_path"):
482
+ lines.append(_character_detail_code("local_index", char_info["index_path"]))
483
+ lines.append(_character_detail_code("internal_key", char_info.get("name", "")))
484
  return "\n".join(lines)
485
 
486
 
 
495
  def get_downloaded_character_choices(series: str = "全部", keyword: str = "") -> list:
496
  """获取已下载角色的下拉选项"""
497
  chars = get_downloaded_character_list()
498
+ if series and not _is_all_series(series):
499
+ chars = [c for c in chars if _series_matches(c.get("series"), series)]
500
  if keyword:
501
  kw = keyword.strip().lower()
502
  if kw:
 
530
  def get_available_character_choices(series: str = "全部", keyword: str = "") -> list:
531
  """获取可下载角色的下拉选项"""
532
  chars = get_available_character_list()
533
+ if series and not _is_all_series(series):
534
+ chars = [c for c in chars if _series_matches(c.get("series"), series)]
535
  if keyword:
536
  kw = keyword.strip().lower()
537
  if kw:
 
550
 
551
  def _refresh_downloaded_updates(series: str, keyword: str) -> Tuple[Dict, Dict]:
552
  series_choices = get_downloaded_character_series()
553
+ series = _normalize_series_choice(series)
554
  if series not in series_choices:
555
+ series = _all_series_label()
556
  return (
557
  gr.update(choices=series_choices, value=series),
558
  gr.update(choices=get_downloaded_character_choices(series, keyword))
 
565
 
566
  if not name:
567
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
568
+ return t("please_select_character_to_download", "messages"), choices_update, series_update
569
 
570
  try:
571
  success = download_character_model(name)
572
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
573
  if success:
574
  return (
575
+ tf("character_download_complete", "messages", name=name),
576
  choices_update,
577
  series_update
578
  )
579
  else:
580
  return (
581
+ tf("character_download_failed", "messages", name=name),
582
  choices_update,
583
  series_update
584
  )
585
  except Exception as e:
586
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
587
  return (
588
+ tf("character_download_error", "messages", error=str(e)),
589
  choices_update,
590
  series_update
591
  )
 
596
  from tools.character_models import download_all_character_models
597
 
598
  try:
599
+ series_arg = None if _is_all_series(series) else series
600
+ result = download_all_character_models(series=series_arg)
601
  ok = result.get("success", [])
602
  failed = result.get("failed", [])
603
+ status = tf("bulk_download_complete", "messages", count=len(ok))
604
  if failed:
605
+ status += tf("bulk_download_failed_items", "messages", count=len(failed), names=", ".join(failed))
606
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
607
  return status, choices_update, series_update
608
  except Exception as e:
609
  series_update, choices_update = _refresh_downloaded_updates(selected_series, keyword)
610
+ return tf("bulk_download_error", "messages", error=str(e)), choices_update, series_update
611
 
612
 
613
  def update_download_choices(series: str, keyword: str) -> Dict:
 
652
  """
653
  _none6 = (None, None, None, None, None, None)
654
  if audio_path is None:
655
+ return *_none6, t("please_upload_song", "messages")
656
 
657
  if not character_name:
658
+ return *_none6, t("please_select_character", "messages")
659
 
660
  try:
661
  from tools.character_models import get_character_model_path, get_character_info
 
666
  char_meta = get_character_info(resolved_name, downloaded_only=True) or {}
667
  model_info = get_character_model_path(resolved_name)
668
  if model_info is None:
669
+ return *_none6, tf("character_model_missing", "messages", name=resolved_name)
670
 
671
  # 进度回调
672
  def progress_callback(msg: str, step: int, total: int):
 
773
  progress_callback=progress_callback
774
  )
775
 
776
+ status_msg = t("cover_complete_status", "messages")
777
  status_msg += f"\n{get_cover_vc_route_status(vc_preprocess_mode, vc_pipeline_mode).splitlines()[0]}"
778
+ status_msg += "\n" + tf(
779
+ "vc_pipeline_mode_status",
780
+ "messages",
781
+ value=pipeline_value_to_label.get(vc_pipeline_mode, vc_pipeline_mode),
782
+ )
783
+ status_msg += "\n" + tf(
784
+ "singing_repair_status",
785
+ "messages",
786
+ value=_bool_status_label(singing_repair),
787
+ )
788
+ status_msg += "\n" + tf(
789
+ "source_constraint_status",
790
+ "messages",
791
+ value=source_value_to_label.get(source_constraint_mode, source_constraint_mode),
792
+ )
793
  if char_meta.get("version_label"):
794
+ status_msg += "\n" + tf("model_version_status", "messages", value=char_meta["version_label"])
795
  if char_meta.get("continuity"):
796
+ status_msg += "\n" + tf("character_continuity_status", "messages", value=char_meta["continuity"])
797
  if char_meta.get("repo"):
798
+ status_msg += "\n" + tf("model_source_status", "messages", value=char_meta["repo"])
799
  status_msg += f"\n{get_runtime_build_label()}"
800
  if result.get("all_files_dir"):
801
+ status_msg += "\n" + tf("all_files_dir_status", "messages", value=result["all_files_dir"])
802
 
803
  return (
804
  result["cover"],
 
814
  import traceback
815
  error_msg = str(e) if str(e) else traceback.format_exc()
816
  log.error(f"处理失败: {error_msg}")
817
+ return None, None, None, None, None, None, tf("cover_process_failed", "messages", error=error_msg)
818
 
819
 
820
  def check_mature_deecho_status() -> str:
 
826
  roformer_ready = check_roformer_available()
827
  icon = "✅" if roformer_ready else "❌"
828
  status_lines.append(
829
+ f"{icon} {ROFORMER_DEREVERB_DEFAULT_MODEL} {t('mature_auto_preferred_suffix', 'route_status')}"
830
  )
831
  if roformer_ready:
832
+ status_lines.append(t("mature_roformer_auto_download_note", "route_status"))
833
 
834
  for name in MATURE_DEECHO_MODELS:
835
  exists = check_model(name)
836
  icon = "✅" if exists else "❌"
837
+ suffix = t("mature_legacy_status_suffix", "route_status")
838
  status_lines.append(f"{icon} {name}{suffix}")
839
 
840
  if roformer_ready:
841
  status_lines.append("")
842
+ status_lines.append(tf("mature_current_preferred", "route_status", model=f"RoFormer {ROFORMER_DEREVERB_DEFAULT_MODEL}"))
843
  else:
844
  status_lines.append("")
845
+ status_lines.append(t("mature_missing_strict", "route_status"))
846
 
847
  return "\n".join(status_lines)
848
 
 
854
  try:
855
  success = download_mature_deecho_models()
856
  status = check_mature_deecho_status()
857
+ prefix = t("download_complete_status", "messages") if success else t("download_warning_status", "messages")
858
  return f"{prefix}\n\n{status}"
859
  except Exception as e:
860
+ return tf("download_error_status", "messages", error=str(e))
861
 
862
 
863
  def get_cover_vc_route_status(
 
880
 
881
  if pipeline_mode == "official":
882
  return newline.join([
883
+ t("official_route_title", "route_status"),
884
+ t("official_route_flow", "route_status"),
885
+ t("official_route_note", "route_status"),
886
  build_label,
887
  ])
888
 
889
  if mode == "uvr_deecho":
890
  if preferred:
891
  return newline.join([
892
+ t("strict_route_ready_title", "route_status"),
893
+ tf("route_current_model", "route_status", model=preferred),
894
+ t("strict_route_flow", "route_status"),
895
  build_label,
896
  ])
897
  return newline.join([
898
+ t("strict_route_unavailable_title", "route_status"),
899
+ t("strict_route_unavailable_flow", "route_status"),
900
+ t("strict_route_unavailable_advice", "route_status"),
901
  build_label,
902
  ])
903
 
904
  if preferred:
905
  return newline.join([
906
+ t("auto_route_ready_title", "route_status"),
907
+ tf("route_current_model", "route_status", model=preferred),
908
+ t("strict_route_flow", "route_status"),
909
  build_label,
910
  ])
911
  return newline.join([
912
+ t("auto_route_missing_title", "route_status"),
913
+ t("auto_route_missing_reason", "route_status"),
914
+ t("strict_route_unavailable_flow", "route_status"),
915
  build_label,
916
  ])
917
 
 
935
  from lib.device import get_device_info as _get_info, _is_rocm, _has_xpu, _has_directml, _has_mps
936
 
937
  lines = []
938
+ lines.append(tf("pytorch_version", "device_info", version=torch.__version__))
939
 
940
  info = _get_info()
941
+ lines.append(tf("available_backends", "device_info", backends=", ".join(info["backends"])))
942
 
943
  for dev in info["devices"]:
944
  mem = f"{dev['total_memory_gb']} GB" if dev.get("total_memory_gb") else "N/A"
945
+ lines.append(tf("gpu_line", "device_info", name=dev["name"], backend=dev["backend"], memory=mem))
946
 
947
  if torch.cuda.is_available():
948
  ver = torch.version.hip if _is_rocm() else torch.version.cuda
949
  label = "ROCm" if _is_rocm() else "CUDA"
950
+ lines.append(tf("backend_version", "device_info", label=label, version=ver))
951
 
952
  if not info["devices"]:
953
+ lines.append(t("no_gpu_cpu", "device_info"))
954
 
955
  return "\n".join(lines)
956
 
 
1475
  """创建 Gradio 界面"""
1476
 
1477
  with gr.Blocks(
1478
+ title=t("app_title"),
1479
  theme=gr.themes.Base(
1480
  primary_hue="orange",
1481
  secondary_hue="gray",
 
1544
 
1545
  # 标题
1546
  gr.Markdown(
1547
+ f"# 🎤 {t('app_title')}",
1548
  elem_classes=["main-title"]
1549
  )
1550
  gr.Markdown(
1551
+ f"<center>{t('app_description')}</center>"
1552
  )
1553
  gr.Markdown(
1554
  f"<div class='runtime-stamp'>{get_runtime_build_label()}</div>"
 
1680
  downloaded_series = gr.Dropdown(
1681
  label=t("series_filter", "ui"),
1682
  choices=get_downloaded_character_series(),
1683
+ value=_all_series_label(),
1684
  interactive=True
1685
  )
1686
 
 
1692
 
1693
  character_dropdown = gr.Dropdown(
1694
  label=t("character", "cover"),
1695
+ choices=get_downloaded_character_choices(_all_series_label(), ""),
1696
  interactive=True,
1697
  info=t("character_choice_info", "ui")
1698
  )
 
1710
 
1711
  # 角色下载区域
1712
  with gr.Accordion(t("download_character", "cover"), open=False):
1713
+ series_choices = [_all_series_label()] + get_available_character_series()
1714
  download_series = gr.Dropdown(
1715
  label=t("series_filter", "ui"),
1716
  choices=series_choices,
1717
+ value=_all_series_label(),
1718
  interactive=True
1719
  )
1720
 
 
1726
 
1727
  download_char_dropdown = gr.Dropdown(
1728
  label=t("select_to_download", "cover"),
1729
+ choices=get_available_character_choices(_all_series_label(), ""),
1730
  interactive=True,
1731
  info=t("download_character_info", "ui")
1732
  )
 
2064
  )
2065
 
2066
  download_all_btn.click(
2067
+ fn=lambda series, keyword: download_all_characters(_all_series_label(), series, keyword),
2068
  inputs=[downloaded_series, downloaded_keyword],
2069
  outputs=[download_char_status, character_dropdown, downloaded_series]
2070
  )