Skip to content
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

feat: translations #662

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 117 additions & 33 deletions tagstudio/resources/translations/en.json
Original file line number Diff line number Diff line change
@@ -1,120 +1,163 @@
{
"app.git": "Git Commit",
"app.pre_release": "Pre-Release",
"app.title": "{base_title} - Library '{library_dir}'",
"edit.tag_manager": "Manage Tags",
"entries.duplicate.merge.label": "Merging Duplicate Entries",
"entries.duplicate.merge.label": "Merging Duplicate Entries...",
"entries.duplicate.merge": "Merge Duplicate Entries",
"entries.duplicate.refresh": "Refresh Duplicate Entries",
"entries.duplicates.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.",
"entries.mirror.confirmation": "Are you sure you want to mirror the following %{len(self.lib.dupe_files)} Entries?",
"entries.mirror.label": "Mirroring 1/%{count} Entries...",
"entries.mirror.confirmation": "Are you sure you want to mirror the following {count} Entries?",
"entries.mirror.label": "Mirroring {idx}/{total} Entries...",
"entries.mirror.title": "Mirroring Entries",
"entries.mirror": "Mirror",
"entries.mirror.window_title": "Mirror Entries",
"entries.mirror": "&Mirror",
"entries.tags": "Tags",
"entries.unlinked.delete.confirm": "Are you sure you want to delete the following %{len(self.lib.missing_files)} entries?",
"entries.unlinked.delete.deleting_count": "Deleting %{x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries",
"entries.unlinked.delete.confirm": "Are you sure you want to delete the following {count} entries?",
"entries.unlinked.delete.deleting_count": "Deleting {idx}/{count} Unlinked Entries",
"entries.unlinked.delete.deleting": "Deleting Entries",
"entries.unlinked.delete": "Delete Unlinked Entries",
"entries.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 or deleted if desired.",
"entries.unlinked.refresh_all": "Refresh All",
"entries.unlinked.relink.attempting": "Attempting to Relink %{x[0]+1}/%{len(self.lib.missing_files)} Entries, %{self.fixed} Successfully Relinked",
"entries.unlinked.relink.manual": "Manual Relink",
"entries.unlinked.delete_alt": "De&lete Unlinked Entries",
"entries.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.<br><br>Unlinked entries may be automatically relinked via searching your directories or deleted if desired.",
"entries.unlinked.refresh_all": "&Refresh All",
"entries.unlinked.relink.attempting": "Attempting to Relink {idx}/{missing_count} Entries, {fixed_count} Successfully Relinked",
"entries.unlinked.relink.manual": "&Manual Relink",
"entries.unlinked.relink.title": "Relinking Entries",
"entries.unlinked.scanning": "Scanning Library for Unlinked Entries...",
"entries.unlinked.search_and_relink": "Search && Relink",
"entries.unlinked.search_and_relink": "&Search && Relink",
"entries.unlinked.title": "Fix Unlinked Entries",
"entries.unlinked.missing_count.none": "Unlinked Entries: N/A",
"entries.unlinked.missing_count.some": "Unlinked Entries: {count}",
"field.copy": "Copy Field",
"field.edit": "Edit Field",
"field.paste": "Paste Field",
"file.date_added": "Date Added",
"file.date_created": "Date Created",
"file.date_modified": "Date Modified",
"file.dimensions": "Dimensions",
"file.duplicates.description": "TagStudio supports importing DupeGuru results to manage duplicate files.",
"file.duplicates.dupeguru.advice": "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.",
"file.duplicates.dupeguru.file_extension": "DupeGuru Files (*.dupeguru)",
"file.duplicates.dupeguru.load_file": "Load DupeGuru File",
"file.duplicates.dupeguru.load_file": "&Load DupeGuru File",
"file.duplicates.dupeguru.no_file": "No DupeGuru File Selected",
"file.duplicates.dupeguru.open_file": "Open DupeGuru Results File",
"file.duplicates.fix": "Fix Duplicate Files",
"file.duplicates.matches_uninitialized": "Duplicate File Matches: N/A",
"file.duplicates.matches": "Duplicate File Matches: %{count}",
"file.duplicates.mirror_entries": "Mirror Entries",
"file.duplicates.matches": "Duplicate File Matches: {count}",
"file.duplicates.mirror_entries": "&Mirror Entries",
"file.duplicates.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.",
"file.duration": "Length",
"file.not_found": "File Not Found",
"file.open_file_with": "Open file with",
"file.open_file": "Open file",
"file.open_location.generic": "Show file in explorer",
"file.open_location.generic": "Show file in file explorer",
"file.open_location.mac": "Reveal in Finder",
"file.open_location.windows": "Show in File Explorer",
"file.open_location.windows": "Show in Explorer",
"folders_to_tags.close_all": "Close All",
"folders_to_tags.converting": "Converting folders to Tags",
"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.",
"folders_to_tags.open_all": "Open All",
"folders_to_tags.title": "Create Tags From Folders",
"generic.add": "Add",
"generic.apply": "Apply",
"generic.apply_alt": "&Apply",
"generic.cancel": "Cancel",
"generic.cancel_alt": "&Cancel",
"generic.close": "Close",
"generic.copy": "Copy",
"generic.cut": "Cut",
"generic.save": "Save",
"generic.delete": "Delete",
"generic.delete_alt": "&Delete",
"generic.continue": "Continue",
"generic.done": "Done",
"generic.done_alt": "&Done",
"generic.edit": "Edit",
"generic.edit_alt": "&Edit",
"generic.rename": "Rename",
"generic.rename_alt": "&Rename",
"generic.overwrite": "Overwrite",
"generic.overwrite_alt": "&Overwrite",
"generic.skip": "Skip",
"generic.skip_alt": "&Skip",
"generic.navigation.back": "Back",
"generic.navigation.next": "Next",
"generic.paste": "Paste",
"generic.recent_libraries": "Recent Libraries",
"generic.filename": "Filename",
"help.visit_github": "Visit GitHub Repository",
"home.search_entries": "Search Entries",
"home.search_library": "Search Library",
"home.search_tags": "Search Tags",
"home.search": "Search",
"home.thumbnail_size.extra_large": "Extra Large Thumbnails",
"home.thumbnail_size.large": "Large Thumbnails",
"home.thumbnail_size.medium": "Medium Thumbnails",
"home.thumbnail_size.small": "Small Thumbnails",
"home.thumbnail_size.mini": "Mini Thumbnails",
"home.thumbnail_size": "Thumbnail Size",
"ignore_list.add_extension": "Add Extension",
"ignore_list.add_extension": "&Add Extension",
"ignore_list.mode.exclude": "Exclude",
"ignore_list.mode.include": "Include",
"ignore_list.mode.label": "List Mode",
"ignore_list.mode.label": "List Mode:",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be better to keep the colon out of the base translation then add it after the fact for any labels that benefit from it? To me I feel this would allow us to be more flexible with UI changes without needing to update the translations for things like this, but I'd like to get your thoughts.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depends on whether colons are universally used in all languages (which I kind of doubt), because if not there would be some value in leaving that to translators. Code wise it is cleaner to leave it to the translation strings, but that's not really important besides maybe being a tie breaker

"ignore_list.title": "File Extensions",
"library.field.add": "Add Field",
"library.field.confirm_remove": "Are you sure you want to remove this \"%{self.lib.get_field_attr(field, \"name\")}\" field?",
"library.field.confirm_remove": "Are you sure you want to remove this \"{name}\" field?",
"library.field.mixed_data": "Mixed Data",
"library.field.remove": "Remove Field",
"library.missing": "Library Location is Missing",
"library.name": "Library",
"library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...",
"library.refresh.scanning": "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",
"library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found",
"library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found",
"library.refresh.title": "Refreshing Directories",
"library.scan_library.title": "Scanning Library",
"macros.running.dialog.new_entries": "Running Configured Macros on %{x + 1}/%{len(new_ids)} New Entries",
"macros.running.dialog.new_entries": "Running Configured Macros on {count}/{total} New Entries",
"macros.running.dialog.title": "Running Macros on New Entries",
"menu.edit.ignore_list": "Ignore Files and Folders",
"menu.edit.manage_file_extensions": "Manage File Extensions",
"menu.edit.manage_tags": "Manage Tags",
"menu.edit.new_tag": "New &Tag",
"menu.edit": "Edit",
"menu.file.close_library": "&Close Library",
"menu.file.new_library": "New Library",
"menu.file.open_create_library": "Open/Create Library",
"menu.file.open_create_library": "&Open/Create Library",
"menu.file.open_library": "Open Library",
"menu.file.save_library": "Save Library",
"menu.file": "File",
"menu.help": "Help",
"menu.macros": "Macros",
"menu.file.save_backup": "&Save Library Backup",
"menu.file.refresh_directories": "&Refresh Directories",
"menu.file": "&File",
"menu.help": "&Help",
"menu.macros.folders_to_tags": "Folders to Tags",
"menu.macros": "&Macros",
"menu.select": "Select",
"menu.tools": "Tools",
"menu.view": "View",
"menu.tools.fix_duplicate_files": "Fix Duplicate &Files",
"menu.tools.fix_unlinked_entries": "Fix &Unlinked Entries",
"menu.tools": "&Tools",
"menu.view": "&View",
"menu.window": "Window",
"preview.no_selection": "No Items Selected",
"select.all": "Select All",
"select.clear": "Clear Selection",
"settings.open_library_on_start": "Open Library on Start",
"settings.show_filenames_in_grid": "Show Filenames in Grid",
"settings.show_recent_libraries": "Show Recent Libraries",
"splash.opening_library": "Opening Library",
"status.library_backup_success": "Library Backup Saved at:",
"splash.opening_library": "Opening Library \"{library_path}\"...",
"status.library_closing": "Closing Library...",
"status.library_closed": "Library Closed ({time_span})",
"status.library_backup_in_progress": "Saving Library Backup...",
"status.library_backup_success": "Library Backup Saved at: \"{path}\" ({time_span})",
"status.library_save_success": "Library Saved and Closed!",
"status.library_search_query": "Searching Library for",
"status.results_found": "{results.total_count} Results Found",
"status.library_search_query": "Searching Library...",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: In v9.4 the "Searching Library for" would be followed by the search query in the status bar, though currently I don't see the status bar being updated with this at all, even when trying to artificially slow them down. I'm fine with this change, though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Searching Library... gets shown for me (if only for a very short time).

In v9.4 the "Searching Library for" would be followed by the search query in the status bar

This changed in the Search PR as with the new search that would just mean repeating the query that is already in the search bar

"status.results_found": "{count} Results Found ({time_span})",
"status.results": "Results",
"tag_manager.title": "Library Tags",
"tag.add_to_search": "Add to Search",
"tag.edit": "Edit Tag",
"tag.add": "Add Tag",
"tag.add.plural": "Add Tags",
"tag.create": "Create Tag",
"tag.remove": "Remove Tag",
"tag.aliases": "Aliases",
"tag.color": "Color",
"tag.name": "Name",
Expand All @@ -124,9 +167,50 @@
"tag.parent_tags": "Parent Tags",
"tag.search_for_tag": "Search for Tag",
"tag.shorthand": "Shorthand",
"tag.tag_name_required": "Tag Name (Required)",
"tag.confirm_delete": "Are you sure you want to delete the tag \"{tag_name}\"?",
"view.size.0": "Mini",
"view.size.1": "Small",
"view.size.2": "Medium",
"view.size.3": "Large",
"view.size.4": "Extra Large"
}
"view.size.4": "Extra Large",
"window.title.error": "Error",
"window.title.open_create_library": "Open/Create Library",
"window.message.error_opening_library": "Error opening library.",
"drop_import.title": "Conflicting File(s)",
"drop_import.decription": "The following files have filenames already exist in the library",
"drop_import.progress.label.initial": "Importing New Files...",
"drop_import.progress.label.singular": "Importing New Files...\n1 File imported.{suffix}",
"drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}",
"drop_import.progress.window_title": "Import Files",
"drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.",
"drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.",
"landing.open_create_library": "Open/Create Library {shortcut}",
"media_player.autoplay": "Autoplay",
"json_migration.title": "Save Format Migration: \"{path}\"",
"json_migration.info.description": "Library save files created with TagStudio versions <b>9.4 and below</b> will need to be migrated to the new <b>v9.5+</b> format.<br><h2>What you need to know:</h2><ul><li>Your existing library save file will <b><i>NOT</i></b> be deleted</li><li>Your personal files will <b><i>NOT</i></b> be deleted, moved, or modified</li><li>The new v9.5+ save format can not be opened in earlier versions of TagStudio</li></ul>",
"json_migration.title.old_lib": "<h2>v9.4 Library</h2>",
"json_migration.title.new_lib": "<h2>v9.5+ Library</h2>",
"json_migration.start_and_preview": "Start and Preview",
"json_migration.finish_migration": "Finish Migration",
"json_migration.checking_for_parity": "Checking for Parity...",
"json_migration.creating_database_tables": "Creating SQL Database Tables...",
"json_migration.migrating_files_entries": "Migrating {entries:,d} File Entries...",
"json_migration.migration_complete": "Migration Complete!",
"json_migration.migration_complete_with_discrepancies": "Migration Complete, Discrepancies Found",
"json_migration.discrepancies_found": "Library Discrepancies Found",
"json_migration.discrepancies_found.description": "Discrepancies were found between the original and converted library formats. Please review and choose to whether continue with the migration or to cancel.",
"json_migration.heading.match": "Matched",
"json_migration.heading.differ": "Discrepancy",
"json_migration.heading.entires": "Entries:",
"json_migration.heading.tags": "Tags:",
"json_migration.heading.shorthands": "Shorthands:",
"json_migration.heading.parent_tags": "Parent Tags:",
"json_migration.heading.aliases": "Aliases:",
"json_migration.heading.colors": "Colors:",
"json_migration.heading.file_extension_list": "File Extension List:",
Comment on lines +205 to +211
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be possible to reuse some or all of these from the existing translation keys while adding a colon at the end for this part of the UI

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as per my count 3 out of 10 headings could be taken from somewhere else. I think it would be better to have them seperate here (I think in general there is value having keys with duplicate values as it makes it simpler down the line when a string needs to change for one of the multiple uses but not the others; it is obviously more work to translate, but I think Weblate recognizes when the base string is the same and suggests other existing translations for that string, so it should just be copy-pasting)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: The ones that are the same as others are json_migration.heading.tags, json_migration.heading.parent_tags and json_migration.heading.aliases

