杨月政 commited on
Commit
e1a7f6a
·
1 Parent(s): 339c325

feat: 添加中文界面翻译 (i18n)

Browse files

- app.py: Tab 改为中文(歌声合成、歌声转换)
- webui.py: 添加 i18n,SVS 界面全部中文化
- webui_svc.py: 使用 _i18n,SVC 界面全部中文化
- i18n_config.py: 语言配置,默认 zh

Made-with: Cursor

Files changed (4) hide show
  1. app.py +2 -2
  2. i18n_config.py +2 -0
  3. webui.py +60 -26
  4. webui_svc.py +26 -18
app.py CHANGED
@@ -46,9 +46,9 @@ if __name__ == "__main__":
46
  '</div>'
47
  )
48
  with gr.Tabs():
49
- with gr.Tab("Singing Voice Synthesis"):
50
  render_svs_tab()
51
- with gr.Tab("Singing Voice Conversion"):
52
  render_svc_tab()
53
 
54
  page.queue()
 
46
  '</div>'
47
  )
48
  with gr.Tabs():
49
+ with gr.Tab("歌声合成"):
50
  render_svs_tab()
51
+ with gr.Tab("歌声转换"):
52
  render_svc_tab()
53
 
54
  page.queue()
i18n_config.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """Shared i18n config for SoulX-Singer Space. Set LANG to 'zh' for Chinese, 'en' for English."""
2
+ LANG = "zh"
webui.py CHANGED
@@ -22,6 +22,45 @@ from cli.inference import build_model as build_svs_model, process as svs_process
22
 
23
  ROOT = Path(__file__).parent
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  def _get_device() -> str:
27
  if torch.cuda.is_available():
