Skip to content

Commit 69aed92

Browse files
feat: translations (#662)
* feat: implement Translator class * feat: add translate_with_setter and implement formatting of translations * feat: extend PanelModal to allow for translation * feat: extend ProgressWidget to allow for translation * feat: translation in ts_qt.py * debug: set default lang to DE and show "Not Translated" when replacing untranslated stuff * add translation todos * feat: translate modals * feat: translate more stuff * fix: UI test wasn't comparing to translated strings * feat: translations for most of the remaining stuff * fix: replace debug changes with simpler one * uncomment debug change * fix: missing parameter in call * fix: various review feedback * fix: don't translate json migration discrepancies list * fix: typo * fix: various PR feedback * fix: correctly read non-ascii characters * fix: add missing new line at eof * fix: comment out line of debug code * fix: address latest review comment * fix: KeyError that occurred when formatting translations * fix: regression of d594e84 * fix: add newline to en.json * fix: organize en.json, fix typo --------- Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
1 parent 40bfee0 commit 69aed92

28 files changed

+620
-341
lines changed
Lines changed: 115 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
11
{
22
"app.git": "Git Commit",
33
"app.pre_release": "Pre-Release",
4+
"app.title": "{base_title} - Library '{library_dir}'",
5+
"drop_import.description": "The following files have filenames already exist in the library",
6+
"drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.",
7+
"drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.",
8+
"drop_import.progress.label.initial": "Importing New Files...",
9+
"drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}",
10+
"drop_import.progress.label.singular": "Importing New Files...\n1 File imported.{suffix}",
11+
"drop_import.progress.window_title": "Import Files",
12+
"drop_import.title": "Conflicting File(s)",
413
"edit.tag_manager": "Manage Tags",
5-
"entries.duplicate.merge.label": "Merging Duplicate Entries",
14+
"entries.duplicate.merge.label": "Merging Duplicate Entries...",
615
"entries.duplicate.merge": "Merge Duplicate Entries",
716
"entries.duplicate.refresh": "Refresh Duplicate Entries",
817
"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.",
9-
"entries.mirror.confirmation": "Are you sure you want to mirror the following %{len(self.lib.dupe_files)} Entries?",
10-
"entries.mirror.label": "Mirroring 1/%{count} Entries...",
18+
"entries.mirror.confirmation": "Are you sure you want to mirror the following {count} Entries?",
19+
"entries.mirror.label": "Mirroring {idx}/{total} Entries...",
1120
"entries.mirror.title": "Mirroring Entries",
12-
"entries.mirror": "Mirror",
21+
"entries.mirror.window_title": "Mirror Entries",
22+
"entries.mirror": "&Mirror",
1323
"entries.tags": "Tags",
14-
"entries.unlinked.delete.confirm": "Are you sure you want to delete the following %{len(self.lib.missing_files)} entries?",
15-
"entries.unlinked.delete.deleting_count": "Deleting %{x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries",
24+
"entries.unlinked.delete_alt": "De&lete Unlinked Entries",
25+
"entries.unlinked.delete.confirm": "Are you sure you want to delete the following {count} entries?",
26+
"entries.unlinked.delete.deleting_count": "Deleting {idx}/{count} Unlinked Entries",
1627
"entries.unlinked.delete.deleting": "Deleting Entries",
1728
"entries.unlinked.delete": "Delete Unlinked Entries",
18-
"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.",
19-
"entries.unlinked.refresh_all": "Refresh All",
20-
"entries.unlinked.relink.attempting": "Attempting to Relink %{x[0]+1}/%{len(self.lib.missing_files)} Entries, %{self.fixed} Successfully Relinked",
21-
"entries.unlinked.relink.manual": "Manual Relink",
29+
"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.",
30+
"entries.unlinked.missing_count.none": "Unlinked Entries: N/A",
31+
"entries.unlinked.missing_count.some": "Unlinked Entries: {count}",
32+
"entries.unlinked.refresh_all": "&Refresh All",
33+
"entries.unlinked.relink.attempting": "Attempting to Relink {idx}/{missing_count} Entries, {fixed_count} Successfully Relinked",
34+
"entries.unlinked.relink.manual": "&Manual Relink",
2235
"entries.unlinked.relink.title": "Relinking Entries",
2336
"entries.unlinked.scanning": "Scanning Library for Unlinked Entries...",
24-
"entries.unlinked.search_and_relink": "Search && Relink",
37+
"entries.unlinked.search_and_relink": "&Search && Relink",
2538
"entries.unlinked.title": "Fix Unlinked Entries",
2639
"field.copy": "Copy Field",
2740
"field.edit": "Edit Field",
@@ -30,21 +43,22 @@
3043
"file.date_created": "Date Created",
3144
"file.date_modified": "Date Modified",
3245
"file.dimensions": "Dimensions",
46+
"file.duplicates.description": "TagStudio supports importing DupeGuru results to manage duplicate files.",
3347
"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.",
3448
"file.duplicates.dupeguru.file_extension": "DupeGuru Files (*.dupeguru)",
35-
"file.duplicates.dupeguru.load_file": "Load DupeGuru File",
49+
"file.duplicates.dupeguru.load_file": "&Load DupeGuru File",
3650
"file.duplicates.dupeguru.no_file": "No DupeGuru File Selected",
3751
"file.duplicates.dupeguru.open_file": "Open DupeGuru Results File",
3852
"file.duplicates.fix": "Fix Duplicate Files",
3953
"file.duplicates.matches_uninitialized": "Duplicate File Matches: N/A",
40-
"file.duplicates.matches": "Duplicate File Matches: %{count}",
41-
"file.duplicates.mirror_entries": "Mirror Entries",
54+
"file.duplicates.matches": "Duplicate File Matches: {count}",
55+
"file.duplicates.mirror_entries": "&Mirror Entries",
4256
"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.",
4357
"file.duration": "Length",
4458
"file.not_found": "File Not Found",
4559
"file.open_file_with": "Open file with",
4660
"file.open_file": "Open file",
47-
"file.open_location.generic": "Show file in explorer",
61+
"file.open_location.generic": "Show file in file explorer",
4862
"file.open_location.mac": "Reveal in Finder",
4963
"file.open_location.windows": "Show in File Explorer",
5064
"folders_to_tags.close_all": "Close All",
@@ -53,80 +67,150 @@
5367
"folders_to_tags.open_all": "Open All",
5468
"folders_to_tags.title": "Create Tags From Folders",
5569
"generic.add": "Add",
70+
"generic.apply_alt": "&Apply",
5671
"generic.apply": "Apply",
72+
"generic.cancel_alt": "&Cancel",
5773
"generic.cancel": "Cancel",
74+
"generic.close": "Close",
75+
"generic.continue": "Continue",
5876
"generic.copy": "Copy",
5977
"generic.cut": "Cut",
78+
"generic.delete_alt": "&Delete",
6079
"generic.delete": "Delete",
80+
"generic.done_alt": "&Done",
6181
"generic.done": "Done",
82+
"generic.edit_alt": "&Edit",
6283
"generic.edit": "Edit",
84+
"generic.filename": "Filename",
6385
"generic.navigation.back": "Back",
6486
"generic.navigation.next": "Next",
87+
"generic.overwrite_alt": "&Overwrite",
88+
"generic.overwrite": "Overwrite",
6589
"generic.paste": "Paste",
6690
"generic.recent_libraries": "Recent Libraries",
91+
"generic.rename_alt": "&Rename",
92+
"generic.rename": "Rename",
93+
"generic.save": "Save",
94+
"generic.skip_alt": "&Skip",
95+
"generic.skip": "Skip",
6796
"help.visit_github": "Visit GitHub Repository",
6897
"home.search_entries": "Search Entries",
6998
"home.search_library": "Search Library",
7099
"home.search_tags": "Search Tags",
71100
"home.search": "Search",
101+
"home.thumbnail_size.extra_large": "Extra Large Thumbnails",
102+
"home.thumbnail_size.large": "Large Thumbnails",
103+
"home.thumbnail_size.medium": "Medium Thumbnails",
104+
"home.thumbnail_size.mini": "Mini Thumbnails",
105+
"home.thumbnail_size.small": "Small Thumbnails",
72106
"home.thumbnail_size": "Thumbnail Size",
73-
"ignore_list.add_extension": "Add Extension",
107+
"ignore_list.add_extension": "&Add Extension",
74108
"ignore_list.mode.exclude": "Exclude",
75109
"ignore_list.mode.include": "Include",
76-
"ignore_list.mode.label": "List Mode",
110+
"ignore_list.mode.label": "List Mode:",
77111
"ignore_list.title": "File Extensions",
112+
"json_migration.checking_for_parity": "Checking for Parity...",
113+
"json_migration.creating_database_tables": "Creating SQL Database Tables...",
114+
"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>",
115+
"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.",
116+
"json_migration.discrepancies_found": "Library Discrepancies Found",
117+
"json_migration.finish_migration": "Finish Migration",
118+
"json_migration.heading.aliases": "Aliases:",
119+
"json_migration.heading.colors": "Colors:",
120+
"json_migration.heading.differ": "Discrepancy",
121+
"json_migration.heading.entires": "Entries:",
122+
"json_migration.heading.extension_list_type": "Extension List Type:",
123+
"json_migration.heading.fields": "Fields:",
124+
"json_migration.heading.file_extension_list": "File Extension List:",
125+
"json_migration.heading.match": "Matched",
126+
"json_migration.heading.parent_tags": "Parent Tags:",
127+
"json_migration.heading.paths": "Paths:",
128+
"json_migration.heading.shorthands": "Shorthands:",
129+
"json_migration.heading.tags": "Tags:",
130+
"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>",
131+
"json_migration.migrating_files_entries": "Migrating {entries:,d} File Entries...",
132+
"json_migration.migration_complete_with_discrepancies": "Migration Complete, Discrepancies Found",
133+
"json_migration.migration_complete": "Migration Complete!",
134+
"json_migration.start_and_preview": "Start and Preview",
135+
"json_migration.title.new_lib": "<h2>v9.5+ Library</h2>",
136+
"json_migration.title.old_lib": "<h2>v9.4 Library</h2>",
137+
"json_migration.title": "Save Format Migration: \"{path}\"",
138+
"landing.open_create_library": "Open/Create Library {shortcut}",
78139
"library.field.add": "Add Field",
79-
"library.field.confirm_remove": "Are you sure you want to remove this \"%{self.lib.get_field_attr(field, \"name\")}\" field?",
140+
"library.field.confirm_remove": "Are you sure you want to remove this \"{name}\" field?",
80141
"library.field.mixed_data": "Mixed Data",
81142
"library.field.remove": "Remove Field",
82143
"library.missing": "Library Location is Missing",
83144
"library.name": "Library",
84145
"library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...",
85-
"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",
146+
"library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found",
147+
"library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found",
86148
"library.refresh.title": "Refreshing Directories",
87149
"library.scan_library.title": "Scanning Library",
88-
"macros.running.dialog.new_entries": "Running Configured Macros on %{x + 1}/%{len(new_ids)} New Entries",
150+
"macros.running.dialog.new_entries": "Running Configured Macros on {count}/{total} New Entries",
89151
"macros.running.dialog.title": "Running Macros on New Entries",
152+
"media_player.autoplay": "Autoplay",
90153
"menu.edit.ignore_list": "Ignore Files and Folders",
154+
"menu.edit.manage_file_extensions": "Manage File Extensions",
155+
"menu.edit.manage_tags": "Manage Tags",
156+
"menu.edit.new_tag": "New &Tag",
91157
"menu.edit": "Edit",
158+
"menu.file.close_library": "&Close Library",
92159
"menu.file.new_library": "New Library",
93-
"menu.file.open_create_library": "Open/Create Library",
160+
"menu.file.open_create_library": "&Open/Create Library",
94161
"menu.file.open_library": "Open Library",
162+
"menu.file.refresh_directories": "&Refresh Directories",
163+
"menu.file.save_backup": "&Save Library Backup",
95164
"menu.file.save_library": "Save Library",
96-
"menu.file": "File",
97-
"menu.help": "Help",
98-
"menu.macros": "Macros",
165+
"menu.file": "&File",
166+
"menu.help": "&Help",
167+
"menu.macros.folders_to_tags": "Folders to Tags",
168+
"menu.macros": "&Macros",
99169
"menu.select": "Select",
100-
"menu.tools": "Tools",
101-
"menu.view": "View",
170+
"menu.tools.fix_duplicate_files": "Fix Duplicate &Files",
171+
"menu.tools.fix_unlinked_entries": "Fix &Unlinked Entries",
172+
"menu.tools": "&Tools",
173+
"menu.view": "&View",
102174
"menu.window": "Window",
103175
"preview.no_selection": "No Items Selected",
104176
"select.all": "Select All",
105177
"select.clear": "Clear Selection",
106178
"settings.open_library_on_start": "Open Library on Start",
107179
"settings.show_filenames_in_grid": "Show Filenames in Grid",
108180
"settings.show_recent_libraries": "Show Recent Libraries",
109-
"splash.opening_library": "Opening Library",
110-
"status.library_backup_success": "Library Backup Saved at:",
181+
"splash.opening_library": "Opening Library \"{library_path}\"...",
182+
"status.library_backup_in_progress": "Saving Library Backup...",
183+
"status.library_backup_success": "Library Backup Saved at: \"{path}\" ({time_span})",
184+
"status.library_closed": "Library Closed ({time_span})",
185+
"status.library_closing": "Closing Library...",
111186
"status.library_save_success": "Library Saved and Closed!",
112-
"status.library_search_query": "Searching Library for",
113-
"status.results_found": "{results.total_count} Results Found",
187+
"status.library_search_query": "Searching Library...",
188+
"status.results_found": "{count} Results Found ({time_span})",
114189
"status.results": "Results",
115190
"tag_manager.title": "Library Tags",
116191
"tag.add_to_search": "Add to Search",
192+
"tag.add.plural": "Add Tags",
117193
"tag.add": "Add Tag",
118194
"tag.aliases": "Aliases",
119195
"tag.color": "Color",
196+
"tag.confirm_delete": "Are you sure you want to delete the tag \"{tag_name}\"?",
197+
"tag.create": "Create Tag",
198+
"tag.edit": "Edit Tag",
120199
"tag.name": "Name",
121200
"tag.new": "New Tag",
122201
"tag.parent_tags.add": "Add Parent Tag(s)",
123202
"tag.parent_tags.description": "This tag can be treated as a substitute for any of these Parent Tags in searches.",
124203
"tag.parent_tags": "Parent Tags",
204+
"tag.remove": "Remove Tag",
125205
"tag.search_for_tag": "Search for Tag",
126206
"tag.shorthand": "Shorthand",
207+
"tag.tag_name_required": "Tag Name (Required)",
127208
"view.size.0": "Mini",
128209
"view.size.1": "Small",
129210
"view.size.2": "Medium",
130211
"view.size.3": "Large",
131-
"view.size.4": "Extra Large"
212+
"view.size.4": "Extra Large",
213+
"window.message.error_opening_library": "Error opening library.",
214+
"window.title.error": "Error",
215+
"window.title.open_create_library": "Open/Create Library"
132216
}

tagstudio/src/qt/main_window.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from src.qt.pagination import Pagination
2626
from src.qt.widgets.landing import LandingWidget
2727

28+
from src.qt.translations import Translations
29+
2830
# Only import for type checking/autocompletion, will not be imported at runtime.
2931
if typing.TYPE_CHECKING:
3032
from src.qt.ts_qt import QtDriver
@@ -77,6 +79,8 @@ def setupUi(self, MainWindow):
7779
# Thumbnail Size placeholder
7880
self.thumb_size_combobox = QComboBox(self.centralwidget)
7981
self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox")
82+
Translations.translate_with_setter(self.thumb_size_combobox.setPlaceholderText, "home.thumbnail_size")
83+
self.thumb_size_combobox.setCurrentText("")
8084
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
8185
sizePolicy.setHorizontalStretch(0)
8286
sizePolicy.setVerticalStretch(0)
@@ -128,7 +132,7 @@ def setupUi(self, MainWindow):
128132
self.horizontalLayout_2 = QHBoxLayout()
129133
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
130134
self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize)
131-
self.backButton = QPushButton(self.centralwidget)
135+
self.backButton = QPushButton("<", self.centralwidget)
132136
self.backButton.setObjectName(u"backButton")
133137
self.backButton.setMinimumSize(QSize(0, 32))
134138
self.backButton.setMaximumSize(QSize(32, 16777215))
@@ -139,7 +143,7 @@ def setupUi(self, MainWindow):
139143