"json_migration.heading.extension_list_type": "Extension List Type:",
"json_migration.heading.paths": "Paths:",
"json_migration.heading.fields": "Fields:",
"json_migration.description": "<br>Start and preview the results of the library migration process. The converted library will <i>not</i> be used unless you click \"Finish Migration\". <br><br>Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"<b>(!)</b>\" symbol next to them.<br><center><i>This process may take up to several minutes for larger libraries.</i></center>"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The newline was removed here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not quite certain what you mean by that

34 changes: 8 additions & 26 deletions tagstudio/src/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from src.qt.pagination import Pagination
from src.qt.widgets.landing import LandingWidget

from src.qt.translations import Translations

# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:
from src.qt.ts_qt import QtDriver
Expand Down Expand Up @@ -77,6 +79,8 @@ def setupUi(self, MainWindow):
# Thumbnail Size placeholder
self.thumb_size_combobox = QComboBox(self.centralwidget)
self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox")
Translations.translate_with_setter(self.thumb_size_combobox.setPlaceholderText, "home.thumbnail_size")
self.thumb_size_combobox.setCurrentText("")
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
Expand Down Expand Up @@ -128,7 +132,7 @@ def setupUi(self, MainWindow):
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize)
self.backButton = QPushButton(self.centralwidget)
self.backButton = QPushButton("<", self.centralwidget)
self.backButton.setObjectName(u"backButton")
self.backButton.setMinimumSize(QSize(0, 32))
self.backButton.setMaximumSize(QSize(32, 16777215))
Expand All @@ -139,7 +143,7 @@ def setupUi(self, MainWindow):

