diff --git a/.gitignore b/.gitignore index 1800114..e843527 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ cover/ # Translations *.mo *.pot +*.po~ # Django stuff: *.log diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3c35d6f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "i18n-ally.localesPaths": [ + "locales" + ] +} \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index c80bb60..e13b4f9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,5 @@ pipeline { - agent { label 'Pi4' } // Use Raspberry Pi 4 agent + agent { label 'Pi4' } // Use Raspberry Pi 4 agent running on Ubuntu Server LTS 24.04 environment { PIP_EXTRA_INDEX_URL = 'http://localhost:8090/simple/' // Local PyPI repo PACKAGE_NAME = 'kapitanbooru_uploader' // Your package name @@ -17,6 +17,18 @@ pipeline { sh '. venv/bin/activate && pip install --upgrade pip build twine' } } + + stage('Compile Translations') { + steps { + // Find all .po files under kapitanbooru_uploader/locales and compile them to .mo + sh ''' + find kapitanbooru_uploader/locales -name "*.po" -print0 | while IFS= read -r -d '' po; do + mo=$(echo "$po" | sed 's/\\.po$/.mo/'); + msgfmt "$po" -o "$mo"; + done + ''' + } + } stage('Build Package') { steps { diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..cc025b2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include kapitanbooru_uploader/locales *.mo diff --git a/kapitanbooru_uploader/I18N.py b/kapitanbooru_uploader/I18N.py new file mode 100644 index 0000000..2d52d65 --- /dev/null +++ b/kapitanbooru_uploader/I18N.py @@ -0,0 +1,50 @@ +import gettext +import locale +import os +from typing import Dict + + +class I18N: + def __init__(self, locale_dir=None): + # If no locale_dir is provided, use the locales folder relative to this file. + if locale_dir is None: + current_dir = os.path.dirname(os.path.abspath(__file__)) + locale_dir = os.path.join(current_dir, "locales") + self.locale_dir = locale_dir + self.languages = {"en": "English", "pl": "Polski"} + self.current_lang = "en" + self.translations: Dict[str, str] = {} + self.load_translations() + + def load_translations(self): + """Load all available translations""" + for lang in self.languages: + try: + trans = gettext.translation( + "messages", localedir=self.locale_dir, languages=[lang] + ) + self.translations[lang] = trans.gettext + except FileNotFoundError: + self.translations[lang] = lambda x: x + + def set_language(self, lang: str): + """Set application language""" + if lang in self.languages: + self.current_lang = lang + # For Windows language detection + try: + locale.setlocale(locale.LC_ALL, self.get_locale_code(lang)) + except locale.Error: + pass + + def get_locale_code(self, lang: str) -> str: + """Map languages to locale codes""" + return {"en": "en_US.UTF-8", "pl": "pl_PL.UTF-8"}.get(lang, "en_US.UTF-8") + + def __call__(self, text: str) -> str: + """Translate text using current language""" + return self.translations[self.current_lang](text) + + +# Shortcut for translation function +_ = I18N() diff --git a/kapitanbooru_uploader/ImageBrowser.py b/kapitanbooru_uploader/ImageBrowser.py index ba795f7..0d0ae9b 100644 --- a/kapitanbooru_uploader/ImageBrowser.py +++ b/kapitanbooru_uploader/ImageBrowser.py @@ -11,8 +11,9 @@ from typing import Dict, Tuple, Optional import networkx as nx import requests from PIL import Image, ImageTk, PngImagePlugin - import wdtagger as wdt + +from .I18N import _ from .ProgressFile import ProgressFile from .TagsRepo import TagsRepo from .autocomplete import TagManager @@ -26,11 +27,11 @@ class ProcessingDialog: def __init__(self, root, target_function, *args): self.root = root self.top = tk.Toplevel(root) - self.top.title("Processing...") + self.top.title(_("Processing...")) self.top.geometry("300x150") self.top.protocol("WM_DELETE_WINDOW", self.on_close) - self.label = tk.Label(self.top, text="Processing, please wait...") + self.label = tk.Label(self.top, text=_("Processing, please wait...")) self.label.pack(pady=10) # Start with indeterminate progress bar @@ -38,8 +39,15 @@ class ProcessingDialog: self.progress.pack(pady=10, fill="x") self.progress.start(10) + sig = inspect.signature(target_function) + if "secondary_progress_queue" in sig.parameters: + self.sub_progress = ttk.Progressbar(self.top, mode="indeterminate") + self.sub_progress.pack(pady=10, fill="x") + self.sub_progress.start(10) + # Setup communication queue and periodic checker self.queue = queue.Queue() + self.sub_queue = queue.Queue() self.running = True self.cancel_event = threading.Event() # Cancellation flag self.thread = threading.Thread( @@ -58,6 +66,10 @@ class ProcessingDialog: self.progress.config(mode=msg[1]) if msg[1] == "determinate": self.progress["value"] = 0 + self.progress.stop() + elif msg[1] == "indeterminate": + self.progress["value"] = 0 + self.progress.start() elif msg[0] == "max": self.progress["maximum"] = msg[1] elif msg[0] == "progress": @@ -69,6 +81,26 @@ class ProcessingDialog: except queue.Empty: break + try: + msg = self.sub_queue.get_nowait() + + if msg[0] == "mode": + self.sub_progress.config(mode=msg[1]) + if msg[1] == "determinate": + self.sub_progress["value"] = 0 + self.sub_progress.stop() + elif msg[1] == "indeterminate": + self.sub_progress["value"] = 0 + self.sub_progress.start() + elif msg[0] == "max": + self.sub_progress["maximum"] = msg[1] + elif msg[0] == "progress": + self.sub_progress["value"] = msg[1] + + self.top.update_idletasks() + except queue.Empty: + break + if self.running: self.top.after(100, self.process_queue) @@ -79,6 +111,8 @@ class ProcessingDialog: kwargs = {} if "progress_queue" in sig.parameters: kwargs["progress_queue"] = self.queue + if "secondary_progress_queue" in sig.parameters: + kwargs["secondary_progress_queue"] = self.sub_queue if "cancel_event" in sig.parameters: kwargs["cancel_event"] = self.cancel_event target_function(*args, **kwargs) @@ -91,7 +125,7 @@ class ProcessingDialog: self.running = False self.progress.stop() self.top.after(0, self.top.destroy) - self.thread.join() + self.root.after(100, self.thread.join) def on_close(self): """Handle manual window closure""" @@ -105,6 +139,7 @@ class ImageBrowser(tk.Tk): super().__init__() self.title("Kapitanbooru Uploader") self.geometry("900x600") + self.version = "0.4.0" self.settings = Settings() self.tags_repo = TagsRepo(self.settings) @@ -169,6 +204,15 @@ class ImageBrowser(tk.Tk): self.create_widgets() self.bind_events() + def reload_ui(self): + """Reload UI components with new language""" + # Destroy current widgets + for widget in self.winfo_children(): + widget.destroy() + # Rebuild UI + self.create_menu() + self.create_widgets() + def load_implication_graph(self) -> nx.DiGraph: G = nx.DiGraph() conn = self.tags_repo.get_conn() @@ -249,7 +293,7 @@ class ImageBrowser(tk.Tk): png_tags = set(parse_parameters(parameters, self.tags_repo).split()) img.close() except Exception as e: - print("Błąd przy otwieraniu pliku", file_path, ":", e) + print(_("Błąd przy otwieraniu pliku"), file_path, ":", e) png_tags = set() # Pobierz tagi z Taggera – sprawdzając cache @@ -291,10 +335,10 @@ class ImageBrowser(tk.Tk): self.tagger_cache[md5] = result self.tagger_processed.add(md5) self.after(0, self.update_status_bar) - print(f"Tagger przetworzył: {file_path}") + print(_("Tagger przetworzył:"), f"{file_path}") return result except Exception as e: - print("Błąd Taggera dla", file_path, ":", e) + print(_("Błąd Taggera dla"), file_path, ":", e) def map_tagger_rating(self, result: wdt.Result) -> str: """ @@ -320,37 +364,98 @@ class ImageBrowser(tk.Tk): self.file_menu = tk.Menu(menubar, tearoff=0) # File menu items - create references for items we need to control - self.file_menu.add_command(label="Otwórz folder", command=self.select_folder) + self.file_menu.add_command(label=_("Otwórz folder"), command=self.select_folder) self.file_menu.add_separator() self.file_menu.add_command( - label="Wyślij", command=self.upload_current_image, state=tk.DISABLED + label=_("Wyślij"), command=self.upload_current_image, state=tk.DISABLED ) self.file_menu.add_command( - label="Wyślij wszystko", command=self.upload_all_files, state=tk.DISABLED + label=_("Wyślij wszystko"), command=self.upload_all_files, state=tk.DISABLED ) self.file_menu.add_separator() self.file_menu.add_command( - label="Podmień tagi", command=self.edit_current_image, state=tk.DISABLED + label=_("Podmień tagi"), command=self.edit_current_image, state=tk.DISABLED ) self.file_menu.add_command( - label="Otwórz post", command=self.view_current_post, state=tk.DISABLED + label=_("Otwórz post"), command=self.view_current_post, state=tk.DISABLED ) self.file_menu.add_separator() - self.file_menu.add_command(label="Zakończ", command=self.quit) + self.file_menu.add_command(label=_("Zakończ"), command=self.quit) - menubar.add_cascade(label="Plik", menu=self.file_menu) + menubar.add_cascade(label=_("Plik"), menu=self.file_menu) # Options menu options_menu = tk.Menu(menubar, tearoff=0) - options_menu.add_command(label="Ustawienia", command=self.open_settings) + options_menu.add_command(label=_("Ustawienia"), command=self.open_settings) options_menu.add_separator() options_menu.add_command( - label="Wyczyść cache Taggera", command=self.clear_cache + label=_("Wyczyść cache Taggera"), command=self.clear_cache ) options_menu.add_command( - label="Zregeneruj bazę tagów", command=self.regenerate_tags_db + label=_("Zregeneruj bazę tagów"), command=self.regenerate_tags_db ) - menubar.add_cascade(label="Opcje", menu=options_menu) + menubar.add_cascade(label=_("Opcje"), menu=options_menu) + + help_menu = tk.Menu(menubar, tearoff=0) + help_menu.add_command(label=_("About"), command=self.show_about) + menubar.add_cascade(label=_("Help"), menu=help_menu) + + def show_about(self): + """Multilingual About window (updated version)""" + about = tk.Toplevel(self) + about.title(_("About Kapitanbooru Uploader")) + about.resizable(False, False) + + # Window content + frame = ttk.Frame(about, padding=10) + frame.pack(fill="both", expand=True) + + ttk.Label( + frame, text="Kapitanbooru Uploader", font=("TkDefaultFont", 16, "bold") + ).grid(row=0, column=0, sticky=tk.W) + + content = [ + (f"Version {self.version}", 1), + ("", 2), + (_("A GUI application for uploading images to KapitanBooru."), 3), + (_("Features include image upload, tag management, automatic"), 4), + (_("tagging with wdtagger, and cache management."), 5), + ("", 6), + (_("Authors:"), 7), + ("Michał Leśniak", 8), + ("", 9), + (_("License: MIT License"), 10), + (f"Copyright © 2025 Michał Leśniak", 11), + ("", 12), + ] + + for text, row in content: + ttk.Label(frame, text=text).grid(row=row, column=0, sticky=tk.W) + + # Repository link + repo_frame = ttk.Frame(frame) + repo_frame.grid(row=13, sticky=tk.W) + ttk.Label(repo_frame, text=_("Repository:")).pack(side=tk.LEFT) + repo_url = "https://git.mlesniak.pl/kapitan/kapitanbooru-uploader" + repo = ttk.Label(repo_frame, text=repo_url, cursor="hand2", foreground="blue") + repo.pack(side=tk.LEFT, padx=(5, 0)) + repo.bind("", lambda e: open_webbrowser(repo_url, self.settings)) + + # Website link + website_frame = ttk.Frame(frame) + website_frame.grid(row=14, sticky=tk.W) + ttk.Label(website_frame, text=_("Website:")).pack(side=tk.LEFT) + website_url = "https://booru.mlesniak.pl" + website = ttk.Label( + website_frame, text=website_url, cursor="hand2", foreground="blue" + ) + website.pack(side=tk.LEFT, padx=(5, 0)) + website.bind( + "", lambda e: open_webbrowser(website_url, self.settings) + ) + + # Close button + ttk.Button(about, text=_("Close"), command=about.destroy).pack(pady=10) def regenerate_tags_db(self): self.processing_dialog = ProcessingDialog(self, self.tags_repo.regenerate_db) @@ -358,41 +463,44 @@ class ImageBrowser(tk.Tk): def clear_cache(self): res, err = self.tagger_cache.clear_cache() if res: - messagebox.showinfo("Cache", "Cache Taggera zostało wyczyszczone.") + messagebox.showinfo(_("Cache"), _("Cache Taggera zostało wyczyszczone.")) else: - messagebox.showerror("Cache", f"Błąd przy czyszczeniu cache: {err}") + messagebox.showerror( + _("Cache"), f"{_('Błąd przy czyszczeniu cache:')} {err}" + ) def open_settings(self): settings_window = tk.Toplevel(self) - settings_window.title("Ustawienia") - settings_window.geometry("300x350") + settings_window.title(_("Ustawienia")) + settings_window.geometry("300x430") # Enlarged vertically + settings_window.resizable(False, False) # Disable resizing settings_window.grab_set() - lbl_login = tk.Label(settings_window, text="Login:") + lbl_login = tk.Label(settings_window, text=_("Login:")) lbl_login.pack(pady=(10, 0)) entry_login = tk.Entry(settings_window) entry_login.pack(pady=(0, 10), padx=10, fill="x") entry_login.insert(0, self.settings.username) - lbl_password = tk.Label(settings_window, text="Hasło:") + lbl_password = tk.Label(settings_window, text=_("Hasło:")) lbl_password.pack(pady=(10, 0)) entry_password = tk.Entry(settings_window, show="*") entry_password.pack(pady=(0, 10), padx=10, fill="x") entry_password.insert(0, self.settings.password) - lbl_base_url = tk.Label(settings_window, text="Base URL:") + lbl_base_url = tk.Label(settings_window, text=_("Base URL:")) lbl_base_url.pack(pady=(10, 0)) entry_base_url = tk.Entry(settings_window) entry_base_url.pack(pady=(0, 10), padx=10, fill="x") entry_base_url.insert(0, self.settings.base_url) - lbl_default_tags = tk.Label(settings_window, text="Default Tags:") + lbl_default_tags = tk.Label(settings_window, text=_("Default Tags:")) lbl_default_tags.pack(pady=(10, 0)) entry_default_tags = tk.Entry(settings_window) entry_default_tags.pack(pady=(0, 10), padx=10, fill="x") entry_default_tags.insert(0, self.settings.default_tags) - lbl_browser = tk.Label(settings_window, text="Browser:") + lbl_browser = tk.Label(settings_window, text=_("Browser:")) lbl_browser.pack(pady=(10, 0)) cb_browser = ttk.Combobox( settings_window, @@ -406,16 +514,39 @@ class ImageBrowser(tk.Tk): ) ) + lbl_lang = tk.Label(settings_window, text=_("Language:")) + lbl_lang.pack(pady=(10, 0)) + cb_lang = ttk.Combobox( + settings_window, + values=list(self.settings.i18n.languages.values()), + state="readonly", + ) + cb_lang.pack(pady=(0, 10), padx=10, fill="x") + cb_lang.set( + self.settings.i18n.languages.get(self.settings.i18n.current_lang, "en") + ) + def save_and_close(): self.settings.username = entry_login.get() self.settings.password = entry_password.get() self.settings.base_url = entry_base_url.get() self.settings.default_tags = entry_default_tags.get() self.settings.browser = self.settings.installed_browsers[cb_browser.get()] + self.settings.i18n.set_language( + next( + ( + lang + for lang, name in self.settings.i18n.languages.items() + if name == cb_lang.get() + ), + "en", + ) + ) self.settings.save_settings() settings_window.destroy() + self.reload_ui() - btn_save = tk.Button(settings_window, text="Zapisz", command=save_and_close) + btn_save = tk.Button(settings_window, text=_("Zapisz"), command=save_and_close) btn_save.pack(pady=10) def create_widgets(self): @@ -457,7 +588,7 @@ class ImageBrowser(tk.Tk): right_frame.grid_rowconfigure(4, weight=0) # Upload Panel # PNG Tags – widget Text z scrollbar - png_frame = tk.LabelFrame(right_frame, text="PNG Tags") + png_frame = tk.LabelFrame(right_frame, text=_("PNG Tags")) png_frame.grid(row=0, column=0, sticky=tk.EW, padx=5, pady=5) png_frame.grid_columnconfigure(0, weight=1) self.png_tags_text = tk.Text(png_frame, wrap=tk.WORD) @@ -469,7 +600,7 @@ class ImageBrowser(tk.Tk): ) # Tagger Tags – widget Text z scrollbar - tagger_frame = tk.LabelFrame(right_frame, text="Tagger Tags") + tagger_frame = tk.LabelFrame(right_frame, text=_("Tagger Tags")) tagger_frame.grid(row=1, column=0, sticky=tk.EW, padx=5, pady=5) tagger_frame.grid_columnconfigure(0, weight=1) self.tagger_tags_text = tk.Text(tagger_frame, wrap=tk.WORD) @@ -483,7 +614,7 @@ class ImageBrowser(tk.Tk): ) # Manual Tags – Entry (stała wysokość) - manual_frame = tk.LabelFrame(right_frame, text="Manual Tags") + manual_frame = tk.LabelFrame(right_frame, text=_("Manual Tags")) manual_frame.grid(row=2, column=0, sticky=tk.NSEW, padx=5, pady=5) self.manual_tags_manager = TagManager( manual_frame, self.settings, self.tags_repo, self.update_final_tags @@ -491,7 +622,7 @@ class ImageBrowser(tk.Tk): self.manual_tags_manager.pack(fill=tk.BOTH, expand=True) # Final Tags – widget Text z scrollbar, który rozszerza się - final_frame = tk.LabelFrame(right_frame, text="Final Tags") + final_frame = tk.LabelFrame(right_frame, text=_("Final Tags")) final_frame.grid(row=3, column=0, sticky=tk.NSEW, padx=5, pady=5) final_frame.grid_rowconfigure(0, weight=1) final_frame.grid_columnconfigure(0, weight=1) @@ -511,12 +642,12 @@ class ImageBrowser(tk.Tk): ) self.rating_dropdown.pack(side=tk.LEFT, padx=5) self.upload_button = tk.Button( - upload_frame, text="Upload", command=self.upload_current_image + upload_frame, text=_("Wyślij"), command=self.upload_current_image ) self.upload_button.pack(side=tk.LEFT, padx=5) self.upload_button.config(state=tk.DISABLED) self.view_post_button = tk.Button( - upload_frame, text="Wyświetl", command=self.view_current_post + upload_frame, text=_("Wyświetl"), command=self.view_current_post ) self.view_post_button.pack(side=tk.LEFT, padx=5) self.view_post_button.config(state=tk.DISABLED) @@ -533,9 +664,9 @@ class ImageBrowser(tk.Tk): def update_status_bar(self): status_text = ( - f"Przetworzono tagi: {len(self.tagger_processed)}/{self.total_files} plików | " - f"Zweryfikowano status uploadu: {self.upload_verified}/{self.total_files} plików | " - f"Zuploadowano: {self.uploaded_count}/{self.upload_verified} plików" + f"{_('Przetworzono tagi:')} {len(self.tagger_processed)}/{self.total_files} {_('plików')} | " + f"{_('Zweryfikowano status uploadu:')} {self.upload_verified}/{self.total_files} {_('plików')} | " + f"{_('Zuploadowano:')} {self.uploaded_count}/{self.upload_verified} {_('plików')}" ) self.status_label.config(text=status_text) @@ -563,7 +694,7 @@ class ImageBrowser(tk.Tk): Otwiera okno dialogowe wyboru folderu z obrazkami i wczytuje pliki PNG z wybranego folderu. """ - folder = filedialog.askdirectory(title="Wybierz folder z obrazkami PNG") + folder = filedialog.askdirectory(title=_("Wybierz folder z obrazkami PNG")) if folder: self.folder_path = folder self.load_images() @@ -593,7 +724,9 @@ class ImageBrowser(tk.Tk): self.show_image(0) self.post_load_processing() else: - messagebox.showinfo("Informacja", "Brak plików PNG w wybranym folderze.") + messagebox.showinfo( + _("Informacja"), _("Brak plików PNG w wybranym folderze.") + ) def post_load_processing(self): """ @@ -675,7 +808,7 @@ class ImageBrowser(tk.Tk): self.uploaded[file_path] = False self.after(0, self.update_status_bar) except Exception as e: - print("Błąd podczas sprawdzania paczki uploadu:", e) + print(_("Błąd podczas sprawdzania paczki uploadu:"), e) self.after(100, self.join_check_uploaded_files_thread) def update_button_states(self): @@ -716,14 +849,14 @@ class ImageBrowser(tk.Tk): post_ops_state = tk.NORMAL if post_id else tk.DISABLED # Update menu items - self.file_menu.entryconfig("Wyślij wszystko", state=send_all_state) - self.file_menu.entryconfig("Podmień tagi", state=post_ops_state) - self.file_menu.entryconfig("Otwórz post", state=post_ops_state) + self.file_menu.entryconfig(_("Wyślij wszystko"), state=send_all_state) + self.file_menu.entryconfig(_("Podmień tagi"), state=post_ops_state) + self.file_menu.entryconfig(_("Otwórz post"), state=post_ops_state) # Update buttons self.upload_button.config( state=tk.NORMAL if has_current else tk.DISABLED, - text="Podmień tagi" if post_id else "Wyślij", + text=_("Podmień tagi") if post_id else _("Wyślij"), command=self.edit_current_image if post_id else self.upload_current_image, ) self.view_post_button.config(state=post_ops_state) @@ -731,7 +864,7 @@ class ImageBrowser(tk.Tk): # Special case for "Wyślij" menu item wyślij_state = tk.DISABLED if post_id else tk.NORMAL self.file_menu.entryconfig( - "Wyślij", state=wyślij_state if has_current else tk.DISABLED + _("Wyślij"), state=wyślij_state if has_current else tk.DISABLED ) def view_current_post(self): @@ -751,7 +884,7 @@ class ImageBrowser(tk.Tk): for chunk in iter(lambda: f.read(chunk_size), b""): hash_md5.update(chunk) except Exception as e: - print("Błąd przy obliczaniu MD5:", e) + print(_("Błąd przy obliczaniu MD5:"), e) return "" return hash_md5.hexdigest() @@ -807,7 +940,7 @@ class ImageBrowser(tk.Tk): self.tagger_thread_idx += 1 thread.start() except Exception as e: - messagebox.showerror("Błąd", f"Nie można załadować obrazka:\n{e}") + messagebox.showerror(_("Błąd"), f"{_('Nie można załadować obrazka:')}\n{e}") def edit_current_image(self): """ @@ -1016,13 +1149,21 @@ class ImageBrowser(tk.Tk): if tag in self.implication_graph: implied_by_selected.update(nx.descendants(self.implication_graph, tag)) else: - print(f"Warning: Tag '{tag}' not found in implication graph") + print( + _("Warning: Tag '{tag}' not found in implication graph").format( + tag=tag + ) + ) self.missing_tags.add(tag) # Log missing tags for tag in selected_png_tags: if tag in self.implication_graph: implied_by_selected.update(nx.descendants(self.implication_graph, tag)) else: - print(f"Warning: Tag '{tag}' not found in implication graph") + print( + _("Warning: Tag '{tag}' not found in implication graph").format( + tag=tag + ) + ) self.missing_tags.add(tag) # Log missing tags # Build visible list @@ -1249,7 +1390,7 @@ class ImageBrowser(tk.Tk): # Ustaw komunikat, że Tagger pracuje self.tagger_tags_text.config(state=tk.NORMAL) self.tagger_tags_text.delete("1.0", tk.END) - self.tagger_tags_text.insert("1.0", "Tagger przetwarza...") + self.tagger_tags_text.insert("1.0", _("Tagger przetwarza...")) self.tagger_tags_text.config(state=tk.DISABLED) file_path = self.image_files[self.current_index] result = self.get_tagger_results(file_path) @@ -1264,14 +1405,21 @@ class ImageBrowser(tk.Tk): file_path, final_tags=None, final_rating=None, - progress_queue=None, - cancel_event=None, + progress_queue: Optional[queue.Queue] = None, + cancel_event: Optional[threading.Event] = None, ): base_file_name = os.path.basename(file_path) if progress_queue: progress_queue.put(("mode", "determinate")) progress_queue.put(("max", 100)) - progress_queue.put(("label", f"Wysyłam plik {base_file_name}...")) + progress_queue.put( + ( + "label", + _("Wysyłam plik {base_file_name}...").format( + base_file_name=base_file_name + ), + ) + ) url = self.settings.base_url.rstrip("/") + "/api/danbooru/add_post" tags = ( self.final_tags_text.get("1.0", tk.END).strip() @@ -1308,22 +1456,29 @@ class ImageBrowser(tk.Tk): show_warn = False post_url = None if response.status_code in (200, 201): - message = "Upload zakończony powodzeniem!" + message = _("Wysyłanie zakończone powodzeniem!") post_url = response.headers.get("X-Danbooru-Location", None) elif response.status_code == 409: - message = f"Upload zakończony błędem.\nStatus: 409\nTreść: {response.headers.get('X-Danbooru-Errors', '')}" + message = _( + "Wysyłanie zakończone błędem.\nStatus: {status_code}\nTreść: {text}" + ).format( + status_code=response.status_code, + text=response.headers.get("X-Danbooru-Errors", ""), + ) post_url = response.headers.get("X-Danbooru-Location", None) show_warn = True else: - message = f"Upload zakończony błędem.\nStatus: {response.status_code}\nTreść: {response.text}" + message = _( + "Wysyłanie zakończone błędem.\nStatus: {status_code}\nTreść: {text}" + ).format(status_code=response.status_code, text=response.text) show_warn = True # Aktualizacja wyglądu listy – musimy użyć domyślnych argumentów w lambdzie, aby zachować bieżący indeks if show_warn: if not final_tags: - messagebox.showwarning("Upload", message) + messagebox.showwarning(_("Wysyłanie"), message) else: if not final_tags: - messagebox.showinfo("Upload", message) + messagebox.showinfo(_("Wysyłanie"), message) self.after( 0, lambda idx=self.image_files.index( @@ -1336,7 +1491,7 @@ class ImageBrowser(tk.Tk): self.uploaded_count += 1 self.after(0, self.update_status_bar) except Exception as e: - messagebox.showerror("Błąd uploadu", str(e)) + messagebox.showerror(_("Błąd wysyłania"), str(e)) finally: self.upload_button.after(0, self.update_button_states) @@ -1356,20 +1511,27 @@ class ImageBrowser(tk.Tk): if not post_id: messagebox.showerror( - "Błąd edycji", "Post nie został znaleziony dla tego pliku" + _("Błąd edycji"), _("Post nie został znaleziony dla tego pliku") ) return if progress_queue: progress_queue.put(("mode", "determinate")) progress_queue.put(("max", 100)) - progress_queue.put(("label", f"Aktualizuję tagi dla {base_file_name}...")) + progress_queue.put( + ( + "label", + _("Aktualizuję tagi dla {base_file_name}...").format( + base_file_name=base_file_name + ), + ) + ) try: # Check for cancellation before starting the operation. if cancel_event is not None and cancel_event.is_set(): if progress_queue: - progress_queue.put(("label", "Operacja anulowana")) + progress_queue.put(("label", _("Operacja anulowana"))) return # Get authentication session and token @@ -1379,7 +1541,7 @@ class ImageBrowser(tk.Tk): # Check cancellation after login if needed. if cancel_event is not None and cancel_event.is_set(): if progress_queue: - progress_queue.put(("label", "Operacja anulowana")) + progress_queue.put(("label", _("Operacja anulowana"))) return # Prepare tags and rating @@ -1410,7 +1572,7 @@ class ImageBrowser(tk.Tk): # Check for cancellation before sending the update request. if cancel_event is not None and cancel_event.is_set(): if progress_queue: - progress_queue.put(("label", "Operacja anulowana")) + progress_queue.put(("label", _("Operacja anulowana"))) return # Send update request @@ -1420,23 +1582,23 @@ class ImageBrowser(tk.Tk): if response.status_code == 302: if progress_queue: progress_queue.put(("progress", 100)) - message = "Tagi zostały zaktualizowane!" + message = _("Tagi zostały zaktualizowane!") if not final_tags: # Only show success if not bulk operation - messagebox.showinfo("Sukces edycji", message) + messagebox.showinfo(_("Sukces edycji"), message) # Update UI state self.after(0, self.update_button_states) return # Handle other status codes - error_msg = ( - f"Błąd podczas aktualizacji tagów\nStatus: {response.status_code}" + error_msg = _("Błąd podczas aktualizacji tagów\nStatus: {code}").format( + code=response.status_code ) if response.text: - error_msg += f"\nTreść: {response.text}" + error_msg += f"\n{_('Treść:')} {response.text}" messagebox.showerror("Błąd edycji", error_msg) except Exception as e: - messagebox.showerror("Krytyczny błąd edycji", str(e)) + messagebox.showerror(_("Krytyczny błąd edycji"), str(e)) finally: if progress_queue: progress_queue.put(("progress", 100)) @@ -1448,12 +1610,18 @@ class ImageBrowser(tk.Tk): i wywołuje upload_file. """ if not messagebox.askyesno( - "Potwierdzenie", - "Czy na pewno chcesz wrzucić wszystkie niewrzucone pliki?\nKażdy z nich zostanie oznaczony tagiem 'meta:auto_upload'.\nUpewnij się, że tagi są poprawne!", + _("Potwierdzenie"), + _( + "Czy na pewno chcesz wrzucić wszystkie niewrzucone pliki?\nKażdy z nich zostanie oznaczony tagiem 'meta:auto_upload'.\nUpewnij się, że tagi są poprawne!" + ), ): return - def worker(progress_queue: queue = None, cancel_event: threading.Event = None): + def worker( + progress_queue: queue.Queue = None, + cancel_event: threading.Event = None, + secondary_progress_queue: queue.Queue = None, + ): files_count = len(self.image_files) if progress_queue: progress_queue.put(("mode", "determinate")) @@ -1461,26 +1629,37 @@ class ImageBrowser(tk.Tk): file_idx = 0 for file_path in self.image_files: if progress_queue: - progress_queue.put(("progress", file_idx * 1.0 / files_count)) + progress_queue.put(("progress", file_idx * 100 / files_count)) progress_queue.put( ("label", f"Wysyłam plik {file_idx+1}/{files_count}...") ) if cancel_event is not None and cancel_event.is_set(): if progress_queue: - progress_queue.put(("label", f"Anulowano operację!")) + progress_queue.put(("label", _("Anulowano operację!"))) return if not self.uploaded.get(file_path, False): final_tags, final_rating = ( self.compute_final_tags_and_rating_for_file(file_path) ) print( - f"Uploading {file_path} z tagami: {final_tags} i ratingiem: {final_rating}" + _( + "Wysyłanie {file_path} z tagami: {final_tags} i ratingiem: {final_rating}" + ).format( + file_path=file_path, + final_tags=final_tags, + final_rating=final_rating, + ) ) self.upload_file( - file_path, final_tags=final_tags, final_rating=final_rating + file_path, + final_tags=final_tags, + final_rating=final_rating, + progress_queue=secondary_progress_queue, + cancel_event=cancel_event, ) + file_idx += 1 if progress_queue: - progress_queue.put(("label", f"Przesłano pliki!")) + progress_queue.put(("label", _("Przesłano pliki!"))) progress_queue.put(("progress", 100)) self.processing_dialog = ProcessingDialog(self, worker) diff --git a/kapitanbooru_uploader/ProgressFile.py b/kapitanbooru_uploader/ProgressFile.py index 7be10a3..36cdd68 100644 --- a/kapitanbooru_uploader/ProgressFile.py +++ b/kapitanbooru_uploader/ProgressFile.py @@ -1,3 +1,6 @@ +from .I18N import _ + + # Klasa pomocnicza do monitorowania postępu uploadu class ProgressFile: def __init__(self, f, callback, total_size, cancel_event=None): @@ -10,7 +13,7 @@ class ProgressFile: def read(self, size=-1): # Check for cancellation before reading more data if self.cancel_event is not None and self.cancel_event.is_set(): - raise Exception("Upload cancelled by user.") + raise Exception(_("Upload cancelled by user.")) data = self.f.read(size) self.read_bytes += len(data) diff --git a/kapitanbooru_uploader/TagsRepo.py b/kapitanbooru_uploader/TagsRepo.py index 7c49173..c4370e3 100644 --- a/kapitanbooru_uploader/TagsRepo.py +++ b/kapitanbooru_uploader/TagsRepo.py @@ -1,11 +1,15 @@ from collections import deque import json import os +import queue import sqlite3 -import time -import requests from pathlib import Path +import threading +import time +import requests + +from .I18N import _ from .settings import Settings # Stałe – auth_token (CSRF token) oraz ciasteczka @@ -51,7 +55,11 @@ class TagsRepo: regenerate = False if not Path(self.db_path).is_file(): regenerate = True - print(f"Database file not found: {self.db_path}, will regenerate DB") + print( + _("Database file not found: {path}, will regenerate DB").format( + path=self.db_path + ) + ) self.init_tags_db() if regenerate: self.regenerate_db() @@ -132,9 +140,11 @@ class TagsRepo: conn.commit() conn.close() except Exception as e: - print("Błąd przy inicjalizacji bazy tagów:", e) + print(_("Błąd przy inicjalizacji bazy tagów:"), e) - def regenerate_db(self, progress_queue=None, cancel_event=None): + def regenerate_db( + self, progress_queue: queue.Queue = None, cancel_event: threading.Event = None + ): """ Regenerate the database of tags, aliases, and tag implications. @@ -146,7 +156,7 @@ class TagsRepo: cursor = conn.cursor() if progress_queue: - progress_queue.put(("label", "Czyszczenie bazy danych...")) + progress_queue.put(("label", _("Czyszczenie bazy danych..."))) cursor.execute("DELETE FROM tags") cursor.execute("DELETE FROM tag_aliases") conn.commit() @@ -162,12 +172,19 @@ class TagsRepo: while True: if cancel_event and cancel_event.is_set(): if progress_queue: - progress_queue.put(("label", "Anulowano pobieranie tagów.")) + progress_queue.put(("label", _("Anulowano pobieranie tagów."))) conn.close() return if progress_queue: - progress_queue.put(("label", f"Pobieranie tagów (od ID {last_id})...")) + progress_queue.put( + ( + "label", + _("Pobieranie tagów (od ID {last_id})...").format( + last_id=last_id + ), + ) + ) start_time = time.monotonic() url = f"https://danbooru.donmai.us/tags.json?limit=1000&page=a{last_id}" response = requests.get(url) @@ -176,7 +193,9 @@ class TagsRepo: progress_queue.put( ( "label", - f"Błąd przy pobieraniu tagów od ID {last_id}: HTTP {response.status_code}", + _( + "Błąd przy pobieraniu tagów od ID {last_id}: HTTP {code}" + ).format(last_id=last_id, code=response.status_code), ) ) break @@ -189,7 +208,9 @@ class TagsRepo: for item in data: if cancel_event and cancel_event.is_set(): if progress_queue: - progress_queue.put(("label", "Anulowano przetwarzanie tagów.")) + progress_queue.put( + ("label", _("Anulowano przetwarzanie tagów.")) + ) conn.close() return tag_id = item.get("id") @@ -223,7 +244,9 @@ class TagsRepo: time.sleep(min_interval - elapsed) if progress_queue: - progress_queue.put(("label", f"Pobrano {len(data_list)} tagów...")) + progress_queue.put( + ("label", _("Pobrano {count} tagów...").format(count=len(data_list))) + ) data_list = sorted(data_list, key=lambda x: x[0]) data_list = [(idx,) + row for idx, row in enumerate(data_list)] @@ -244,13 +267,20 @@ class TagsRepo: while True: if cancel_event and cancel_event.is_set(): if progress_queue: - progress_queue.put(("label", "Anulowano pobieranie aliasów tagów.")) + progress_queue.put( + ("label", _("Anulowano pobieranie aliasów tagów.")) + ) conn.close() return if progress_queue: progress_queue.put( - ("label", f"Pobieranie aliasów tagów (od ID {last_id})...") + ( + "label", + _("Pobieranie aliasów tagów (od ID {last_id})...").format( + last_id=last_id + ), + ) ) start_time = time.monotonic() url = ( @@ -263,7 +293,9 @@ class TagsRepo: progress_queue.put( ( "label", - f"Błąd przy pobieraniu aliasów tagów od ID {last_id}: HTTP {response.status_code}", + _( + "Błąd przy pobieraniu aliasów tagów od ID {last_id}: HTTP {code}" + ).format(last_id=last_id, code=response.status_code), ) ) break @@ -277,7 +309,7 @@ class TagsRepo: if cancel_event and cancel_event.is_set(): if progress_queue: progress_queue.put( - ("label", "Anulowano przetwarzanie aliasów tagów.") + ("label", _("Anulowano przetwarzanie aliasów tagów.")) ) conn.close() return @@ -296,7 +328,12 @@ class TagsRepo: time.sleep(min_interval - elapsed) if progress_queue: - progress_queue.put(("label", f"Pobrano {len(data_list)} aliasów tagów...")) + progress_queue.put( + ( + "label", + _("Pobrano {count} aliasów tagów...").format(count=len(data_list)), + ) + ) data_list = sorted(data_list, key=lambda x: x[0]) data_list = [(idx,) + row for idx, row in enumerate(data_list)] @@ -335,14 +372,19 @@ class TagsRepo: if cancel_event and cancel_event.is_set(): if progress_queue: progress_queue.put( - ("label", "Anulowano pobieranie implikacji tagów.") + ("label", _("Anulowano pobieranie implikacji tagów.")) ) conn.close() return if progress_queue: progress_queue.put( - ("label", f"Pobieranie implikacji tagów (od ID {last_id})...") + ( + "label", + _("Pobieranie implikacji tagów (od ID {last_id})...").format( + last_id=last_id + ), + ) ) start_time = time.monotonic() url = f"https://danbooru.donmai.us/tag_implications.json?limit=1000&page=a{last_id}" @@ -352,7 +394,9 @@ class TagsRepo: progress_queue.put( ( "label", - f"Błąd przy pobieraniu implikacji tagów od ID {last_id}: HTTP {response.status_code}", + _( + "Błąd przy pobieraniu implikacji tagów od ID {last_id}: HTTP {code}" + ).format(last_id=last_id, code=response.status_code), ) ) break @@ -366,7 +410,7 @@ class TagsRepo: if cancel_event and cancel_event.is_set(): if progress_queue: progress_queue.put( - ("label", "Anulowano przetwarzanie implikacji tagów.") + ("label", _("Anulowano przetwarzanie implikacji tagów.")) ) conn.close() return @@ -411,7 +455,12 @@ class TagsRepo: if progress_queue: progress_queue.put( - ("label", f"Pobrano implikacje dla {len(tag_dict)} tagów...") + ( + "label", + _("Pobrano implikacje dla {count} tagów...").format( + count=len(tag_dict) + ), + ) ) # ----------------------- @@ -435,7 +484,7 @@ class TagsRepo: conn.close() if progress_queue: - progress_queue.put(("label", "Regeneracja bazy zakończona.")) + progress_queue.put(("label", _("Regeneracja bazy zakończona."))) def build_transitive_closure(self, tag_dict): closure = set() diff --git a/kapitanbooru_uploader/autocomplete.py b/kapitanbooru_uploader/autocomplete.py index 365ee65..8b702b4 100644 --- a/kapitanbooru_uploader/autocomplete.py +++ b/kapitanbooru_uploader/autocomplete.py @@ -1,6 +1,7 @@ import tkinter as tk from tkinter import font +from .I18N import _ from .TagsRepo import TagsRepo from .common import open_tag_wiki_url from .tag_processing import process_tag @@ -24,7 +25,37 @@ class AutocompleteEntry(tk.Entry): self.bind("", self.on_up) self.bind("", self.on_return) self.bind("", self.on_return) - self.bind("", lambda e: self.hide_listbox()) + self.bind("", self.on_focus_out) + + def on_focus_out(self, event): + self.after(10, self.delayed_focus_check) + + def delayed_focus_check(self): + try: + # Get current focus using Tk's internal command + focused_name = self.tk.call("focus") + if not focused_name: + self.hide_listbox() + return + + # Convert to widget object + focused_widget = self.nametowidget(focused_name) + + # Check if focus is in our listbox hierarchy + if self.listbox_window and self.is_child_of_listbox_window(focused_widget): + return + except (KeyError, tk.TclError): + pass + + self.hide_listbox() + + def is_child_of_listbox_window(self, widget): + current = widget + while current: + if current == self.listbox_window: + return True + current = current.master + return False def on_keyrelease(self, event): if event.keysym in ("Down", "Up", "Return", "Tab"): @@ -99,7 +130,7 @@ class AutocompleteEntry(tk.Entry): self.suggestion_map[display_text] = tag_insert return suggestions except Exception as e: - print("Błąd przy pobieraniu sugestii:", e) + print(_("Błąd przy pobieraniu sugestii:"), e) return [] def show_listbox(self): @@ -139,12 +170,22 @@ class AutocompleteEntry(tk.Entry): def on_listbox_click(self, event): if self.listbox: - index = self.listbox.curselection() - if index: - value = self.listbox.get(index) + # Process click first before changing focus + index = self.listbox.nearest(event.y) + if index >= 0: + # Get the display text from listbox + selected_display = self.listbox.get(index) + # Get the actual tag from the suggestion map + tag = self.suggestion_map.get(selected_display, selected_display) + + if self.callback: + self.callback(tag) + + # Clear the entry and hide listbox self.delete(0, tk.END) - self.insert(tk.END, value) - self.hide_listbox() + # Then explicitly manage focus + self.focus_set() # Use focus_set() instead of focus_force() + self.hide_listbox() return "break" def on_listbox_motion(self, event): diff --git a/kapitanbooru_uploader/common.py b/kapitanbooru_uploader/common.py index c37aace..0e0f834 100644 --- a/kapitanbooru_uploader/common.py +++ b/kapitanbooru_uploader/common.py @@ -3,10 +3,11 @@ import subprocess from bs4 import BeautifulSoup import requests +from .I18N import _ from .settings import Settings -def open_tag_wiki_url(tag, settings: Settings): +def open_tag_wiki_url(tag: str, settings: Settings): """Otwiera w przeglądarce URL strony wiki dla podanego tagu.""" # Usuń prefiksy for prefix in [ @@ -31,7 +32,7 @@ def open_webbrowser(url, settings: Settings): subprocess.run([settings.browser, url], check=True) return except Exception as e: - print("Błąd przy otwieraniu przeglądarki:", e) + print(_("Błąd przy otwieraniu przeglądarki:"), e) import webbrowser webbrowser.open(url) @@ -68,18 +69,18 @@ def login(settings: Settings): shm_session = session.cookies.get("shm_session") if not (shm_user and shm_session): - raise Exception("Login succeeded, but expected cookies were not set.") + raise Exception(_("Login succeeded, but expected cookies were not set.")) - print("Login successful. Cookies stored in session:") - print(f"shm_user: {shm_user}") - print(f"shm_session: {shm_session}") + print(_("Login successful. Cookies stored in session.")) return session else: - raise Exception(f"Login failed: {response.status_code} - {response.text}") + raise Exception( + f"{_('Login failed:')} {response.status_code} - {response.text}" + ) -def get_auth_token(session, settings): +def get_auth_token(session: requests.Session, settings: Settings) -> str: """ Given a logged-in session and settings, fetch the user page and extract the auth_token from the hidden input field. @@ -91,7 +92,7 @@ def get_auth_token(session, settings): # Retrieve the user identifier from cookies shm_user = session.cookies.get("shm_user") if not shm_user: - raise Exception("shm_user cookie not found; login might have failed.") + raise Exception(_("shm_user cookie not found; login might have failed.")) # Build the URL to fetch, e.g., /user/ user_url = f"{settings.base_url.rstrip('/')}/user/{shm_user}" @@ -108,7 +109,9 @@ def get_auth_token(session, settings): if response.status_code != 200: raise Exception( - f"Failed to load {user_url}, status code: {response.status_code}" + _("Failed to load {user_url}, status code: {code}").format( + user_url=user_url, code=response.status_code + ) ) # Parse the returned HTML with BeautifulSoup @@ -118,7 +121,7 @@ def get_auth_token(session, settings): auth_input = soup.find("input", {"name": "auth_token"}) if auth_input and auth_input.has_attr("value"): auth_token = auth_input["value"] - print(f"Found auth_token: {auth_token}") + print(_("Found auth_token:"), auth_token) return auth_token else: - raise Exception("auth_token not found in the HTML page.") + raise Exception(_("auth_token not found in the HTML page.")) diff --git a/kapitanbooru_uploader/locales/en/LC_MESSAGES/messages.po b/kapitanbooru_uploader/locales/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000..f676028 --- /dev/null +++ b/kapitanbooru_uploader/locales/en/LC_MESSAGES/messages.po @@ -0,0 +1,491 @@ +msgid "" +msgstr "" +"Project-Id-Version: Kapitanbooru Uploader 0.4.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-02 00:39+0100\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: autocomplete.py:133 +msgid "Błąd przy pobieraniu sugestii:" +msgstr "Error fetching suggestions:" + +#: common.py:35 +msgid "Błąd przy otwieraniu przeglądarki:" +msgstr "Error opening browser:" + +#: common.py:72 +msgid "Login succeeded, but expected cookies were not set." +msgstr "Login succeeded, but expected cookies were not set." + +#: common.py:74 +msgid "Login successful. Cookies stored in session." +msgstr "Login successful. Cookies stored in session." + +#: common.py:79 +msgid "Login failed:" +msgstr "Login failed:" + +#: common.py:95 +msgid "shm_user cookie not found; login might have failed." +msgstr "shm_user cookie not found; login might have failed." + +#: common.py:112 +#, python-brace-format +msgid "Failed to load {user_url}, status code: {code}" +msgstr "Failed to load {user_url}, status code: {code}" + +#: common.py:124 +msgid "Found auth_token:" +msgstr "Found auth_token:" + +#: common.py:127 +msgid "auth_token not found in the HTML page." +msgstr "auth_token not found in the HTML page." + +#: ImageBrowser.py:30 +msgid "Processing..." +msgstr "Processing..." + +#: ImageBrowser.py:34 +msgid "Processing, please wait..." +msgstr "Processing, please wait..." + +#: ImageBrowser.py:263 +msgid "Błąd przy otwieraniu pliku" +msgstr "Error opening file" + +#: ImageBrowser.py:305 +msgid "Tagger przetworzył:" +msgstr "Tagger processed:" + +#: ImageBrowser.py:308 +msgid "Błąd Taggera dla" +msgstr "Tagger error for" + +#: ImageBrowser.py:334 +msgid "Otwórz folder" +msgstr "Open folder" + +#: ImageBrowser.py:337 ImageBrowser.py:600 ImageBrowser.py:814 +#: ImageBrowser.py:822 +msgid "Wyślij" +msgstr "Send" + +#: ImageBrowser.py:340 ImageBrowser.py:807 +msgid "Wyślij wszystko" +msgstr "Send all" + +#: ImageBrowser.py:344 ImageBrowser.py:808 ImageBrowser.py:814 +msgid "Podmień tagi" +msgstr "Replace tags" + +#: ImageBrowser.py:347 ImageBrowser.py:809 +msgid "Otwórz post" +msgstr "Open post" + +#: ImageBrowser.py:350 +msgid "Zakończ" +msgstr "Finish" + +#: ImageBrowser.py:352 +msgid "Plik" +msgstr "File" + +#: ImageBrowser.py:356 ImageBrowser.py:430 +msgid "Ustawienia" +msgstr "Settings" + +#: ImageBrowser.py:359 +msgid "Wyczyść cache Taggera" +msgstr "Clear Tagger cache" + +#: ImageBrowser.py:362 +msgid "Zregeneruj bazę tagów" +msgstr "Regenerate tag database" + +#: ImageBrowser.py:364 +msgid "Opcje" +msgstr "Options" + +#: ImageBrowser.py:367 +msgid "About" +msgstr "About" + +#: ImageBrowser.py:368 +msgid "Help" +msgstr "Help" + +#: ImageBrowser.py:373 +msgid "About Kapitanbooru Uploader" +msgstr "About Kapitanbooru Uploader" + +#: ImageBrowser.py:386 +msgid "A GUI application for uploading images to KapitanBooru." +msgstr "A GUI application for uploading images to KapitanBooru." + +#: ImageBrowser.py:387 +msgid "Features include image upload, tag management, automatic" +msgstr "Features include image upload, tag management, automatic" + +#: ImageBrowser.py:388 +msgid "tagging with wdtagger, and cache management." +msgstr "tagging with wdtagger, and cache management." + +#: ImageBrowser.py:390 +msgid "Authors:" +msgstr "Authors:" + +#: ImageBrowser.py:393 +msgid "License: MIT License" +msgstr "License: MIT License" + +#: ImageBrowser.py:396 +msgid "Repository:" +msgstr "Repository:" + +#: ImageBrowser.py:397 +msgid "Website:" +msgstr "Website:" + +#: ImageBrowser.py:414 +msgid "Close" +msgstr "Close" + +#: ImageBrowser.py:422 ImageBrowser.py:425 +msgid "Cache" +msgstr "Cache" + +#: ImageBrowser.py:422 +msgid "Cache Taggera zostało wyczyszczone." +msgstr "Tagger cache has been cleared." + +#: ImageBrowser.py:425 +msgid "Błąd przy czyszczeniu cache:" +msgstr "Error clearing cache:" + +#: ImageBrowser.py:434 +msgid "Login:" +msgstr "Login:" + +#: ImageBrowser.py:440 +msgid "Hasło:" +msgstr "Password:" + +#: ImageBrowser.py:446 +msgid "Base URL:" +msgstr "Base URL:" + +#: ImageBrowser.py:452 +msgid "Default Tags:" +msgstr "Default Tags:" + +#: ImageBrowser.py:458 +msgid "Browser:" +msgstr "Browser:" + +#: ImageBrowser.py:472 +msgid "Language:" +msgstr "Language:" + +#: ImageBrowser.py:504 +msgid "Zapisz" +msgstr "Save" + +#: ImageBrowser.py:546 +msgid "PNG Tags" +msgstr "PNG Tags" + +#: ImageBrowser.py:558 +msgid "Tagger Tags" +msgstr "Tagger Tags" + +#: ImageBrowser.py:572 +msgid "Manual Tags" +msgstr "Manual Tags" + +#: ImageBrowser.py:580 +msgid "Final Tags" +msgstr "Final Tags" + +#: ImageBrowser.py:605 +msgid "Wyświetl" +msgstr "Display" + +#: ImageBrowser.py:622 +msgid "Przetworzono tagi:" +msgstr "Processed tags:" + +#: ImageBrowser.py:622 ImageBrowser.py:623 ImageBrowser.py:624 +msgid "plików" +msgstr "files" + +#: ImageBrowser.py:623 +msgid "Zweryfikowano status uploadu:" +msgstr "Upload status verified:" + +#: ImageBrowser.py:624 +msgid "Zuploadowano:" +msgstr "Uploaded:" + +#: ImageBrowser.py:652 +msgid "Wybierz folder z obrazkami PNG" +msgstr "Select folder with PNG images" + +#: ImageBrowser.py:683 +msgid "Informacja" +msgstr "Information" + +#: ImageBrowser.py:683 +msgid "Brak plików PNG w wybranym folderze." +msgstr "No PNG files in the selected folder." + +#: ImageBrowser.py:766 +msgid "Błąd podczas sprawdzania paczki uploadu:" +msgstr "Error while checking upload package:" + +#: ImageBrowser.py:842 +msgid "Błąd przy obliczaniu MD5:" +msgstr "Error calculating MD5:" + +#: ImageBrowser.py:898 +msgid "Błąd" +msgstr "Error" + +#: ImageBrowser.py:898 +msgid "Nie można załadować obrazka:" +msgstr "Unable to load image:" + +#: ImageBrowser.py:1108 ImageBrowser.py:1118 +#, python-brace-format +msgid "Warning: Tag '{tag}' not found in implication graph" +msgstr "Warning: Tag '{tag}' not found in implication graph" + +#: ImageBrowser.py:1348 +msgid "Tagger przetwarza..." +msgstr "Tagger processing..." + +#: ImageBrowser.py:1373 +#, python-brace-format +msgid "Wysyłam plik {base_file_name}..." +msgstr "Sending file {base_file_name}..." + +#: ImageBrowser.py:1414 +msgid "Wysyłanie zakończone powodzeniem!" +msgstr "Upload completed successfully!" + +#: ImageBrowser.py:1418 ImageBrowser.py:1427 +#, python-brace-format +msgid "" +"Wysyłanie zakończone błędem.\n" +"Status: {status_code}\n" +"Treść: {text}" +msgstr "" +"Upload failed.\nStatus: {status_code}\nContent: {text}" + +#: ImageBrowser.py:1433 ImageBrowser.py:1436 +msgid "Wysyłanie" +msgstr "Uploading" + +#: ImageBrowser.py:1449 +msgid "Błąd wysyłania" +msgstr "Upload error" + +#: ImageBrowser.py:1469 +msgid "Błąd edycji" +msgstr "Edit error" + +#: ImageBrowser.py:1469 +msgid "Post nie został znaleziony dla tego pliku" +msgstr "Post not found for this file" + +#: ImageBrowser.py:1479 +#, python-brace-format +msgid "Aktualizuję tagi dla {base_file_name}..." +msgstr "Updating tags for {base_file_name}..." + +#: ImageBrowser.py:1489 ImageBrowser.py:1499 ImageBrowser.py:1530 +msgid "Operacja anulowana" +msgstr "Operation cancelled" + +#: ImageBrowser.py:1540 +msgid "Tagi zostały zaktualizowane!" +msgstr "Tags have been updated!" + +#: ImageBrowser.py:1542 +msgid "Sukces edycji" +msgstr "Edit successful" + +#: ImageBrowser.py:1548 +#, python-brace-format +msgid "" +"Błąd podczas aktualizacji tagów\n" +"Status: {code}" +msgstr "" +"Error updating tags\nStatus: {code}" + +#: ImageBrowser.py:1552 +msgid "Treść:" +msgstr "Content:" + +#: ImageBrowser.py:1556 +msgid "Krytyczny błąd edycji" +msgstr "Critical edit error" + +#: ImageBrowser.py:1568 +msgid "Potwierdzenie" +msgstr "Confirmation" + +#: ImageBrowser.py:1570 +msgid "" +"Czy na pewno chcesz wrzucić wszystkie niewrzucone pliki?\n" +"Każdy z nich zostanie oznaczony tagiem 'meta:auto_upload'.\n" +"Upewnij się, że tagi są poprawne!" +msgstr "" +"Are you sure you want to upload all unsubmitted files?\n" +"Each will be tagged with 'meta:auto_upload'.\n" +"Make sure the tags are correct!" + +#: ImageBrowser.py:1589 +msgid "Anulowano operację!" +msgstr "Operation cancelled!" + +#: ImageBrowser.py:1597 +#, python-brace-format +msgid "" +"Wysyłanie {file_path} z tagami: {final_tags} i ratingiem: {final_rating}" +msgstr "Uploading {file_path} with tags: {final_tags} and rating: {final_rating}" + +#: ImageBrowser.py:1608 +msgid "Przesłano pliki!" +msgstr "Files have been uploaded!" + +#: ProgressFile.py:16 +msgid "Upload cancelled by user." +msgstr "Upload cancelled by user." + +#: settings.py:163 +msgid "Błąd podczas ładowania ustawień:" +msgstr "Error loading settings:" + +#: settings.py:184 +msgid "Błąd podczas zapisywania ustawień:" +msgstr "Error saving settings:" + +#: tagger_cache.py:63 +msgid "Błąd przy odczycie cache dla" +msgstr "Error reading cache for" + +#: tagger_cache.py:88 +msgid "Błąd przy zapisie cache dla" +msgstr "Error writing cache for" + +#: tagger_cache.py:98 +msgid "Błąd przy usuwaniu cache dla" +msgstr "Error deleting cache for" + +#: tagger_cache.py:111 +msgid "Błąd przy czyszczeniu przeterminowanego cache:" +msgstr "Error clearing expired cache:" + +#: TagsRepo.py:59 +#, python-brace-format +msgid "Database file not found: {path}, will regenerate DB" +msgstr "Database file not found: {path}, will regenerate DB" + +#: TagsRepo.py:143 +msgid "Błąd przy inicjalizacji bazy tagów:" +msgstr "Error initializing tag database:" + +#: TagsRepo.py:159 +msgid "Czyszczenie bazy danych..." +msgstr "Cleaning database..." + +#: TagsRepo.py:175 +msgid "Anulowano pobieranie tagów." +msgstr "Tag fetching cancelled." + +#: TagsRepo.py:183 +#, python-brace-format +msgid "Pobieranie tagów (od ID {last_id})..." +msgstr "Fetching tags (from ID {last_id})..." + +#: TagsRepo.py:197 +#, python-brace-format +msgid "Błąd przy pobieraniu tagów od ID {last_id}: HTTP {code}" +msgstr "Error fetching tags from ID {last_id}: HTTP {code}" + +#: TagsRepo.py:212 +msgid "Anulowano przetwarzanie tagów." +msgstr "Tag processing cancelled." + +#: TagsRepo.py:248 +#, python-brace-format +msgid "Pobrano {count} tagów..." +msgstr "Fetched {count} tags..." + +#: TagsRepo.py:271 +msgid "Anulowano pobieranie aliasów tagów." +msgstr "Tag alias fetching cancelled." + +#: TagsRepo.py:280 +#, python-brace-format +msgid "Pobieranie aliasów tagów (od ID {last_id})..." +msgstr "Fetching tag aliases (from ID {last_id})..." + +#: TagsRepo.py:297 +#, python-brace-format +msgid "Błąd przy pobieraniu aliasów tagów od ID {last_id}: HTTP {code}" +msgstr "Error fetching tag aliases from ID {last_id}: HTTP {code}" + +#: TagsRepo.py:312 +msgid "Anulowano przetwarzanie aliasów tagów." +msgstr "Tag alias processing cancelled." + +#: TagsRepo.py:334 +#, python-brace-format +msgid "Pobrano {count} aliasów tagów..." +msgstr "Fetched {count} tag aliases..." + +#: TagsRepo.py:375 +msgid "Anulowano pobieranie implikacji tagów." +msgstr "Tag implication fetching cancelled." + +#: TagsRepo.py:384 +#, python-brace-format +msgid "Pobieranie implikacji tagów (od ID {last_id})..." +msgstr "Fetching tag implications (from ID {last_id})..." + +#: TagsRepo.py:398 +#, python-brace-format +msgid "Błąd przy pobieraniu implikacji tagów od ID {last_id}: HTTP {code}" +msgstr "Error fetching tag implications from ID {last_id}: HTTP {code}" + +#: TagsRepo.py:413 +msgid "Anulowano przetwarzanie implikacji tagów." +msgstr "Tag implication processing cancelled." + +#: TagsRepo.py:460 +#, python-brace-format +msgid "Pobrano implikacje dla {count} tagów..." +msgstr "Fetched implications for {count} tags..." + +#: TagsRepo.py:487 +msgid "Regeneracja bazy zakończona." +msgstr "Database regeneration complete." + +#: tag_processing.py:19 +msgid "Błąd przy pobieraniu tagów postaci:" +msgstr "Error fetching character tags:" + +#: tag_processing.py:34 +msgid "Błąd przy pobieraniu tagów copyright:" +msgstr "Error fetching copyright tags:" + +#: tag_processing.py:165 +msgid "Błąd podczas odczytu tag_aliases:" +msgstr "Error reading tag aliases:" + +#: tag_processing.py:181 +msgid "Błąd podczas odczytu tags:" +msgstr "Error reading tags:" diff --git a/kapitanbooru_uploader/locales/pl/LC_MESSAGES/messages.po b/kapitanbooru_uploader/locales/pl/LC_MESSAGES/messages.po new file mode 100644 index 0000000..110839b --- /dev/null +++ b/kapitanbooru_uploader/locales/pl/LC_MESSAGES/messages.po @@ -0,0 +1,515 @@ +msgid "" +msgstr "" +"Project-Id-Version: Kapitanbooru Uploader 0.4.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-02 00:39+0100\n" +"Language: pl\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: autocomplete.py:133 +msgid "Błąd przy pobieraniu sugestii:" +msgstr "Błąd przy pobieraniu sugestii:" + +#: common.py:35 +msgid "Błąd przy otwieraniu przeglądarki:" +msgstr "Błąd przy otwieraniu przeglądarki:" + +#: common.py:72 +msgid "Login succeeded, but expected cookies were not set." +msgstr "" +"Logowanie powiodło się, ale oczekiwane ciasteczka nie zostały ustawione." + +#: common.py:74 +msgid "Login successful. Cookies stored in session." +msgstr "Logowanie powiodło się. Ciasteczka zapisane w sesji." + +#: common.py:79 +msgid "Login failed:" +msgstr "Logowanie nie powiodło się:" + +#: common.py:95 +msgid "shm_user cookie not found; login might have failed." +msgstr "Nie znaleziono ciasteczka shm_user; logowanie mogło nie powieść się." + +#: common.py:112 +#, python-brace-format +msgid "Failed to load {user_url}, status code: {code}" +msgstr "Nie udało się załadować {user_url}, kod statusu: {code}" + +#: common.py:124 +msgid "Found auth_token:" +msgstr "Znaleziono auth_token:" + +#: common.py:127 +msgid "auth_token not found in the HTML page." +msgstr "Nie znaleziono auth_token w stronie HTML." + +#: ImageBrowser.py:30 +msgid "Processing..." +msgstr "Przetwarzanie..." + +#: ImageBrowser.py:34 +msgid "Processing, please wait..." +msgstr "Przetwarzanie, proszę czekać..." + +#: ImageBrowser.py:263 +msgid "Błąd przy otwieraniu pliku" +msgstr "Błąd przy otwieraniu pliku" + +#: ImageBrowser.py:305 +msgid "Tagger przetworzył:" +msgstr "Tagger przetworzył:" + +#: ImageBrowser.py:308 +msgid "Błąd Taggera dla" +msgstr "Błąd Taggera dla" + +#: ImageBrowser.py:334 +msgid "Otwórz folder" +msgstr "Otwórz folder" + +#: ImageBrowser.py:337 ImageBrowser.py:600 ImageBrowser.py:814 +#: ImageBrowser.py:822 +msgid "Wyślij" +msgstr "Wyślij" + +#: ImageBrowser.py:340 ImageBrowser.py:807 +msgid "Wyślij wszystko" +msgstr "Wyślij wszystko" + +#: ImageBrowser.py:344 ImageBrowser.py:808 ImageBrowser.py:814 +msgid "Podmień tagi" +msgstr "Podmień tagi" + +#: ImageBrowser.py:347 ImageBrowser.py:809 +msgid "Otwórz post" +msgstr "Otwórz post" + +#: ImageBrowser.py:350 +msgid "Zakończ" +msgstr "Zakończ" + +#: ImageBrowser.py:352 +msgid "Plik" +msgstr "Plik" + +#: ImageBrowser.py:356 ImageBrowser.py:430 +msgid "Ustawienia" +msgstr "Ustawienia" + +#: ImageBrowser.py:359 +msgid "Wyczyść cache Taggera" +msgstr "Wyczyść cache Taggera" + +#: ImageBrowser.py:362 +msgid "Zregeneruj bazę tagów" +msgstr "Zregeneruj bazę tagów" + +#: ImageBrowser.py:364 +msgid "Opcje" +msgstr "Opcje" + +#: ImageBrowser.py:367 +msgid "About" +msgstr "O programie" + +#: ImageBrowser.py:368 +msgid "Help" +msgstr "Pomoc" + +#: ImageBrowser.py:373 +msgid "About Kapitanbooru Uploader" +msgstr "O programie Kapitanbooru Uploader" + +#: ImageBrowser.py:386 +msgid "A GUI application for uploading images to KapitanBooru." +msgstr "Aplikacja GUI do przesyłania obrazów do KapitanBooru." + +#: ImageBrowser.py:387 +msgid "Features include image upload, tag management, automatic" +msgstr "Funkcje obejmują przesyłanie obrazów, zarządzanie tagami, automatyczne" + +#: ImageBrowser.py:388 +msgid "tagging with wdtagger, and cache management." +msgstr "tagowanie za pomocą wdtagger oraz zarządzanie cache." + +#: ImageBrowser.py:390 +msgid "Authors:" +msgstr "Autorzy:" + +#: ImageBrowser.py:393 +msgid "License: MIT License" +msgstr "Licencja: MIT License" + +#: ImageBrowser.py:396 +msgid "Repository:" +msgstr "Repozytorium:" + +#: ImageBrowser.py:397 +msgid "Website:" +msgstr "Strona internetowa:" + +#: ImageBrowser.py:414 +msgid "Close" +msgstr "Zamknij" + +#: ImageBrowser.py:422 ImageBrowser.py:425 +msgid "Cache" +msgstr "Cache" + +#: ImageBrowser.py:422 +msgid "Cache Taggera zostało wyczyszczone." +msgstr "Cache Taggera zostało wyczyszczone." + +#: ImageBrowser.py:425 +msgid "Błąd przy czyszczeniu cache:" +msgstr "Błąd przy czyszczeniu cache:" + +#: ImageBrowser.py:434 +msgid "Login:" +msgstr "Login:" + +#: ImageBrowser.py:440 +msgid "Hasło:" +msgstr "Hasło:" + +#: ImageBrowser.py:446 +msgid "Base URL:" +msgstr "Base URL:" + +#: ImageBrowser.py:452 +msgid "Default Tags:" +msgstr "Domyślne tagi:" + +#: ImageBrowser.py:458 +msgid "Browser:" +msgstr "Przeglądarka:" + +#: ImageBrowser.py:472 +msgid "Language:" +msgstr "Język:" + +#: ImageBrowser.py:504 +msgid "Zapisz" +msgstr "Zapisz" + +#: ImageBrowser.py:546 +msgid "PNG Tags" +msgstr "Tagi PNG" + +#: ImageBrowser.py:558 +msgid "Tagger Tags" +msgstr "Tagi Taggera" + +#: ImageBrowser.py:572 +msgid "Manual Tags" +msgstr "Tagi ręczne" + +#: ImageBrowser.py:580 +msgid "Final Tags" +msgstr "Ostateczne tagi" + +#: ImageBrowser.py:605 +msgid "Wyświetl" +msgstr "Wyświetl" + +#: ImageBrowser.py:622 +msgid "Przetworzono tagi:" +msgstr "Przetworzono tagi:" + +#: ImageBrowser.py:622 ImageBrowser.py:623 ImageBrowser.py:624 +msgid "plików" +msgstr "plików" + +#: ImageBrowser.py:623 +msgid "Zweryfikowano status uploadu:" +msgstr "Zweryfikowano status uploadu:" + +#: ImageBrowser.py:624 +msgid "Zuploadowano:" +msgstr "Zuploadowano:" + +#: ImageBrowser.py:652 +msgid "Wybierz folder z obrazkami PNG" +msgstr "Wybierz folder z obrazami PNG" + +#: ImageBrowser.py:683 +msgid "Informacja" +msgstr "Informacja" + +#: ImageBrowser.py:683 +msgid "Brak plików PNG w wybranym folderze." +msgstr "Brak plików PNG w wybranym folderze." + +#: ImageBrowser.py:766 +msgid "Błąd podczas sprawdzania paczki uploadu:" +msgstr "Błąd podczas sprawdzania paczki uploadu:" + +#: ImageBrowser.py:842 +msgid "Błąd przy obliczaniu MD5:" +msgstr "Błąd przy obliczaniu MD5:" + +#: ImageBrowser.py:898 +msgid "Błąd" +msgstr "Błąd" + +#: ImageBrowser.py:898 +msgid "Nie można załadować obrazka:" +msgstr "Nie można załadować obrazka:" + +#: ImageBrowser.py:1108 ImageBrowser.py:1118 +#, python-brace-format +msgid "Warning: Tag '{tag}' not found in implication graph" +msgstr "Ostrzeżenie: Tag '{tag}' nie został znaleziony w grafie implikacji" + +#: ImageBrowser.py:1348 +msgid "Tagger przetwarza..." +msgstr "Tagger przetwarza..." + +#: ImageBrowser.py:1373 +#, python-brace-format +msgid "Wysyłam plik {base_file_name}..." +msgstr "Wysyłam plik {base_file_name}..." + +#: ImageBrowser.py:1414 +#, fuzzy +msgid "Wysyłanie zakończone powodzeniem!" +msgstr "Upload zakończony powodzeniem!" + +#: ImageBrowser.py:1418 ImageBrowser.py:1427 +#, fuzzy, python-brace-format +msgid "" +"Wysyłanie zakończone błędem.\n" +"Status: {status_code}\n" +"Treść: {text}" +msgstr "" +"Upload zakończony błędem.\n" +"Status: {status_code}\n" +"Treść: {text}" + +#: ImageBrowser.py:1433 ImageBrowser.py:1436 +msgid "Wysyłanie" +msgstr "" + +#: ImageBrowser.py:1449 +#, fuzzy +msgid "Błąd wysyłania" +msgstr "Błąd edycji" + +#: ImageBrowser.py:1469 +msgid "Błąd edycji" +msgstr "Błąd edycji" + +#: ImageBrowser.py:1469 +msgid "Post nie został znaleziony dla tego pliku" +msgstr "Post nie został znaleziony dla tego pliku" + +#: ImageBrowser.py:1479 +#, python-brace-format +msgid "Aktualizuję tagi dla {base_file_name}..." +msgstr "Aktualizuję tagi dla {base_file_name}..." + +#: ImageBrowser.py:1489 ImageBrowser.py:1499 ImageBrowser.py:1530 +msgid "Operacja anulowana" +msgstr "Operacja anulowana" + +#: ImageBrowser.py:1540 +msgid "Tagi zostały zaktualizowane!" +msgstr "Tagi zostały zaktualizowane!" + +#: ImageBrowser.py:1542 +msgid "Sukces edycji" +msgstr "Sukces edycji" + +#: ImageBrowser.py:1548 +#, python-brace-format +msgid "" +"Błąd podczas aktualizacji tagów\n" +"Status: {code}" +msgstr "" +"Błąd podczas aktualizacji tagów\n" +"Status: {code}" + +#: ImageBrowser.py:1552 +msgid "Treść:" +msgstr "Treść:" + +#: ImageBrowser.py:1556 +msgid "Krytyczny błąd edycji" +msgstr "Krytyczny błąd edycji" + +#: ImageBrowser.py:1568 +msgid "Potwierdzenie" +msgstr "Potwierdzenie" + +#: ImageBrowser.py:1570 +msgid "" +"Czy na pewno chcesz wrzucić wszystkie niewrzucone pliki?\n" +"Każdy z nich zostanie oznaczony tagiem 'meta:auto_upload'.\n" +"Upewnij się, że tagi są poprawne!" +msgstr "" +"Czy na pewno chcesz wrzucić wszystkie niewrzucone pliki?\n" +"Każdy z nich zostanie oznaczony tagiem 'meta:auto_upload'.\n" +"Upewnij się, że tagi są poprawne!" + +#: ImageBrowser.py:1589 +msgid "Anulowano operację!" +msgstr "Operacja anulowana!" + +#: ImageBrowser.py:1597 +#, fuzzy, python-brace-format +msgid "" +"Wysyłanie {file_path} z tagami: {final_tags} i ratingiem: {final_rating}" +msgstr "" +"Wysyłanie {file_path} z tagami: {final_tags} i ratingiem: {final_rating}" + +#: ImageBrowser.py:1608 +msgid "Przesłano pliki!" +msgstr "Pliki zostały przesłane!" + +#: ProgressFile.py:16 +msgid "Upload cancelled by user." +msgstr "Przesyłanie anulowane przez użytkownika." + +#: settings.py:163 +msgid "Błąd podczas ładowania ustawień:" +msgstr "Błąd podczas ładowania ustawień:" + +#: settings.py:184 +msgid "Błąd podczas zapisywania ustawień:" +msgstr "Błąd podczas zapisywania ustawień:" + +#: tagger_cache.py:63 +msgid "Błąd przy odczycie cache dla" +msgstr "Błąd przy odczycie cache dla" + +#: tagger_cache.py:88 +msgid "Błąd przy zapisie cache dla" +msgstr "Błąd przy zapisie cache dla" + +#: tagger_cache.py:98 +msgid "Błąd przy usuwaniu cache dla" +msgstr "Błąd przy usuwaniu cache dla" + +#: tagger_cache.py:111 +msgid "Błąd przy czyszczeniu przeterminowanego cache:" +msgstr "Błąd przy czyszczeniu przeterminowanego cache:" + +#: TagsRepo.py:59 +#, python-brace-format +msgid "Database file not found: {path}, will regenerate DB" +msgstr "Nie znaleziono pliku bazy danych: {path}, baza zostanie zregenerowana" + +#: TagsRepo.py:143 +msgid "Błąd przy inicjalizacji bazy tagów:" +msgstr "Błąd przy inicjalizacji bazy tagów:" + +#: TagsRepo.py:159 +msgid "Czyszczenie bazy danych..." +msgstr "Czyszczenie bazy danych..." + +#: TagsRepo.py:175 +msgid "Anulowano pobieranie tagów." +msgstr "Pobieranie tagów anulowane." + +#: TagsRepo.py:183 +#, python-brace-format +msgid "Pobieranie tagów (od ID {last_id})..." +msgstr "Pobieranie tagów (od ID {last_id})..." + +#: TagsRepo.py:197 +#, python-brace-format +msgid "Błąd przy pobieraniu tagów od ID {last_id}: HTTP {code}" +msgstr "Błąd przy pobieraniu tagów od ID {last_id}: HTTP {code}" + +#: TagsRepo.py:212 +msgid "Anulowano przetwarzanie tagów." +msgstr "Przetwarzanie tagów anulowane." + +#: TagsRepo.py:248 +#, python-brace-format +msgid "Pobrano {count} tagów..." +msgstr "Pobrano {count} tagów..." + +#: TagsRepo.py:271 +msgid "Anulowano pobieranie aliasów tagów." +msgstr "Pobieranie aliasów tagów anulowane." + +#: TagsRepo.py:280 +#, python-brace-format +msgid "Pobieranie aliasów tagów (od ID {last_id})..." +msgstr "Pobieranie aliasów tagów (od ID {last_id})..." + +#: TagsRepo.py:297 +#, python-brace-format +msgid "Błąd przy pobieraniu aliasów tagów od ID {last_id}: HTTP {code}" +msgstr "Błąd przy pobieraniu aliasów tagów od ID {last_id}: HTTP {code}" + +#: TagsRepo.py:312 +msgid "Anulowano przetwarzanie aliasów tagów." +msgstr "Przetwarzanie aliasów tagów anulowane." + +#: TagsRepo.py:334 +#, python-brace-format +msgid "Pobrano {count} aliasów tagów..." +msgstr "Pobrano {count} aliasów tagów..." + +#: TagsRepo.py:375 +msgid "Anulowano pobieranie implikacji tagów." +msgstr "Pobieranie implikacji tagów anulowane." + +#: TagsRepo.py:384 +#, python-brace-format +msgid "Pobieranie implikacji tagów (od ID {last_id})..." +msgstr "Pobieranie implikacji tagów (od ID {last_id})..." + +#: TagsRepo.py:398 +#, python-brace-format +msgid "Błąd przy pobieraniu implikacji tagów od ID {last_id}: HTTP {code}" +msgstr "Błąd przy pobieraniu implikacji tagów od ID {last_id}: HTTP {code}" + +#: TagsRepo.py:413 +msgid "Anulowano przetwarzanie implikacji tagów." +msgstr "Przetwarzanie implikacji tagów anulowane." + +#: TagsRepo.py:460 +#, python-brace-format +msgid "Pobrano implikacje dla {count} tagów..." +msgstr "Pobrano implikacje dla {count} tagów..." + +#: TagsRepo.py:487 +msgid "Regeneracja bazy zakończona." +msgstr "Regeneracja bazy zakończona." + +#: tag_processing.py:19 +msgid "Błąd przy pobieraniu tagów postaci:" +msgstr "Błąd przy pobieraniu tagów postaci:" + +#: tag_processing.py:34 +msgid "Błąd przy pobieraniu tagów copyright:" +msgstr "Błąd przy pobieraniu tagów copyright:" + +#: tag_processing.py:165 +msgid "Błąd podczas odczytu tag_aliases:" +msgstr "Błąd podczas odczytu tag_aliases:" + +#: tag_processing.py:181 +msgid "Błąd podczas odczytu tags:" +msgstr "Błąd podczas odczytu tags:" + +#, python-brace-format +#~ msgid "" +#~ "Upload zakończony błędem.\n" +#~ "Status: 409\n" +#~ "Treść: {error}" +#~ msgstr "" +#~ "Upload zakończony błędem.\n" +#~ "Status: 409\n" +#~ "Treść: {error}" + +#~ msgid "Upload" +#~ msgstr "Upload" + +#~ msgid "Błąd uploadu" +#~ msgstr "Błąd uploadu" diff --git a/kapitanbooru_uploader/settings.py b/kapitanbooru_uploader/settings.py index 551c1b1..2c8e31d 100644 --- a/kapitanbooru_uploader/settings.py +++ b/kapitanbooru_uploader/settings.py @@ -1,11 +1,11 @@ import base64 -import importlib import json import os -import sqlite3 import subprocess import sys +from .I18N import _ + # Na Windowsie używamy DPAPI if sys.platform.startswith("win"): try: @@ -49,6 +49,7 @@ def get_browser_paths_windows(): return browsers + def get_browsers_linux(): """Detects installed browsers on Linux by checking available executables.""" browsers = {"Default": None} @@ -114,6 +115,7 @@ class Settings: self.default_tags = "artist:kapitan meta:ai-generated" self.cache_expiry = 604800 # 7 dni w sekundach self.browser = "" + self.i18n = _ self.installed_browsers = detect_installed_browsers() self.load_settings() self.installed_browsers_reverse = { @@ -139,6 +141,7 @@ class Settings: self.default_tags = "artist:kapitan meta:ai-generated" self.cache_expiry = 604800 # 7 dni w sekundach self.browser = "" + self.i18n.set_language("en") try: if os.path.exists(self.get_settings_path()): with open(self.get_settings_path(), "r", encoding="utf-8") as f: @@ -155,8 +158,9 @@ class Settings: self.browser = data.get("browser", self.browser) if self.browser not in self.installed_browsers: self.browser = "" + self.i18n.set_language(data.get("language", "en")) except Exception as e: - print("Błąd podczas ładowania ustawień:", e) + print(_("Błąd podczas ładowania ustawień:"), e) def save_settings(self): """Zapisuje ustawienia do pliku.""" @@ -166,6 +170,7 @@ class Settings: "default_tags": self.default_tags, "cache_expiry": self.cache_expiry, "browser": self.browser, + "language": self.i18n.current_lang, } # Na Windowsie szyfrujemy hasło if sys.platform.startswith("win"): @@ -176,4 +181,4 @@ class Settings: with open(self.get_settings_path(), "w", encoding="utf-8") as f: json.dump(data, f, indent=4) except Exception as e: - print("Błąd podczas zapisywania ustawień:", e) + print(_("Błąd podczas zapisywania ustawień:"), e) diff --git a/kapitanbooru_uploader/tag_processing.py b/kapitanbooru_uploader/tag_processing.py index 590c1e0..ad359c6 100644 --- a/kapitanbooru_uploader/tag_processing.py +++ b/kapitanbooru_uploader/tag_processing.py @@ -1,6 +1,7 @@ from functools import lru_cache import re +from .I18N import _ from .TagsRepo import TagsRepo @@ -15,7 +16,7 @@ def get_character_tags(tags_repo: TagsRepo): conn.close() return {row[0] for row in rows} except Exception as e: - print("Błąd przy pobieraniu tagów postaci:", e) + print(_("Błąd przy pobieraniu tagów postaci:"), e) return set() @@ -30,7 +31,7 @@ def get_copyright_tags(tags_repo: TagsRepo): conn.close() return {row[0] for row in rows} except Exception as e: - print("Błąd przy pobieraniu tagów copyright:", e) + print(_("Błąd przy pobieraniu tagów copyright:"), e) return set() @@ -161,7 +162,7 @@ def process_tag(tag, tags_repo: TagsRepo): tag_lookup = row[0] conn.close() except Exception as e: - print("Błąd podczas odczytu tag_aliases:", e) + print(_("Błąd podczas odczytu tag_aliases:"), e) # Sprawdź w tabeli tags – kolumna name nie zawiera prefiksów try: @@ -177,5 +178,5 @@ def process_tag(tag, tags_repo: TagsRepo): # Tag nie istnieje return tag_lookup, None except Exception as e: - print("Błąd podczas odczytu tags:", e) + print(_("Błąd podczas odczytu tags:"), e) return tag_lookup, None diff --git a/kapitanbooru_uploader/tagger_cache.py b/kapitanbooru_uploader/tagger_cache.py index 1c1fdd1..5639a88 100644 --- a/kapitanbooru_uploader/tagger_cache.py +++ b/kapitanbooru_uploader/tagger_cache.py @@ -2,6 +2,7 @@ import os import pickle import sqlite3 import time +from .I18N import _ from .settings import Settings @@ -59,7 +60,7 @@ class TaggerCache: else: self.delete_cache_entry(file_md5) except Exception as e: - print("Błąd przy odczycie cache dla", file_md5, ":", e) + print(_("Błąd przy odczycie cache dla"), file_md5, ":", e) return None def __setitem__(self, file_md5, result): @@ -84,7 +85,7 @@ class TaggerCache: conn.commit() conn.close() except Exception as e: - print("Błąd przy zapisie cache dla", file_md5, ":", e) + print(_("Błąd przy zapisie cache dla"), file_md5, ":", e) def delete_cache_entry(self, file_md5): try: @@ -94,7 +95,7 @@ class TaggerCache: conn.commit() conn.close() except Exception as e: - print("Błąd przy usuwaniu cache dla", file_md5, ":", e) + print(_("Błąd przy usuwaniu cache dla"), file_md5, ":", e) def clear_expired_cache(self): try: @@ -107,7 +108,7 @@ class TaggerCache: conn.commit() conn.close() except Exception as e: - print("Błąd przy czyszczeniu przeterminowanego cache:", e) + print(_("Błąd przy czyszczeniu przeterminowanego cache:"), e) def clear_cache(self): try: diff --git a/pyproject.toml b/pyproject.toml index 8e666a7..cbd56e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "kapitanbooru-uploader" -version = "0.3.0" +version = "0.4.0" description = "A GUI application for uploading images to KapitanBooru" authors = [ {name = "Michał Leśniak", email = "kapitan@mlesniak.pl"} @@ -25,4 +25,8 @@ license = {file = "LICENSE"} kapitanbooru-uploader = "kapitanbooru_uploader.__main__:main" [tool.setuptools] -packages = ["kapitanbooru_uploader"] \ No newline at end of file +packages = ["kapitanbooru_uploader"] +include-package-data = true + +[tool.setuptools.package-data] +"kapitanbooru_uploader" = ["locales/*/LC_MESSAGES/*.mo"] \ No newline at end of file diff --git a/update_translations.bat b/update_translations.bat new file mode 100644 index 0000000..76037d4 --- /dev/null +++ b/update_translations.bat @@ -0,0 +1,19 @@ +@echo off +setlocal enabledelayedexpansion + +:: Create POT file +del locales\messages.pot 2>nul +for %%f in (*.py) do ( + if not exist locales\messages.pot ( + xgettext -d messages -o locales\messages.pot "%%f" + ) else ( + xgettext -d messages -o locales\messages.pot --join-existing "%%f" + ) +) + +:: Update PO files +for /D %%d in (locales\*) do ( + if exist "%%d\LC_MESSAGES\messages.po" ( + msgmerge --update "%%d\LC_MESSAGES\messages.po" locales\messages.pot + ) +) \ No newline at end of file