Skip to content

Add translation support #598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
144 changes: 144 additions & 0 deletions tagstudio/resources/translations/en_us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
{
"home.base_title": "TagStudio Alpha",
"home.main_window": "Main Window",
"home.include_all_tags": "And (Includes All Tags)",
"home.include_any_tag": "Or (Includes Any Tag)",
"home.thumbnail_size": "Thumbnail Size",
"home.search_entries": "Search Entries",
"home.search": "Search",
"menu.file": "File",
"menu.edit": "Edit",
"menu.tools": "Tools",
"menu.macros": "Macros",
"menu.window": "Window",
"menu.help": "Help",
"tag.new": "New Tag",
"tag.add": "Add Tag",
"tag.library": "Library Tags",
"merge.window_title": "Merging Duplicate Entries",
"merge.merge_dupe_entries": "Merging Duplicate Entries",
"preview.dimensions": "Dimensions",
"preview.recent": "Recent Libraries",
"library.title": "Title",
"library.author": "Author",
"library.Artist": "Artist",
"library.url": "URL",
"library.description": "Description",
"library.notes": "Notes",
"library.tags": "Tags",
"library.content_tags": "Content Tags",
"library.meta_tags": "Meta Tags",
"library.collation": "Collation",
"library.date": "Date",
"library.date_created": "Date Created",
"library.date_modified": "Date Modified",
"library.date_taken": "Date Taken",
"library.date_published": "Date Published",
"library.archived": "Date Archived",
"library.favorite": "Favorite",
"library.book": "Book",
"library.comic": "Comic",
"library.series": "Series",
"library.manga": "Manga",
"library.source": "Source",
"library.date_uploaded": "Date Uploaded",
"library.date_released": "Date Released",
"library.volume": "Volume",
"library.anthology": "Anthology",
"library.magazine": "Magazine",
"library.publisher": "Publisher",
"library.guest_artist": "Guest Artist",
"library.composer": "Composer",
"library.comments": "Comments",
"open_library.no_tagstudio_library_found": "No existing TagStudio library found at '%{path}'. Creating one.",
"open_library.library_creation_return_code": "Library Creation Return Code:",
"open_library.title": "Library",
"dialog.open_create_library": "Open/Create Library",
"splash.open_library": "Opening Library",
"status.save_success": "Library Saved and Closed!",
"status.backup_success": "Library Backup Saved at:",
"status.search_library_query": "Searching Library for",
"status.enumerate_query": "Query:%{query}, Frame: %{i}, Length: %{len(f)}",
"status.number_results_found": "%{len(all_items)} Results Found for \"%{query}\" (%{format_timespan(end_time - start_time)})",
"status.results_found": "Results",
"dialog.save_library": "Save Library",
"dialog.refresh_directories": "Refreshing Directories",
"dialog.scan_directories": "Scanning Directories for New Files...\nPreparing...",
"dialog.scan_directories.new_files": "Scanning Directories for New Files...\n%{x + 1} File%{\"s\" if x + 1 != 1 else \"\"} Searched, %{len(self.lib.files_not_in_library)} New Files Found",
"tooltip.open_library": "Ctrl+O",
"tooltip.save_library": "Ctrl+S",
"progression.running_macros.new_entries": "Running Macros on New Entries",
"progression.running_macros.one_new_entry": "Running Configured Macros on 1/%{len(new_ids)} New Entries",
"progression.running_macros.several_new_entry": "Running Configured Macros on %{x + 1}/%{len(new_ids)} New Entries",
"file_opener.open_file": "Opening file:}",
"file_opener.not_found": "File not found:",
"file_opener.command_not_found": "Could not find %{command_name} on system PATH",
"add_field.add": "Add Field",
"generic.remove_field": "Remove Field",
"generic.file_extension": "File Extensions",
"generic.open_file": "Open file",
"generic.open_file_explorer": "Open file in explorer",
"generic.cancel": "Cancel",
"generic.add": "Add",
"generic.name": "Name",
"generic.shorthand": "Shorthand",
"generic.aliases": "Aliases",
"generic.color": "Color",
"generic.delete": "Delete",
"generic.exclude": "Exclude",
"generic.include": "Include",
"generic.done": "Done",
"generic.open_all": "Open All",
"generic.close_all": "Close All",
"generic.refresh_all": "Refresh All",
"generic.apply": "Apply",
"generic.mirror": "Mirror",
"generic.search_tags": "Search Tags",
"build_tags.parent_tags": "Parent Tags",
"build_tags.add_parent_tags": "Add Parent Tags",
"delete_unlinked.delete_unlinked": "Delete Unlinked Entries",
"delete_unlinked.confirm": "Are you sure you want to delete the following %{len(self.lib.missing_files)} entries?",
"delete_unlinked.delete_entries": "Deleting Entries",
"delete_unlinked.deleting_number_entries": "Deleting %{x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries",
"file_extension.add_extension": "Add Extension",
"file_extension.list_mode": "List Mode:",
"fix_dupes.fix_dupes": "Fix Duplicate Files",
"fix_dupes.no_file_selected": "No DupeGuru File Selected",
"fix_dupes.load_file": "Load DupeGuru File",
"fix_dupes.mirror_entries": "Mirror Entries",
"fix_dupes.mirror_description": "Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data.",
"fix_dupes.advice_label": "After mirroring, you're free to use DupeGuru to delete the unwanted files. Afterwards, use TagStudio's \"Fix Unlinked Entries\" feature in the Tools menu in order to delete the unlinked Entries.",
"fix_dupes.open_result_files": "Open DupeGuru Results File",
"fix_dupes.name_filter": "DupeGuru Files (*.dupeguru)",
"fix_dupes.no_file_match": "Duplicate File Matches: N/A",
"fix_dupes.number_file_match": "Duplicate File Matches: %{count}",
"fix_unlinked.fix_unlinked": "Fix Unlinked Entries",
"fix_unlinked.description": "Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked. Unlinked entries may be automatically relinked via searching your directories, manually relinked by the user, or deleted if desired.",
"fix_unlinked.duplicate_description": "Duplicate entries are defined as multiple entries which point to the same file on disk. Merging these will combine the tags and metadata from all duplicates into a single consolidated entry. These are not to be confused with \"duplicate files\", which are duplicates of your files themselves outside of TagStudio.",
"fix_unlinked.search_and_relink": "Search && Relink",
"fix_unlinked.refresh_dupes": "Refresh Duplicate Entries",
"fix_unlinked.merge_dupes": "Merge Duplicate Entries",
"fix_unlinked.manual_relink": "Manual Relink",
"fix_unlinked.delete_unlinked": "Delete Unlinked Entries",
"fix_unlinked.scan_library.title": "Scanning Library",
"fix_unlinked.scan_library.label": "Scanning Library for Unlinked Entries...",
"folders_to_tags.folders_to_tags": "Converting folders to Tags",
"folders_to_tags.title": "Create Tags From Folders",
"folders_to_tags.description": "Creates tags based on your folder structure and applies them to your entries.\n The structure below shows all the tags that will be created and what entries they will be applied to.",
"mirror_entities.are_you_sure": "Are you sure you want to mirror the following %{len(self.lib.dupe_files)} Entries?",
"mirror_entities.title": "Mirroring Entries",
"mirror_entities.label": "Mirroring 1/%{count} Entries...",
"relink_unlinked.title": "Relinking Entries",
"relink_unlinked.attempt_relink": "Attempting to Relink %{x[0]+1}/%{len(self.lib.missing_files)} Entries, %{self.fixed} Successfully Relinked",
"landing.open_button": "Open/Create Library %{open_shortcut_text}",
"preview_panel.missing_location": "Location is missing",
"preview_panel.update_widgets": "[ENTRY PANEL] UPDATE WIDGETS (%{self.driver.selected})",
"preview_panel.no_items_selected": "No Items Selected",
"preview_panel.confirm_remove": "Are you sure you want to remove this \"%{self.lib.get_field_attr(field, \"name\")}\" field?",
"preview_panel.mixed_data": "Mixed Data",
"preview_panel.edit_name": "Edit",
"preview_panel.unknown_field_type": "Unknown Field Type",
"tag.search_for_tag": "Search for Tag",
"tag.add_search": "Add to Search",
"text_line_edit.unknown_event_type": "unknown event type: %{event}"
}
12 changes: 6 additions & 6 deletions tagstudio/src/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def setupUi(self, MainWindow):