self.horizontalLayout_2.addWidget(self.backButton)

self.forwardButton = QPushButton(self.centralwidget)
self.forwardButton = QPushButton(">", self.centralwidget)
self.forwardButton.setObjectName(u"forwardButton")
self.forwardButton.setMinimumSize(QSize(0, 32))
self.forwardButton.setMaximumSize(QSize(32, 16777215))
Expand All @@ -152,6 +156,7 @@ def setupUi(self, MainWindow):
self.horizontalLayout_2.addWidget(self.forwardButton)

self.searchField = QLineEdit(self.centralwidget)
Translations.translate_with_setter(self.searchField.setPlaceholderText, "home.search_entries")
self.searchField.setObjectName(u"searchField")
self.searchField.setMinimumSize(QSize(0, 32))
font2 = QFont()
Expand All @@ -167,6 +172,7 @@ def setupUi(self, MainWindow):
self.horizontalLayout_2.addWidget(self.searchField)

self.searchButton = QPushButton(self.centralwidget)
Translations.translate_qobject(self.searchButton, "home.search")
self.searchButton.setObjectName(u"searchButton")
self.searchButton.setMinimumSize(QSize(0, 32))
self.searchButton.setFont(font2)
Expand All @@ -186,33 +192,9 @@ def setupUi(self, MainWindow):
self.statusbar.setSizePolicy(sizePolicy1)
MainWindow.setStatusBar(self.statusbar)

self.retranslateUi(MainWindow)

QMetaObject.connectSlotsByName(MainWindow)
# setupUi

def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate(
"MainWindow", u"MainWindow", None))
# Navigation buttons
self.backButton.setText(
QCoreApplication.translate("MainWindow", u"<", None))
self.forwardButton.setText(
QCoreApplication.translate("MainWindow", u">", None))

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

self.thumb_size_combobox.setCurrentText("")

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

def moveEvent(self, event) -> None:
# time.sleep(0.02) # sleep for 20ms
pass
Expand Down
Loading
Loading