Translations, suggestion box, UX
All checks were successful
Gitea/kapitanbooru-uploader/pipeline/head This commit looks good

This commit is contained in:
Michał Leśniak 2025-03-03 00:47:38 +01:00
parent 9f187afc22
commit 9361bc0363
17 changed files with 1515 additions and 135 deletions

1
.gitignore vendored
View File

@ -54,6 +54,7 @@ cover/
# Translations
*.mo
*.pot
*.po~
# Django stuff:
*.log

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"i18n-ally.localesPaths": [
"locales"
]
}

14
Jenkinsfile vendored
View File

@ -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 {

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
recursive-include kapitanbooru_uploader/locales *.mo

View File

@ -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()

View File

@ -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("<Button-1>", 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(
"<Button-1>", 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)

View File

@ -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)

View File

@ -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()

View File

@ -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("<Up>", self.on_up)
self.bind("<Return>", self.on_return)
self.bind("<Tab>", self.on_return)
self.bind("<FocusOut>", lambda e: self.hide_listbox())
self.bind("<FocusOut>", 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):

View File

@ -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/<shm_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."))

View File

@ -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:"

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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"]
packages = ["kapitanbooru_uploader"]
include-package-data = true
[tool.setuptools.package-data]
"kapitanbooru_uploader" = ["locales/*/LC_MESSAGES/*.mo"]

19
update_translations.bat Normal file
View File

@ -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
)
)