def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate(
"MainWindow", u"MainWindow", None))
"MainWindow", u"Title", None))
# Navigation buttons
self.backButton.setText(
QCoreApplication.translate("MainWindow", u"<", None))
Expand All @@ -205,18 +205,18 @@ def retranslateUi(self, MainWindow):

# Search field
self.searchField.setPlaceholderText(
QCoreApplication.translate("MainWindow", u"Search Entries", None))
QCoreApplication.translate("MainWindow.Search", u"Entries", None))
self.searchButton.setText(
QCoreApplication.translate("MainWindow", u"Search", None))
QCoreApplication.translate("MainWindow.Search", u"Search", None))

# Search type selector
self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", "And (Includes All Tags)"))
self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", "Or (Includes Any Tag)"))
self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow.Search", u"AND"))
self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow.Search", u"OR"))
self.comboBox.setCurrentText("")

# Thumbnail size selector
self.comboBox.setPlaceholderText(
QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
QCoreApplication.translate("MainWindow", u"ThumbnailSize", None))
# retranslateUi

def moveEvent(self, event) -> None:
Expand Down
27 changes: 19 additions & 8 deletions tagstudio/src/qt/modals/build_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


import structlog
from PySide6.QtCore import Signal, Qt
from PySide6.QtCore import Signal, Qt, QCoreApplication
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
Expand Down Expand Up @@ -50,7 +50,7 @@ def __init__(self, library: Library, tag: Tag | None = None):
self.name_layout.setSpacing(0)
self.name_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.name_title = QLabel()
self.name_title.setText("Name")
self.name_title.setText(QCoreApplication.translate("generic", "name"))
self.name_layout.addWidget(self.name_title)
self.name_field = QLineEdit()
self.name_layout.addWidget(self.name_field)
Expand All @@ -63,7 +63,7 @@ def __init__(self, library: Library, tag: Tag | None = None):
self.shorthand_layout.setSpacing(0)
self.shorthand_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.shorthand_title = QLabel()
self.shorthand_title.setText("Shorthand")
self.shorthand_title.setText(QCoreApplication.translate("generic", "shorthand"))
self.shorthand_layout.addWidget(self.shorthand_title)
self.shorthand_field = QLineEdit()
self.shorthand_layout.addWidget(self.shorthand_field)
Expand All @@ -76,7 +76,7 @@ def __init__(self, library: Library, tag: Tag | None = None):
self.aliases_layout.setSpacing(0)
self.aliases_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.aliases_title = QLabel()
self.aliases_title.setText("Aliases")
self.aliases_title.setText(QCoreApplication.translate("generic", "aliases"))
self.aliases_layout.addWidget(self.aliases_title)
self.aliases_field = QTextEdit()
self.aliases_field.setAcceptRichText(False)
Expand All @@ -92,7 +92,9 @@ def __init__(self, library: Library, tag: Tag | None = None):
self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)