140144
self.horizontalLayout_2.addWidget(self.backButton)
141145

142-
self.forwardButton = QPushButton(self.centralwidget)
146+
self.forwardButton = QPushButton(">", self.centralwidget)
143147
self.forwardButton.setObjectName(u"forwardButton")
144148
self.forwardButton.setMinimumSize(QSize(0, 32))
145149
self.forwardButton.setMaximumSize(QSize(32, 16777215))
@@ -152,6 +156,7 @@ def setupUi(self, MainWindow):
152156
self.horizontalLayout_2.addWidget(self.forwardButton)
153157

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

169174
self.searchButton = QPushButton(self.centralwidget)
175+
Translations.translate_qobject(self.searchButton, "home.search")
170176
self.searchButton.setObjectName(u"searchButton")
171177
self.searchButton.setMinimumSize(QSize(0, 32))
172178
self.searchButton.setFont(font2)
@@ -186,33 +192,9 @@ def setupUi(self, MainWindow):
186192
self.statusbar.setSizePolicy(sizePolicy1)
187193
MainWindow.setStatusBar(self.statusbar)
188194

189-
self.retranslateUi(MainWindow)
190-
191195
QMetaObject.connectSlotsByName(MainWindow)
192196
# setupUi
193197

194-
def retranslateUi(self, MainWindow):
195-
MainWindow.setWindowTitle(QCoreApplication.translate(
196-
"MainWindow", u"MainWindow", None))
197-
# Navigation buttons
198-
self.backButton.setText(
199-
QCoreApplication.translate("MainWindow", u"<", None))
200-
self.forwardButton.setText(
201-
QCoreApplication.translate("MainWindow", u">", None))
202-
203-
# Search field
204-
self.searchField.setPlaceholderText(
205-
QCoreApplication.translate("MainWindow", u"Search Entries", None))
206-
self.searchButton.setText(
207-
QCoreApplication.translate("MainWindow", u"Search", None))
208-
209-
self.thumb_size_combobox.setCurrentText("")
210-
211-
# Thumbnail size selector
212-
self.thumb_size_combobox.setPlaceholderText(
213-
QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
214-
# retranslateUi
215-
216198
def moveEvent(self, event) -> None:
217199
# time.sleep(0.02) # sleep for 20ms
218200
pass

0 commit comments

Comments
 (0)