From 5132f86cdc9cb623014589c05cf164a9577d2b50 Mon Sep 17 00:00:00 2001 From: qitian Date: Tue, 7 Jan 2025 14:04:18 +0800 Subject: [PATCH] add mutil language --- locales/zh.json | 46 +++++++++++++++++++++++++ modules/core.py | 4 ++- modules/gettext.py | 26 ++++++++++++++ modules/ui.py | 84 ++++++++++++++++++++++++---------------------- 4 files changed, 119 insertions(+), 41 deletions(-) create mode 100644 locales/zh.json create mode 100644 modules/gettext.py diff --git a/locales/zh.json b/locales/zh.json new file mode 100644 index 0000000..c029898 --- /dev/null +++ b/locales/zh.json @@ -0,0 +1,46 @@ +{ + "Source x Target Mapper": "Source x Target Mapper", + "select an source image": "选择一个源图像", + "Preview": "预览", + "select an target image or video": "选择一个目标图像或视频", + "save image output file": "保存图像输出文件", + "save video output file": "保存视频输出文件", + "select an target image": "选择一个目标图像", + "source": "源", + "Select a target": "选择一个目标", + "Select a face": "选择一张脸", + "Keep audio": "保留音频", + "Face Enhancer": "面纹增强器", + "Many faces": "多脸", + "Show FPS": "显示帧率", + "Keep fps": "保持帧率", + "Keep frames": "保持帧数", + "Fix Blueish Cam": "修复偏蓝的摄像头", + "Mouth Mask": "口罩", + "Show Mouth Mask Box": "显示口罩盒", + "Start": "开始", + "Live": "直播", + "Destroy": "结束", + "Map faces": "识别人脸", + "Processing...": "处理中...", + "Processing succeed!": "处理成功!", + "Processing ignored!": "处理被忽略!", + "Failed to start camera": "启动相机失败", + "Please complete pop-up or close it.": "请先完成弹出窗口或者关闭它", + "Getting unique faces": "获取独特面部", + "Please select a source image first": "请先选择一个源图像", + "No faces found in target": "目标图像中没有人脸", + "Add": "添加", + "Clear": "清除", + "Submit": "确认", + "Select source image": "请选取源图像", + "Select target image": "请选取目标图像", + "Please provide mapping!": "请提供映射", + "Atleast 1 source with target is required!": "至少需要一个来源图像与目标图像相关!", + "At least 1 source with target is required!": "至少需要一个来源图像与目标图像相关!", + "Face could not be detected in last upload!": "最近上传的图像中没有检测到人脸!", + "Select Camera:": "选择摄像头", + "All mappings cleared!": "所有映射均已清除!", + "Mappings successfully submitted!": "成功提交映射!", + "Source x Target Mapper is already open.": "源 x 目标映射器已打开。" +} \ No newline at end of file diff --git a/modules/core.py b/modules/core.py index ee67402..b6ef9b8 100644 --- a/modules/core.py +++ b/modules/core.py @@ -44,6 +44,7 @@ def parse_args() -> None: program.add_argument('--mouth-mask', help='mask the mouth region', dest='mouth_mask', action='store_true', default=False) program.add_argument('--video-encoder', help='adjust output video encoder', dest='video_encoder', default='libx264', choices=['libx264', 'libx265', 'libvpx-vp9']) program.add_argument('--video-quality', help='adjust output video quality', dest='video_quality', type=int, default=18, choices=range(52), metavar='[0-51]') + program.add_argument('-l', '--lang', help='Ui language', default="en") program.add_argument('--live-mirror', help='The live camera display as you see it in the front-facing camera frame', dest='live_mirror', action='store_true', default=False) program.add_argument('--live-resizable', help='The live camera frame is resizable', dest='live_resizable', action='store_true', default=False) program.add_argument('--max-memory', help='maximum amount of RAM in GB', dest='max_memory', type=int, default=suggest_max_memory()) @@ -78,6 +79,7 @@ def parse_args() -> None: modules.globals.max_memory = args.max_memory modules.globals.execution_providers = decode_execution_providers(args.execution_provider) modules.globals.execution_threads = args.execution_threads + modules.globals.lang = args.lang #for ENHANCER tumbler: if 'face_enhancer' in args.frame_processor: @@ -253,5 +255,5 @@ def run() -> None: if modules.globals.headless: start() else: - window = ui.init(start, destroy) + window = ui.init(start, destroy, modules.globals.lang) window.mainloop() diff --git a/modules/gettext.py b/modules/gettext.py new file mode 100644 index 0000000..0334102 --- /dev/null +++ b/modules/gettext.py @@ -0,0 +1,26 @@ +import json +from pathlib import Path + +class LanguageManager: + def __init__(self, default_language="en"): + self.current_language = default_language + self.translations = {} + self.load_language(default_language) + + def load_language(self, language_code) -> bool: + """load language file""" + if language_code == "en": + return True + try: + file_path = Path(__file__).parent.parent / f"locales/{language_code}.json" + with open(file_path, "r", encoding="utf-8") as file: + self.translations = json.load(file) + self.current_language = language_code + return True + except FileNotFoundError: + print(f"Language file not found: {language_code}") + return False + + def _(self, key, default=None) -> str: + """get translate text""" + return self.translations.get(key, default if default else key) \ No newline at end of file diff --git a/modules/ui.py b/modules/ui.py index 21446db..8eb9289 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -26,6 +26,7 @@ from modules.utilities import ( has_image_extension, ) from modules.video_capture import VideoCapturer +from modules.gettext import LanguageManager import platform if platform.system() == "Windows": @@ -63,6 +64,7 @@ RECENT_DIRECTORY_SOURCE = None RECENT_DIRECTORY_TARGET = None RECENT_DIRECTORY_OUTPUT = None +_ = None preview_label = None preview_slider = None source_label = None @@ -77,9 +79,11 @@ target_label_dict_live = {} img_ft, vid_ft = modules.globals.file_types -def init(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.CTk: - global ROOT, PREVIEW +def init(start: Callable[[], None], destroy: Callable[[], None], lang: str) -> ctk.CTk: + global ROOT, PREVIEW, _ + lang_manager = LanguageManager(lang) + _ = lang_manager._ ROOT = create_root(start, destroy) PREVIEW = create_preview(ROOT) @@ -154,7 +158,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C target_label.place(relx=0.6, rely=0.1, relwidth=0.3, relheight=0.25) select_face_button = ctk.CTkButton( - root, text="Select a face", cursor="hand2", command=lambda: select_source_path() + root, text=_("Select a face"), cursor="hand2", command=lambda: select_source_path() ) select_face_button.place(relx=0.1, rely=0.4, relwidth=0.3, relheight=0.1) @@ -165,7 +169,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C select_target_button = ctk.CTkButton( root, - text="Select a target", + text=_("Select a target"), cursor="hand2", command=lambda: select_target_path(), ) @@ -174,7 +178,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C keep_fps_value = ctk.BooleanVar(value=modules.globals.keep_fps) keep_fps_checkbox = ctk.CTkSwitch( root, - text="Keep fps", + text=_("Keep fps"), variable=keep_fps_value, cursor="hand2", command=lambda: ( @@ -187,7 +191,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C keep_frames_value = ctk.BooleanVar(value=modules.globals.keep_frames) keep_frames_switch = ctk.CTkSwitch( root, - text="Keep frames", + text=_("Keep frames"), variable=keep_frames_value, cursor="hand2", command=lambda: ( @@ -200,7 +204,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C enhancer_value = ctk.BooleanVar(value=modules.globals.fp_ui["face_enhancer"]) enhancer_switch = ctk.CTkSwitch( root, - text="Face Enhancer", + text=_("Face Enhancer"), variable=enhancer_value, cursor="hand2", command=lambda: ( @@ -213,7 +217,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio) keep_audio_switch = ctk.CTkSwitch( root, - text="Keep audio", + text=_("Keep audio"), variable=keep_audio_value, cursor="hand2", command=lambda: ( @@ -226,7 +230,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces) many_faces_switch = ctk.CTkSwitch( root, - text="Many faces", + text=_("Many faces"), variable=many_faces_value, cursor="hand2", command=lambda: ( @@ -239,7 +243,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C color_correction_value = ctk.BooleanVar(value=modules.globals.color_correction) color_correction_switch = ctk.CTkSwitch( root, - text="Fix Blueish Cam", + text=_("Fix Blueish Cam"), variable=color_correction_value, cursor="hand2", command=lambda: ( @@ -256,7 +260,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C map_faces = ctk.BooleanVar(value=modules.globals.map_faces) map_faces_switch = ctk.CTkSwitch( root, - text="Map faces", + text=_("Map faces"), variable=map_faces, cursor="hand2", command=lambda: ( @@ -270,7 +274,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps) show_fps_switch = ctk.CTkSwitch( root, - text="Show FPS", + text=_("Show FPS"), variable=show_fps_value, cursor="hand2", command=lambda: ( @@ -283,7 +287,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask) mouth_mask_switch = ctk.CTkSwitch( root, - text="Mouth Mask", + text=_("Mouth Mask"), variable=mouth_mask_var, cursor="hand2", command=lambda: setattr(modules.globals, "mouth_mask", mouth_mask_var.get()), @@ -293,7 +297,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C show_mouth_mask_box_var = ctk.BooleanVar(value=modules.globals.show_mouth_mask_box) show_mouth_mask_box_switch = ctk.CTkSwitch( root, - text="Show Mouth Mask Box", + text=_("Show Mouth Mask Box"), variable=show_mouth_mask_box_var, cursor="hand2", command=lambda: setattr( @@ -303,22 +307,22 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C show_mouth_mask_box_switch.place(relx=0.6, rely=0.55) start_button = ctk.CTkButton( - root, text="Start", cursor="hand2", command=lambda: analyze_target(start, root) + root, text=_("Start"), cursor="hand2", command=lambda: analyze_target(start, root) ) start_button.place(relx=0.15, rely=0.80, relwidth=0.2, relheight=0.05) stop_button = ctk.CTkButton( - root, text="Destroy", cursor="hand2", command=lambda: destroy() + root, text=_("Destroy"), cursor="hand2", command=lambda: destroy() ) stop_button.place(relx=0.4, rely=0.80, relwidth=0.2, relheight=0.05) preview_button = ctk.CTkButton( - root, text="Preview", cursor="hand2", command=lambda: toggle_preview() + root, text=_("Preview"), cursor="hand2", command=lambda: toggle_preview() ) preview_button.place(relx=0.65, rely=0.80, relwidth=0.2, relheight=0.05) # --- Camera Selection --- - camera_label = ctk.CTkLabel(root, text="Select Camera:") + camera_label = ctk.CTkLabel(root, text=_("Select Camera:")) camera_label.place(relx=0.1, rely=0.86, relwidth=0.2, relheight=0.05) available_cameras = get_available_cameras() @@ -342,7 +346,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C live_button = ctk.CTkButton( root, - text="Live", + text=_("Live"), cursor="hand2", command=lambda: webcam_preview( root, @@ -416,7 +420,7 @@ def create_source_target_popup( global POPUP, popup_status_label POPUP = ctk.CTkToplevel(root) - POPUP.title("Source x Target Mapper") + POPUP.title(_("Source x Target Mapper")) POPUP.geometry(f"{POPUP_WIDTH}x{POPUP_HEIGHT}") POPUP.focus() @@ -440,7 +444,7 @@ def create_source_target_popup( button = ctk.CTkButton( scrollable_frame, - text="Select source image", + text=_("Select source image"), command=lambda id=id: on_button_click(map, id), width=DEFAULT_BUTTON_WIDTH, height=DEFAULT_BUTTON_HEIGHT, @@ -474,7 +478,7 @@ def create_source_target_popup( popup_status_label.grid(row=1, column=0, pady=15) close_button = ctk.CTkButton( - POPUP, text="Submit", command=lambda: on_submit_click(start) + POPUP, text=_("Submit"), command=lambda: on_submit_click(start) ) close_button.grid(row=2, column=0, pady=10) @@ -485,7 +489,7 @@ def update_popup_source( global source_label_dict source_path = ctk.filedialog.askopenfilename( - title="select an source image", + title=_("select an source image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) @@ -536,7 +540,7 @@ def create_preview(parent: ctk.CTkToplevel) -> ctk.CTkToplevel: preview = ctk.CTkToplevel(parent) preview.withdraw() - preview.title("Preview") + preview.title(_("Preview")) preview.configure() preview.protocol("WM_DELETE_WINDOW", lambda: toggle_preview()) preview.resizable(width=True, height=True) @@ -552,16 +556,16 @@ def create_preview(parent: ctk.CTkToplevel) -> ctk.CTkToplevel: def update_status(text: str) -> None: - status_label.configure(text=text) + status_label.configure(text=_(text)) ROOT.update() def update_pop_status(text: str) -> None: - popup_status_label.configure(text=text) + popup_status_label.configure(text=_(text)) def update_pop_live_status(text: str) -> None: - popup_status_label_live.configure(text=text) + popup_status_label_live.configure(text=_(text)) def update_tumbler(var: str, value: bool) -> None: @@ -580,7 +584,7 @@ def select_source_path() -> None: PREVIEW.withdraw() source_path = ctk.filedialog.askopenfilename( - title="select an source image", + title=_("select an source image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) @@ -623,7 +627,7 @@ def select_target_path() -> None: PREVIEW.withdraw() target_path = ctk.filedialog.askopenfilename( - title="select an target image or video", + title=_("select an target image or video"), initialdir=RECENT_DIRECTORY_TARGET, filetypes=[img_ft, vid_ft], ) @@ -647,7 +651,7 @@ def select_output_path(start: Callable[[], None]) -> None: if is_image(modules.globals.target_path): output_path = ctk.filedialog.asksaveasfilename( - title="save image output file", + title=_("save image output file"), filetypes=[img_ft], defaultextension=".png", initialfile="output.png", @@ -655,7 +659,7 @@ def select_output_path(start: Callable[[], None]) -> None: ) elif is_video(modules.globals.target_path): output_path = ctk.filedialog.asksaveasfilename( - title="save video output file", + title=_("save video output file"), filetypes=[vid_ft], defaultextension=".mp4", initialfile="output.mp4", @@ -958,7 +962,7 @@ def create_source_target_popup_for_webcam( global POPUP_LIVE, popup_status_label_live POPUP_LIVE = ctk.CTkToplevel(root) - POPUP_LIVE.title("Source x Target Mapper") + POPUP_LIVE.title(_("Source x Target Mapper")) POPUP_LIVE.geometry(f"{POPUP_LIVE_WIDTH}x{POPUP_LIVE_HEIGHT}") POPUP_LIVE.focus() @@ -983,14 +987,14 @@ def create_source_target_popup_for_webcam( popup_status_label_live = ctk.CTkLabel(POPUP_LIVE, text=None, justify="center") popup_status_label_live.grid(row=1, column=0, pady=15) - add_button = ctk.CTkButton(POPUP_LIVE, text="Add", command=lambda: on_add_click()) + add_button = ctk.CTkButton(POPUP_LIVE, text=_("Add"), command=lambda: on_add_click()) add_button.place(relx=0.1, rely=0.92, relwidth=0.2, relheight=0.05) - clear_button = ctk.CTkButton(POPUP_LIVE, text="Clear", command=lambda: on_clear_click()) + clear_button = ctk.CTkButton(POPUP_LIVE, text=_("Clear"), command=lambda: on_clear_click()) clear_button.place(relx=0.4, rely=0.92, relwidth=0.2, relheight=0.05) close_button = ctk.CTkButton( - POPUP_LIVE, text="Submit", command=lambda: on_submit_click() + POPUP_LIVE, text=_("Submit"), command=lambda: on_submit_click() ) close_button.place(relx=0.7, rely=0.92, relwidth=0.2, relheight=0.05) @@ -1033,7 +1037,7 @@ def refresh_data(map: list): button = ctk.CTkButton( scrollable_frame, - text="Select source image", + text=_("Select source image"), command=lambda id=id: on_sbutton_click(map, id), width=DEFAULT_BUTTON_WIDTH, height=DEFAULT_BUTTON_HEIGHT, @@ -1050,7 +1054,7 @@ def refresh_data(map: list): button = ctk.CTkButton( scrollable_frame, - text="Select target image", + text=_("Select target image"), command=lambda id=id: on_tbutton_click(map, id), width=DEFAULT_BUTTON_WIDTH, height=DEFAULT_BUTTON_HEIGHT, @@ -1100,7 +1104,7 @@ def update_webcam_source( global source_label_dict_live source_path = ctk.filedialog.askopenfilename( - title="select an source image", + title=_("select an source image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) @@ -1152,7 +1156,7 @@ def update_webcam_target( global target_label_dict_live target_path = ctk.filedialog.askopenfilename( - title="select an target image", + title=_("select an target image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) @@ -1195,4 +1199,4 @@ def update_webcam_target( target_label_dict_live[button_num] = target_image else: update_pop_live_status("Face could not be detected in last upload!") - return map + return map \ No newline at end of file