@@ -235,7 +274,7 @@ def transcription_function(
235
  if isinstance(target_audio, tuple):
236
  target_audio = target_audio[0]
237
  if prompt_audio is None or target_audio is None:
238
- gr.Warning(message="Please upload both prompt audio and target audio")
239
  return None, None
240
 
241
  prompt_meta_resolved = _resolve_file_path(prompt_metadata)
@@ -299,10 +338,10 @@ def synthesis_function(
299
  target_audio = target_audio[0]
300
 
301
  if not prompt_audio or not os.path.isfile(prompt_audio):
302
- gr.Warning(message="Please upload both prompt audio and target audio")
303
  return None, gr.update(), gr.update()
304
  if not target_audio or not os.path.isfile(target_audio):
305
- gr.Warning(message="Please upload both prompt audio and target audio")
306
  return None, gr.update(), gr.update()
307
 
308
  prompt_meta_path = _resolve_file_path(prompt_metadata)
@@ -316,7 +355,7 @@ def synthesis_function(
316
  prompt_vocal_sep, target_vocal_sep,
317
  )
318
  if not p or not t:
319
- gr.Warning(message="Transcription failed. Check your audio files.")
320
  return None, gr.update(), gr.update()
321
  prompt_meta_path = p
322
  target_meta_path = t
@@ -369,12 +408,12 @@ def render_tab_content() -> None:
369
  # ── Left column: inputs & controls ──
370
  with gr.Column(scale=1):
371
  prompt_audio = gr.Audio(
372
- label="Prompt audio (reference voice), max 30s",
373
  type="filepath",
374
  interactive=True,
375
  )
376
  target_audio = gr.Audio(
377
- label="Target audio (melody / lyrics source), max 60s",
378
  type="filepath",
379
  interactive=True,
380
  )
@@ -383,27 +422,27 @@ def render_tab_content() -> None:
383
  control_radio = gr.Radio(
384
  choices=["melody", "score"],
385
  value="melody",
386
- label="Control type",
387
  scale=1,
388
  )
389
  auto_shift = gr.Checkbox(
390
- label="Auto pitch shift",
391
  value=True,
392
  interactive=True,
393
  scale=1,
394
  )
395
 
396
  synthesis_btn = gr.Button(
397
- value="🎤 Generate singing voice",
398
  variant="primary",
399
  size="lg",
400
  )
401
 
402
  # ── Advanced: transcription settings & metadata ──
403
- with gr.Accordion("Advanced: Transcription & Metadata", open=False):
404
  with gr.Row():
405
  pitch_shift = gr.Number(
406
- label="Pitch shift (semitones)",
407
  value=0,
408
  minimum=-36,
409
  maximum=36,
@@ -412,21 +451,16 @@ def render_tab_content() -> None:
412
  scale=1,
413
  )
414
  seed_input = gr.Number(
415
- label="Seed",
416
  value=12306,
417
  step=1,
418
  interactive=True,
419
  scale=1,
420
  )
421
- gr.Markdown(
422
- "Upload your own metadata files to skip automatic transcription. "
423
- "You can use the [SoulX-Singer-Midi-Editor]"
424
- "(https://huggingface.co/spaces/Soul-AILab/SoulX-Singer-Midi-Editor) "
425
- "to edit metadata for better alignment."
426
- )
427
  with gr.Row():
428
  prompt_lyric_lang = gr.Dropdown(
429
- label="Prompt lyric language",
430
  choices=[
431
  ("Mandarin", "Mandarin"),
432
  ("Cantonese", "Cantonese"),
@@ -437,7 +471,7 @@ def render_tab_content() -> None:
437
  scale=1,
438
  )
439
  target_lyric_lang = gr.Dropdown(
440
- label="Target lyric language",
441
  choices=[
442
  ("Mandarin", "Mandarin"),
443
  ("Cantonese", "Cantonese"),
@@ -449,31 +483,31 @@ def render_tab_content() -> None:
449
  )
450
  with gr.Row():
451
  prompt_vocal_sep = gr.Checkbox(
452
- label="Prompt vocal separation",
453
  value=False,
454
  interactive=True,
455
  scale=1,
456
  )
457
  target_vocal_sep = gr.Checkbox(
458
- label="Target vocal separation",
459
  value=True,
460
  interactive=True,
461
  scale=1,
462
  )
463
  transcription_btn = gr.Button(
464
- value="Run singing transcription",
465
  variant="secondary",
466
  size="lg",
467
  )
468
  with gr.Row():
469
  prompt_metadata = gr.File(
470
- label="Prompt metadata",
471
  type="filepath",
472
  file_types=[".json"],
473
  interactive=True,
474
  )
475
  target_metadata = gr.File(
476
- label="Target metadata",
477
  type="filepath",
478
  file_types=[".json"],
479
  interactive=True,
@@ -482,7 +516,7 @@ def render_tab_content() -> None:
482
  # ── Right column: output ──
483
  with gr.Column(scale=1):
484
  output_audio = gr.Audio(
485
- label="Generated audio",
486
  type="filepath",
487
  interactive=False,
488
  )
 
22
 
23
  ROOT = Path(__file__).parent
24
 
25
+ _I18N = dict(
26
+ prompt_audio_label=dict(en="Prompt audio (reference voice), max 30s", zh="Prompt 音频(参考音色),最长 30 秒"),
27
+ target_audio_label=dict(en="Target audio (melody / lyrics source), max 60s", zh="Target 音频(旋律/歌词来源),最长 60 秒"),
28
+ control_type_label=dict(en="Control type", zh="控制模式"),
29
+ auto_pitch_shift_label=dict(en="Auto pitch shift", zh="自动变调"),
30
+ generate_btn=dict(en="🎤 Generate singing voice", zh="🎤 生成歌声"),
31
+ advanced_accordion=dict(en="Advanced: Transcription & Metadata", zh="高级:转录与元数据"),
32
+ pitch_shift_label=dict(en="Pitch shift (semitones)", zh="变调(半音)"),
33
+ seed_label=dict(en="Seed", zh="种子"),
34
+ metadata_hint=dict(
35
+ en="Upload your own metadata files to skip automatic transcription. "
36
+ "You can use the [SoulX-Singer-Midi-Editor](https://huggingface.co/spaces/Soul-AILab/SoulX-Singer-Midi-Editor) to edit metadata for better alignment.",
37
+ zh="上传自己的 metadata 文件可跳过自动转录。可使用 [SoulX-Singer-Midi-Editor](https://huggingface.co/spaces/Soul-AILab/SoulX-Singer-Midi-Editor) 编辑 metadata 以获得更好对齐。",
38
+ ),
39
+ prompt_lyric_lang_label=dict(en="Prompt lyric language", zh="Prompt 歌词语言"),
40
+ target_lyric_lang_label=dict(en="Target lyric language", zh="Target 歌词语言"),
41
+ prompt_vocal_sep_label=dict(en="Prompt vocal separation", zh="Prompt 人声分离"),
42
+ target_vocal_sep_label=dict(en="Target vocal separation", zh="Target 人声分离"),
43
+ transcription_btn=dict(en="Run singing transcription", zh="运行歌声转录"),
44
+ prompt_metadata_label=dict(en="Prompt metadata", zh="Prompt 元数据"),
45
+ target_metadata_label=dict(en="Target metadata", zh="Target 元数据"),
46
+ output_audio_label=dict(en="Generated audio", zh="合成结果音频"),
47
+ warn_upload_both=dict(en="Please upload both prompt audio and target audio", zh="请同时上传 Prompt 与 Target 音频"),
48
+ warn_transcription_failed=dict(en="Transcription failed. Check your audio files.", zh="转录失败,请检查音频文件。"),
49
+ )
50
+
51
+
52
+ def _get_lang() -> str:
53
+ try:
54
+ from i18n_config import LANG
55
+ return LANG if LANG in ("zh", "en") else "zh"
56
+ except ImportError:
57
+ return "zh"
58
+
59
+
60
+ def _i18n(key: str) -> str:
61
+ lang = _get_lang()
62
+ return _I18N.get(key, {}).get(lang, _I18N.get(key, {}).get("en", key))
63
+
64
 
65
  def _get_device() -> str:
66
  if torch.cuda.is_available():
 
274
  if isinstance(target_audio, tuple):
275
  target_audio = target_audio[0]
276
  if prompt_audio is None or target_audio is None:
277
+ gr.Warning(message=_i18n("warn_upload_both"))
278
  return None, None
279
 
280
  prompt_meta_resolved = _resolve_file_path(prompt_metadata)
 
338
  target_audio = target_audio[0]
339
 
340
  if not prompt_audio or not os.path.isfile(prompt_audio):
341
+ gr.Warning(message=_i18n("warn_upload_both"))
342
  return None, gr.update(), gr.update()
343
  if not target_audio or not os.path.isfile(target_audio):
344
+ gr.Warning(message=_i18n("warn_upload_both"))
345
  return None, gr.update(), gr.update()
346
 
347
  prompt_meta_path = _resolve_file_path(prompt_metadata)
 
355
  prompt_vocal_sep, target_vocal_sep,
356
  )
357
  if not p or not t:
358
+ gr.Warning(message=_i18n("warn_transcription_failed"))
359
  return None, gr.update(), gr.update()
360
  prompt_meta_path = p
361
  target_meta_path = t
 
408
  # ── Left column: inputs & controls ──
409
  with gr.Column(scale=1):
410
  prompt_audio = gr.Audio(
411
+ label=_i18n("prompt_audio_label"),
412
  type="filepath",
413
  interactive=True,
414
  )
415
  target_audio = gr.Audio(
416
+ label=_i18n("target_audio_label"),
417
  type="filepath",
418
  interactive=True,
419
  )
 
422
  control_radio = gr.Radio(
423
  choices=["melody", "score"],
424
  value="melody",
425
+ label=_i18n("control_type_label"),
426
  scale=1,
427
  )
428
  auto_shift = gr.Checkbox(
429
+ label=_i18n("auto_pitch_shift_label"),
430
  value=True,
431
  interactive=True,
432
  scale=1,
433
  )
434
 
435
  synthesis_btn = gr.Button(
436
+ value=_i18n("generate_btn"),
437
  variant="primary",
438
  size="lg",
439
  )
440
 
441
  # ── Advanced: transcription settings & metadata ──
442
+ with gr.Accordion(_i18n("advanced_accordion"), open=False):
443
  with gr.Row():
444
  pitch_shift = gr.Number(
445
+ label=_i18n("pitch_shift_label"),
446
  value=0,
447
  minimum=-36,
448
  maximum=36,
 
451
  scale=1,
452
  )
453
  seed_input = gr.Number(
454
+ label=_i18n("seed_label"),
455
  value=12306,
456
  step=1,
457
  interactive=True,
458
  scale=1,
459
  )
460
+ gr.Markdown(_i18n("metadata_hint"))
 
 
 
 
 
461
  with gr.Row():
462
  prompt_lyric_lang = gr.Dropdown(
463
+ label=_i18n("prompt_lyric_lang_label"),
464
  choices=[
465
  ("Mandarin", "Mandarin"),
466
  ("Cantonese", "Cantonese"),
 
471
  scale=1,
472
  )
473
  target_lyric_lang = gr.Dropdown(
474
+ label=_i18n("target_lyric_lang_label"),
475
  choices=[
476
  ("Mandarin", "Mandarin"),
477
  ("Cantonese", "Cantonese"),
 
483
  )
484
  with gr.Row():
485
  prompt_vocal_sep = gr.Checkbox(
486
+ label=_i18n("prompt_vocal_sep_label"),
487
  value=False,
488
  interactive=True,
489
  scale=1,
490
  )
491
  target_vocal_sep = gr.Checkbox(
492
+ label=_i18n("target_vocal_sep_label"),
493
  value=True,
494
  interactive=True,
495
  scale=1,
496
  )
497
  transcription_btn = gr.Button(
498
+ value=_i18n("transcription_btn"),
499
  variant="secondary",
500
  size="lg",
501
  )
502
  with gr.Row():
503
  prompt_metadata = gr.File(
504
+ label=_i18n("prompt_metadata_label"),
505
  type="filepath",
506
  file_types=[".json"],
507
  interactive=True,
508
  )
509
  target_metadata = gr.File(
510
+ label=_i18n("target_metadata_label"),
511
  type="filepath",
512
  file_types=[".json"],
513
  interactive=True,
 
516
  # ── Right column: output ──
517
  with gr.Column(scale=1):
518
  output_audio = gr.Audio(
519
+ label=_i18n("output_audio_label"),
520
  type="filepath",
521
  interactive=False,
522
  )
webui_svc.py CHANGED
@@ -36,8 +36,10 @@ EXAMPLE_LIST = [
36
  _I18N = dict(
37
  display_lang_label=dict(en="Display Language", zh="显示语言"),
38
  title=dict(en="## SoulX-Singer SVC", zh="## SoulX-Singer SVC"),
39
- prompt_audio_label=dict(en=f"Prompt audio", zh=f"Prompt 音频"),
40
- target_audio_label=dict(en=f"Target audio", zh=f"Target 音频"),
 
 
41
  prompt_vocal_sep_label=dict(en="Prompt vocal separation", zh="Prompt 人声分离"),
42
  target_vocal_sep_label=dict(en="Target vocal separation", zh="Target 人声分离"),
43
  auto_shift_label=dict(en="Auto pitch shift", zh="自动变调"),
@@ -78,11 +80,17 @@ _I18N = dict(
78
  )
79
  )
80
 
81
- _GLOBAL_LANG: Literal["zh", "en"] = "zh"
 
 
 
 
 
82
 
83
 
84
  def _i18n(key: str) -> str:
85
- return _I18N[key][_GLOBAL_LANG]
 
86
 
87
 
88
  def _print_exception(context: str) -> None:
@@ -369,39 +377,39 @@ def render_tab_content() -> None:
369
  # ── Left column: inputs & controls ──
370
  with gr.Column(scale=1):
371
  prompt_audio = gr.Audio(
372
- label="Prompt audio (reference voice)",
373
  type="filepath",
374
  interactive=True,
375
  )
376
  target_audio = gr.Audio(
377
- label="Target audio (to convert)",
378
  type="filepath",
379
  interactive=True,
380
  )
381
 
382
  run_btn = gr.Button(
383
- value="🎤 Singing Voice Conversion",
384
  variant="primary",
385
  size="lg",
386
  )
387
 
388
- with gr.Accordion("Advanced settings", open=False):
389
  with gr.Row():
390
- prompt_vocal_sep = gr.Checkbox(label="Prompt vocal separation", value=False, scale=1)
391
- target_vocal_sep = gr.Checkbox(label="Target vocal separation", value=True, scale=1)
392
  with gr.Row():
393
- auto_shift = gr.Checkbox(label="Auto pitch shift", value=True, scale=1)
394
- auto_mix_acc = gr.Checkbox(label="Auto mix accompaniment", value=True, scale=1)
395
  with gr.Row():
396
- use_fp16 = gr.Checkbox(label="Use FP16", value=True, scale=1)
397
- pitch_shift = gr.Slider(label="Pitch shift (semitones)", value=0, minimum=-36, maximum=36, step=1)
398
- n_step = gr.Slider(label="diffusion steps", value=32, minimum=1, maximum=200, step=1)
399
- cfg = gr.Slider(label="cfg scale", value=1.0, minimum=0.0, maximum=10.0, step=0.1)
400
- seed_input = gr.Slider(label="Seed", value=42, minimum=0, maximum=10000, step=1)
401
 
402
  # ── Right column: output ──
403
  with gr.Column(scale=1):
404
- output_audio = gr.Audio(label="Generated audio", type="filepath", interactive=False)
405
  svc_state = gr.State(value=None)
406
  gr.Examples(
407
  examples=EXAMPLE_LIST,
 
36
  _I18N = dict(
37
  display_lang_label=dict(en="Display Language", zh="显示语言"),
38
  title=dict(en="## SoulX-Singer SVC", zh="## SoulX-Singer SVC"),
39
+ prompt_audio_label=dict(en="Prompt audio (reference voice)", zh="Prompt 音频(参考音色)"),
40
+ target_audio_label=dict(en="Target audio (to convert)", zh="Target 音频(待转换)"),
41
+ advanced_settings=dict(en="Advanced settings", zh="高级设置"),
42
+ use_fp16_label=dict(en="Use FP16", zh="使用 FP16"),
43
  prompt_vocal_sep_label=dict(en="Prompt vocal separation", zh="Prompt 人声分离"),
44
  target_vocal_sep_label=dict(en="Target vocal separation", zh="Target 人声分离"),
45
  auto_shift_label=dict(en="Auto pitch shift", zh="自动变调"),
 
80
  )
81
  )
82
 
83
+ def _get_lang() -> str:
84
+ try:
85
+ from i18n_config import LANG
86
+ return LANG if LANG in ("zh", "en") else "zh"
87
+ except ImportError:
88
+ return "zh"
89
 
90
 
91
  def _i18n(key: str) -> str:
92
+ lang = _get_lang()
93
+ return _I18N.get(key, {}).get(lang, _I18N.get(key, {}).get("en", key))
94
 
95
 
96
  def _print_exception(context: str) -> None:
 
377
  # ── Left column: inputs & controls ──
378
  with gr.Column(scale=1):
379
  prompt_audio = gr.Audio(
380
+ label=_i18n("prompt_audio_label"),
381
  type="filepath",
382
  interactive=True,
383
  )
384
  target_audio = gr.Audio(
385
+ label=_i18n("target_audio_label"),
386
  type="filepath",
387
  interactive=True,
388
  )
389
 
390
  run_btn = gr.Button(
391
+ value=_i18n("run_btn"),
392
  variant="primary",
393
  size="lg",
394
  )
395
 
396
+ with gr.Accordion(_i18n("advanced_settings"), open=False):
397
  with gr.Row():
398
+ prompt_vocal_sep = gr.Checkbox(label=_i18n("prompt_vocal_sep_label"), value=False, scale=1)
399
+ target_vocal_sep = gr.Checkbox(label=_i18n("target_vocal_sep_label"), value=True, scale=1)
400
  with gr.Row():
401
+ auto_shift = gr.Checkbox(label=_i18n("auto_shift_label"), value=True, scale=1)
402
+ auto_mix_acc = gr.Checkbox(label=_i18n("auto_mix_acc_label"), value=True, scale=1)
403
  with gr.Row():
404
+ use_fp16 = gr.Checkbox(label=_i18n("use_fp16_label"), value=True, scale=1)
405
+ pitch_shift = gr.Slider(label=_i18n("pitch_shift_label"), value=0, minimum=-36, maximum=36, step=1)
406
+ n_step = gr.Slider(label=_i18n("n_step_label"), value=32, minimum=1, maximum=200, step=1)
407
+ cfg = gr.Slider(label=_i18n("cfg_label"), value=1.0, minimum=0.0, maximum=10.0, step=0.1)
408
+ seed_input = gr.Slider(label=_i18n("seed_label"), value=42, minimum=0, maximum=10000, step=1)
409
 
410
  # ── Right column: output ──
411
  with gr.Column(scale=1):
412
+ output_audio = gr.Audio(label=_i18n("output_audio_label"), type="filepath", interactive=False)
413
  svc_state = gr.State(value=None)
414
  gr.Examples(
415
  examples=EXAMPLE_LIST,