self.subtags_title = QLabel()
self.subtags_title.setText("Parent Tags")
self.subtags_title.setText(
QCoreApplication.translate("build_tags", "parent_tags")
)
self.subtags_layout.addWidget(self.subtags_title)

self.scroll_contents = QWidget()
Expand All @@ -114,7 +116,11 @@ def __init__(self, library: Library, tag: Tag | None = None):
self.subtags_add_button.setText("+")
tsp = TagSearchPanel(self.lib)
tsp.tag_chosen.connect(lambda x: self.add_subtag_callback(x))
self.add_tag_modal = PanelModal(tsp, "Add Parent Tags", "Add Parent Tags")
self.add_tag_modal = PanelModal(
tsp,
QCoreApplication.translate("build_tags", "add_parent_tags"),
QCoreApplication.translate("build_tags", "add_parent_tags"),
)
self.subtags_add_button.clicked.connect(self.add_tag_modal.show)
self.subtags_layout.addWidget(self.subtags_add_button)

Expand All @@ -130,7 +136,7 @@ def __init__(self, library: Library, tag: Tag | None = None):
self.color_layout.setSpacing(0)
self.color_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.color_title = QLabel()
self.color_title.setText("Color")
self.color_title.setText(QCoreApplication.translate("generic", "color"))
self.color_layout.addWidget(self.color_title)
self.color_field = QComboBox()
self.color_field.setEditable(False)
Expand Down Expand Up @@ -161,7 +167,12 @@ def __init__(self, library: Library, tag: Tag | None = None):

# TODO - fill subtags
self.subtags: set[int] = set()
self.set_tag(tag or Tag(name="New Tag"))
self.set_tag(
tag
or Tag(
name=QCoreApplication.translate("tag", "new"),
)
)

def add_subtag_callback(self, tag_id: int):
logger.info("add_subtag_callback", tag_id=tag_id)
Expand Down
33 changes: 20 additions & 13 deletions tagstudio/src/qt/modals/delete_unlinked.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import typing

from PySide6.QtCore import Signal, Qt, QThreadPool
from PySide6.QtCore import Signal, Qt, QThreadPool, QCoreApplication
from PySide6.QtGui import QStandardItemModel, QStandardItem
from PySide6.QtWidgets import (
QWidget,
Expand Down Expand Up @@ -32,7 +32,9 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry):
super().__init__()
self.driver = driver
self.tracker = tracker
self.setWindowTitle("Delete Unlinked Entries")
self.setWindowTitle(
QCoreApplication.translate("delete_unlinked", "delete_unlinked")
)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setMinimumSize(500, 400)
self.root_layout = QVBoxLayout(self)
Expand All @@ -41,9 +43,9 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry):
self.desc_widget = QLabel()
self.desc_widget.setObjectName("descriptionLabel")
self.desc_widget.setWordWrap(True)
self.desc_widget.setText(f"""
Are you sure you want to delete the following {self.tracker.missing_files_count} entries?
""")
self.desc_widget.setText(
QCoreApplication.translate("delete_unlinked", "confirm")
)
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)

self.list_view = QListView()
Expand All @@ -56,13 +58,13 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry):
self.button_layout.addStretch(1)

self.cancel_button = QPushButton()
self.cancel_button.setText("&Cancel")
self.cancel_button.setText(QCoreApplication.translate("generic", "cancel"))
self.cancel_button.setDefault(True)
self.cancel_button.clicked.connect(self.hide)
self.button_layout.addWidget(self.cancel_button)

self.delete_button = QPushButton()
self.delete_button.setText("&Delete")
self.delete_button.setText(QCoreApplication.translate("generic", "delete"))
self.delete_button.clicked.connect(self.hide)
self.delete_button.clicked.connect(lambda: self.delete_entries())
self.button_layout.addWidget(self.delete_button)
Expand All @@ -72,17 +74,19 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry):
self.root_layout.addWidget(self.button_container)

def refresh_list(self):
self.desc_widget.setText(f"""
Are you sure you want to delete the following {self.tracker.missing_files_count} entries?
""")
self.desc_widget.setText(
QCoreApplication.translate("delete_unlinked", "confirm")
)

self.model.clear()
for i in self.tracker.missing_files:
self.model.appendRow(QStandardItem(str(i.path)))

def delete_entries(self):
pw = ProgressWidget(
window_title="Deleting Entries",
window_title=QCoreApplication.translate(
"delete_unlinked", "delete_entries"
),
label_text="",
cancel_button_text=None,
minimum=0,
Expand All @@ -91,11 +95,14 @@ def delete_entries(self):
pw.show()

iterator = FunctionIterator(self.tracker.execute_deletion)
files_count = self.tracker.missing_files_count
iterator.value.connect(
lambda idx: (
pw.update_progress(idx),
pw.update_label(f"Deleting {idx}/{files_count} Unlinked Entries"),
pw.update_label(
QCoreApplication.translate(
"delete_unlinked", "deleting_number_entries"
)
),
)
)

Expand Down
16 changes: 10 additions & 6 deletions tagstudio/src/qt/modals/file_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio


from PySide6.QtCore import Signal, Qt
from PySide6.QtCore import Signal, Qt, QCoreApplication
from PySide6.QtWidgets import (
QVBoxLayout,
QHBoxLayout,
Expand Down Expand Up @@ -40,7 +40,7 @@ def __init__(self, library: "Library"):
super().__init__()
# Initialize Modal =====================================================
self.lib = library
self.setWindowTitle("File Extensions")
self.setWindowTitle()
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setMinimumSize(240, 400)
self.root_layout = QVBoxLayout(self)
Expand All @@ -55,7 +55,9 @@ def __init__(self, library: "Library"):

# Create "Add Button" Widget -------------------------------------------
self.add_button = QPushButton()
self.add_button.setText("&Add Extension")
self.add_button.setText(
QCoreApplication.translate("file_extension", "add_extension")
)
self.add_button.clicked.connect(self.add_item)
self.add_button.setDefault(True)
self.add_button.setMinimumWidth(100)
Expand All @@ -66,11 +68,13 @@ def __init__(self, library: "Library"):
self.mode_layout.setContentsMargins(0, 0, 0, 0)
self.mode_layout.setSpacing(12)
self.mode_label = QLabel()
self.mode_label.setText("List Mode:")
self.mode_label.setText(
QCoreApplication.translate("file_extension", "list_mode")
)
self.mode_combobox = QComboBox()
self.mode_combobox.setEditable(False)
self.mode_combobox.addItem("Include")
self.mode_combobox.addItem("Exclude")
self.mode_combobox.addItem(QCoreApplication.translate("generic", "include"))
self.mode_combobox.addItem(QCoreApplication.translate("generic", "exclude"))

is_exclude_list = int(bool(self.lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST)))

Expand Down
Loading