From 6418993840e98c19547056fd7f2c322fec5d3fbd Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 17 Nov 2020 17:42:43 +1000 Subject: [PATCH] merge bulk of qt/ - designer files still to do --- qt/aqt/about.py | 10 +- qt/aqt/addcards.py | 24 ++--- qt/aqt/addons.py | 91 +++++++++--------- qt/aqt/browser.py | 176 +++++++++++++++++++---------------- qt/aqt/clayout.py | 51 +++++----- qt/aqt/customstudy.py | 43 +++++---- qt/aqt/deckbrowser.py | 33 ++++--- qt/aqt/deckchooser.py | 19 ++-- qt/aqt/deckconf.py | 29 +++--- qt/aqt/dyndeckconf.py | 14 +-- qt/aqt/editcurrent.py | 5 +- qt/aqt/editor.py | 92 +++++++++++------- qt/aqt/errors.py | 12 +-- qt/aqt/exporting.py | 16 ++-- qt/aqt/fields.py | 20 ++-- qt/aqt/importing.py | 57 +++++------- qt/aqt/main.py | 57 ++++++------ qt/aqt/modelchooser.py | 12 +-- qt/aqt/models.py | 32 ++++--- qt/aqt/overview.py | 35 ++++--- qt/aqt/preferences.py | 15 ++- qt/aqt/previewer.py | 15 ++- qt/aqt/profiles.py | 20 ++-- qt/aqt/progress.py | 4 +- qt/aqt/reviewer.py | 108 +++++++++++---------- qt/aqt/sidebar.py | 9 +- qt/aqt/sound.py | 16 ++-- qt/aqt/studydeck.py | 22 +++-- qt/aqt/toolbar.py | 22 ++--- qt/aqt/update.py | 8 +- qt/aqt/utils.py | 15 ++- qt/aqt/webview.py | 5 +- repos.bzl | 51 +++++----- rslib/ftl/actions.ftl | 32 +++++++ rslib/ftl/adding.ftl | 9 ++ rslib/ftl/browsing.ftl | 104 +++++++++++++++++++++ rslib/ftl/card-templates.ftl | 19 ++++ rslib/ftl/custom-study.ftl | 27 ++++++ rslib/ftl/decks.ftl | 33 +++++++ rslib/ftl/editing.ftl | 35 +++++++ rslib/ftl/exporting.ftl | 17 ++++ rslib/ftl/fields.ftl | 14 +++ rslib/ftl/importing.ftl | 48 ++++++++++ rslib/ftl/media.ftl | 7 ++ rslib/ftl/notetypes.ftl | 15 +++ rslib/ftl/preferences.ftl | 39 ++++++++ rslib/ftl/profiles.ftl | 10 ++ rslib/ftl/scheduling.ftl | 48 ++++++++++ rslib/ftl/studying.ftl | 44 +++++++++ 49 files changed, 1094 insertions(+), 545 deletions(-) create mode 100644 rslib/ftl/actions.ftl create mode 100644 rslib/ftl/adding.ftl create mode 100644 rslib/ftl/browsing.ftl create mode 100644 rslib/ftl/decks.ftl create mode 100644 rslib/ftl/editing.ftl create mode 100644 rslib/ftl/exporting.ftl create mode 100644 rslib/ftl/fields.ftl create mode 100644 rslib/ftl/media.ftl create mode 100644 rslib/ftl/preferences.ftl create mode 100644 rslib/ftl/profiles.ftl create mode 100644 rslib/ftl/studying.ftl diff --git a/qt/aqt/about.py b/qt/aqt/about.py index c73de49b4..5fa4ded54 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -9,7 +9,7 @@ from anki.lang import _ from anki.utils import versionWithBuild from aqt.addons import AddonManager, AddonMeta from aqt.qt import * -from aqt.utils import supportText, tooltip +from aqt.utils import TR, supportText, tooltip, tr class ClosableQDialog(QDialog): @@ -83,9 +83,9 @@ def show(mw): """ info = " " + " ".join(info.splitlines(True)) QApplication.clipboard().setText(info) - tooltip(_("Copied to clipboard"), parent=dialog) + tooltip(tr(TR.ABOUT_COPIED_TO_CLIPBOARD), parent=dialog) - btn = QPushButton(_("Copy Debug Info")) + btn = QPushButton(tr(TR.ABOUT_COPY_DEBUG_INFO)) qconnect(btn.clicked, onCopy) abt.buttonBox.addButton(btn, QDialogButtonBox.ActionRole) abt.buttonBox.button(QDialogButtonBox.Ok).setFocus() @@ -101,13 +101,13 @@ system. It's free and open source." "Anki is licensed under the AGPL3 license. Please see " "the license file in the source distribution for more information." ) - abouttext += "

" + _("Version %s") % versionWithBuild() + "
" + abouttext += "

" + tr(TR.ABOUT_VERSION, val="%s") % versionWithBuild() + "
" abouttext += ("Python %s Qt %s PyQt %s
") % ( platform.python_version(), QT_VERSION_STR, PYQT_VERSION_STR, ) - abouttext += (_("Visit website") % aqt.appWebsite) + "" + abouttext += (tr(TR.ABOUT_VISIT_WEBSITE, val="%s") % aqt.appWebsite) + "" # automatically sorted; add new lines at the end allusers = sorted( diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 127a2898e..b4a67cfa9 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -16,6 +16,7 @@ from aqt.main import ResetReason from aqt.qt import * from aqt.sound import av_player from aqt.utils import ( + TR, addCloseShortcut, askUser, downArrow, @@ -25,6 +26,7 @@ from aqt.utils import ( shortcut, showWarning, tooltip, + tr, ) @@ -35,7 +37,7 @@ class AddCards(QDialog): self.mw = mw self.form = aqt.forms.addcards.Ui_Dialog() self.form.setupUi(self) - self.setWindowTitle(_("Add")) + self.setWindowTitle(tr(TR.ACTIONS_ADD)) self.setMinimumHeight(300) self.setMinimumWidth(400) self.setupChoosers() @@ -67,26 +69,26 @@ class AddCards(QDialog): bb = self.form.buttonBox ar = QDialogButtonBox.ActionRole # add - self.addButton = bb.addButton(_("Add"), ar) + self.addButton = bb.addButton(tr(TR.ACTIONS_ADD), ar) qconnect(self.addButton.clicked, self.addCards) self.addButton.setShortcut(QKeySequence("Ctrl+Return")) - self.addButton.setToolTip(shortcut(_("Add (shortcut: ctrl+enter)"))) + self.addButton.setToolTip(shortcut(tr(TR.ADDING_ADD_SHORTCUT_CTRLANDENTER))) # close - self.closeButton = QPushButton(_("Close")) + self.closeButton = QPushButton(tr(TR.ACTIONS_CLOSE)) self.closeButton.setAutoDefault(False) bb.addButton(self.closeButton, QDialogButtonBox.RejectRole) # help - self.helpButton = QPushButton(_("Help"), clicked=self.helpRequested) # type: ignore + self.helpButton = QPushButton(tr(TR.ACTIONS_HELP), clicked=self.helpRequested) # type: ignore self.helpButton.setAutoDefault(False) bb.addButton(self.helpButton, QDialogButtonBox.HelpRole) # history - b = bb.addButton(_("History") + " " + downArrow(), ar) + b = bb.addButton(tr(TR.ADDING_HISTORY) + " " + downArrow(), ar) if isMac: sc = "Ctrl+Shift+H" else: sc = "Ctrl+H" b.setShortcut(QKeySequence(sc)) - b.setToolTip(_("Shortcut: %s") % shortcut(sc)) + b.setToolTip(tr(TR.ADDING_SHORTCUT, val="%s") % shortcut(sc)) qconnect(b.clicked, self.onHistory) b.setEnabled(False) self.historyButton = b @@ -151,7 +153,7 @@ class AddCards(QDialog): a = m.addAction(line) qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid)) else: - a = m.addAction(_("(Note deleted)")) + a = m.addAction(tr(TR.ADDING_NOTE_DELETED)) a.setEnabled(False) gui_hooks.add_cards_will_show_history_menu(self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0))) @@ -166,7 +168,7 @@ class AddCards(QDialog): ret = note.dupeOrEmpty() problem = None if ret == 1: - problem = _("The first field is empty.") + problem = tr(TR.ADDING_THE_FIRST_FIELD_IS_EMPTY) problem = gui_hooks.add_cards_will_add_note(problem, note) if problem is not None: showWarning(problem, help="AddItems#AddError") @@ -199,7 +201,7 @@ class AddCards(QDialog): # workaround for PyQt focus bug self.editor.hideCompleters() - tooltip(_("Added"), period=500) + tooltip(tr(TR.ADDING_ADDED), period=500) av_player.stop_and_clear_queue() self.onReset(keep=True) self.mw.col.autosave() @@ -229,7 +231,7 @@ class AddCards(QDialog): def ifCanClose(self, onOk: Callable) -> None: def afterSave(): ok = self.editor.fieldsAreBlank(self.previousNote) or askUser( - _("Close and lose current input?"), defaultno=True + tr(TR.ADDING_CLOSE_AND_LOSE_CURRENT_INPUT), defaultno=True ) if ok: onOk() diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index c36b1d8f4..e6c8adae9 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations import io @@ -310,7 +309,7 @@ and have been disabled: %(found)s" meta = self.addon_meta(dir) name = meta.human_name() if not meta.enabled: - name += _(" (disabled)") + name += tr(TR.ADDONS_DISABLED) return name # Conflict resolution @@ -423,10 +422,7 @@ and have been disabled: %(found)s" return True except OSError as e: showWarning( - _( - "Unable to update or delete add-on. Please start Anki while holding down the shift key to temporarily disable add-ons, then try again.\n\nDebug info: %s" - ) - % e, + tr(TR.ADDONS_UNABLE_TO_UPDATE_OR_DELETE_ADDON, val="%s") % e, textFormat="plain", ) return False @@ -468,16 +464,16 @@ and have been disabled: %(found)s" ) -> List[str]: messages = { - "zip": _("Corrupt add-on file."), - "manifest": _("Invalid add-on manifest."), + "zip": tr(TR.ADDONS_CORRUPT_ADDON_FILE), + "manifest": tr(TR.ADDONS_INVALID_ADDON_MANIFEST), } msg = messages.get(result.errmsg, _("Unknown error: {}".format(result.errmsg))) if mode == "download": # preserve old format strings for i18n - template = _("Error downloading %(id)s: %(error)s") + template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS) else: - template = _("Error installing %(base)s: %(error)s") + template = tr(TR.ADDONS_ERROR_INSTALLING_BASES_ERRORS) name = base @@ -488,24 +484,22 @@ and have been disabled: %(found)s" ) -> List[str]: if mode == "download": # preserve old format strings for i18n - template = _("Downloaded %(fname)s") + template = tr(TR.ADDONS_DOWNLOADED_FNAMES) else: - template = _("Installed %(name)s") + template = tr(TR.ADDONS_INSTALLED_NAMES) name = result.name or base strings = [template % dict(name=name, fname=name)] if result.conflicts: strings.append( - _("The following conflicting add-ons were disabled:") + tr(TR.ADDONS_THE_FOLLOWING_CONFLICTING_ADDONS_WERE_DISABLED) + " " + ", ".join(self.addonName(f) for f in result.conflicts) ) if not result.compatible: - strings.append( - _("This add-on is not compatible with your version of Anki.") - ) + strings.append(tr(TR.ADDONS_THIS_ADDON_IS_NOT_COMPATIBLE_WITH)) return strings @@ -743,9 +737,13 @@ class AddonsDialog(QDialog): name = addon.human_name() if not addon.enabled: - return name + " " + _("(disabled)") + return name + " " + tr(TR.ADDONS_DISABLED2) elif not addon.compatible(): - return name + " " + _("(requires %s)") % self.compatible_string(addon) + return ( + name + + " " + + tr(TR.ADDONS_REQUIRES, val="%s") % self.compatible_string(addon) + ) return name @@ -804,7 +802,7 @@ class AddonsDialog(QDialog): def onlyOneSelected(self) -> Optional[str]: dirs = self.selectedAddons() if len(dirs) != 1: - showInfo(_("Please select a single add-on first.")) + showInfo(tr(TR.ADDONS_PLEASE_SELECT_A_SINGLE_ADDON_FIRST)) return None return dirs[0] @@ -820,7 +818,7 @@ class AddonsDialog(QDialog): if re.match(r"^\d+$", addon): openLink(aqt.appShared + "info/{}".format(addon)) else: - showWarning(_("Add-on was not downloaded from AnkiWeb.")) + showWarning(tr(TR.ADDONS_ADDON_WAS_NOT_DOWNLOADED_FROM_ANKIWEB)) def onViewFiles(self) -> None: # if nothing selected, open top level folder @@ -865,13 +863,13 @@ class AddonsDialog(QDialog): if log: show_log_to_user(self, log) else: - tooltip(_("No updates available.")) + tooltip(tr(TR.ADDONS_NO_UPDATES_AVAILABLE)) def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]: if not paths: - key = _("Packaged Anki Add-on") + " (*{})".format(self.mgr.ext) + key = tr(TR.ADDONS_PACKAGED_ANKI_ADDON) + " (*{})".format(self.mgr.ext) paths = getFile( - self, _("Install Add-on(s)"), None, key, key="addons", multi=True + self, tr(TR.ADDONS_INSTALL_ADDONS), None, key, key="addons", multi=True ) if not paths: return False @@ -882,7 +880,7 @@ class AddonsDialog(QDialog): return None def check_for_updates(self) -> None: - tooltip(_("Checking...")) + tooltip(tr(TR.ADDONS_CHECKING)) check_and_prompt_for_updates(self, self.mgr, self.after_downloading) def onConfig(self) -> None: @@ -899,7 +897,7 @@ class AddonsDialog(QDialog): conf = self.mgr.getConfig(addon) if conf is None: - showInfo(_("Add-on has no configuration.")) + showInfo(tr(TR.ADDONS_ADDON_HAS_NO_CONFIGURATION)) return ConfigEditor(self, addon, conf) @@ -919,7 +917,7 @@ class GetAddons(QDialog): self.form = aqt.forms.getaddons.Ui_Dialog() self.form.setupUi(self) b = self.form.buttonBox.addButton( - _("Browse Add-ons"), QDialogButtonBox.ActionRole + tr(TR.ADDONS_BROWSE_ADDONS), QDialogButtonBox.ActionRole ) qconnect(b.clicked, self.onBrowse) restoreGeom(self, "getaddons", adjustSize=True) @@ -934,7 +932,7 @@ class GetAddons(QDialog): try: ids = [int(n) for n in self.form.code.text().split()] except ValueError: - showWarning(_("Invalid code.")) + showWarning(tr(TR.ADDONS_INVALID_CODE)) return self.ids = ids @@ -1007,21 +1005,22 @@ def describe_log_entry(id_and_entry: DownloadLogEntry) -> str: if isinstance(entry, DownloadError): if entry.status_code is not None: if entry.status_code in (403, 404): - buf += _( - "Invalid code, or add-on not available for your version of Anki." - ) + buf += tr(TR.ADDONS_INVALID_CODE_OR_ADDON_NOT_AVAILABLE) else: - buf += _("Unexpected response code: %s") % entry.status_code + buf += ( + tr(TR.QT_MISC_UNEXPECTED_RESPONSE_CODE, val="%s") + % entry.status_code + ) else: buf += ( - _("Please check your internet connection.") + tr(TR.ADDONS_PLEASE_CHECK_YOUR_INTERNET_CONNECTION) + "\n\n" + str(entry.exception) ) elif isinstance(entry, InstallError): buf += entry.errmsg else: - buf += _("Installed successfully.") + buf += tr(TR.ADDONS_INSTALLED_SUCCESSFULLY) return buf @@ -1091,7 +1090,7 @@ class DownloaderInstaller(QObject): # and "%(kb)0.2f" is the number of downloaded # kilobytes. This lead for example to "Downloading 3/5 # (27KB)" - label=_("Downloading %(a)d/%(b)d (%(kb)0.2fKB)...") + label=tr(TR.ADDONS_DOWNLOADING_ADBD_KB02FKB) % dict(a=len(self.log) + 1, b=len(self.ids), kb=self.dl_bytes / 1024) ) @@ -1110,9 +1109,9 @@ def show_log_to_user(parent: QWidget, log: List[DownloadLogEntry]) -> None: have_problem = download_encountered_problem(log) if have_problem: - text = _("One or more errors occurred:") + text = tr(TR.ADDONS_ONE_OR_MORE_ERRORS_OCCURRED) else: - text = _("Download complete. Please restart Anki to apply changes.") + text = tr(TR.ADDONS_DOWNLOAD_COMPLETE_PLEASE_RESTART_ANKI_TO) text += "

" + download_log_to_html(log) if have_problem: @@ -1265,7 +1264,7 @@ def prompt_to_update( ) -> None: names = map(lambda x: mgr.addonName(str(x)), ids) if not askUser( - _("The following add-ons have updates available. Install them now?") + tr(TR.ADDONS_THE_FOLLOWING_ADDONS_HAVE_UPDATES_AVAILABLE) + "\n\n" + "\n".join(names) ): @@ -1307,7 +1306,7 @@ class ConfigEditor(QDialog): def onRestoreDefaults(self) -> None: default_conf = self.mgr.addonConfigDefaults(self.addon) self.updateText(default_conf) - tooltip(_("Restored defaults"), parent=self) + tooltip(tr(TR.ADDONS_RESTORED_DEFAULTS), parent=self) def setupFonts(self) -> None: font_mono = QFontDatabase.systemFont(QFontDatabase.FixedFont) @@ -1373,11 +1372,11 @@ class ConfigEditor(QDialog): showInfo(msg) return except Exception as e: - showInfo(_("Invalid configuration: ") + repr(e)) + showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + repr(e)) return if not isinstance(new_conf, dict): - showInfo(_("Invalid configuration: top level object must be a map")) + showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION_TOP_LEVEL_OBJECT_MUST)) return if new_conf != self.conf: @@ -1417,7 +1416,7 @@ def installAddonPackages( not showInfo( q, parent=parent, - title=_("Install Anki add-on"), + title=tr(TR.ADDONS_INSTALL_ANKI_ADDON), type="warning", customBtns=[QMessageBox.No, QMessageBox.Yes], ) @@ -1430,9 +1429,7 @@ def installAddonPackages( if log: log_html = "
".join(log) if advise_restart: - log_html += "

" + _( - "Please restart Anki to complete the installation." - ) + log_html += "

" + tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE) if len(log) == 1 and not strictly_modal: tooltip(log_html, parent=parent) else: @@ -1440,15 +1437,15 @@ def installAddonPackages( log_html, parent=parent, textFormat="rich", - title=_("Installation complete"), + title=tr(TR.ADDONS_INSTALLATION_COMPLETE), ) if errs: - msg = _("Please report this to the respective add-on author(s).") + msg = tr(TR.ADDONS_PLEASE_REPORT_THIS_TO_THE_RESPECTIVE) showWarning( "

".join(errs + [msg]), parent=parent, textFormat="rich", - title=_("Add-on installation error"), + title=tr(TR.ADDONS_ADDON_INSTALLATION_ERROR), ) return not errs diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 5bdfc5e39..8f328e4a6 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations import html @@ -34,6 +33,7 @@ from aqt.qt import * from aqt.sidebar import NewSidebarTreeView, SidebarItemType, SidebarTreeViewBase from aqt.theme import theme_manager from aqt.utils import ( + TR, MenuList, SubMenu, askUser, @@ -169,7 +169,7 @@ class DataModel(QAbstractTableModel): break # give the user a hint an invalid column was added by an add-on if not txt: - txt = _("Add-on") + txt = tr(TR.BROWSING_ADDON) return txt else: return @@ -331,13 +331,13 @@ class DataModel(QAbstractTableModel): return c.model()["name"] elif type == "cardIvl": if c.type == CARD_TYPE_NEW: - return _("(new)") + return tr(TR.BROWSING_NEW) elif c.type == CARD_TYPE_LRN: - return _("(learning)") + return tr(TR.BROWSING_LEARNING) return self.col.format_timespan(c.ivl * 86400) elif type == "cardEase": if c.type == CARD_TYPE_NEW: - return _("(new)") + return tr(TR.BROWSING_NEW) return "%d%%" % (c.factor / 10) elif type == "deck": if c.odid: @@ -366,7 +366,7 @@ class DataModel(QAbstractTableModel): def nextDue(self, c, index): if c.odid: - return _("(filtered)") + return tr(TR.BROWSING_FILTERED) elif c.queue == QUEUE_TYPE_LRN: date = c.due elif c.queue == QUEUE_TYPE_NEW or c.type == CARD_TYPE_NEW: @@ -621,7 +621,7 @@ class Browser(QMainWindow): f = self.form qconnect(f.previewButton.clicked, self.onTogglePreview) f.previewButton.setToolTip( - _("Preview Selected Card (%s)") % shortcut("Ctrl+Shift+P") + tr(TR.BROWSING_PREVIEW_SELECTED_CARD, val="%s") % shortcut("Ctrl+Shift+P") ) f.previewButton.setShortcut("Ctrl+Shift+P") @@ -737,21 +737,21 @@ class Browser(QMainWindow): def setupColumns(self): self.columns = [ - ("question", _("Question")), - ("answer", _("Answer")), - ("template", _("Card")), - ("deck", _("Deck")), - ("noteFld", _("Sort Field")), - ("noteCrt", _("Created")), + ("question", tr(TR.BROWSING_QUESTION)), + ("answer", tr(TR.BROWSING_ANSWER)), + ("template", tr(TR.BROWSING_CARD)), + ("deck", tr(TR.DECKS_DECK)), + ("noteFld", tr(TR.BROWSING_SORT_FIELD)), + ("noteCrt", tr(TR.BROWSING_CREATED)), ("noteMod", tr(TR.SEARCH_NOTE_MODIFIED)), ("cardMod", tr(TR.SEARCH_CARD_MODIFIED)), ("cardDue", tr(TR.STATISTICS_DUE_DATE)), - ("cardIvl", _("Interval")), - ("cardEase", _("Ease")), - ("cardReps", _("Reviews")), - ("cardLapses", _("Lapses")), - ("noteTags", _("Tags")), - ("note", _("Note")), + ("cardIvl", tr(TR.BROWSING_INTERVAL)), + ("cardEase", tr(TR.BROWSING_EASE)), + ("cardReps", tr(TR.SCHEDULING_REVIEWS)), + ("cardLapses", tr(TR.SCHEDULING_LAPSES)), + ("noteTags", tr(TR.EDITING_TAGS)), + ("note", tr(TR.BROWSING_NOTE)), ] self.columns.sort(key=itemgetter(1)) @@ -762,7 +762,7 @@ class Browser(QMainWindow): qconnect(self.form.searchButton.clicked, self.onSearchActivated) qconnect(self.form.searchEdit.lineEdit().returnPressed, self.onSearchActivated) self.form.searchEdit.setCompleter(None) - self._searchPrompt = _("") + self._searchPrompt = tr(TR.BROWSING_TYPE_HERE_TO_SEARCH) self.form.searchEdit.addItems( [self._searchPrompt] + self.mw.pm.profile["searchHistory"] ) @@ -938,9 +938,7 @@ QTableView {{ gridline-color: {grid} }} type = self.model.activeCols[idx] noSort = ("question", "answer") if type in noSort: - showInfo( - _("Sorting on this column is not supported. Please " "choose another.") - ) + showInfo(tr(TR.BROWSING_SORTING_ON_THIS_COLUMN_IS_NOT)) type = self.col.conf["sortType"] if self.col.conf["sortType"] != type: self.col.conf["sortType"] = type @@ -994,7 +992,7 @@ QTableView {{ gridline-color: {grid} }} if type in self.model.activeCols: if len(self.model.activeCols) < 2: self.model.endReset() - return showInfo(_("You must have at least one column.")) + return showInfo(tr(TR.BROWSING_YOU_MUST_HAVE_AT_LEAST_ONE)) self.model.activeCols.remove(type) adding = False else: @@ -1028,7 +1026,7 @@ QTableView {{ gridline-color: {grid} }} pass def setupSidebar(self) -> None: - dw = self.sidebarDockWidget = QDockWidget(_("Sidebar"), self) + dw = self.sidebarDockWidget = QDockWidget(tr(TR.BROWSING_SIDEBAR), self) dw.setFeatures(QDockWidget.DockWidgetClosable) dw.setObjectName("Sidebar") dw.setAllowedAreas(Qt.LeftDockWidgetArea) @@ -1105,14 +1103,14 @@ QTableView {{ gridline-color: {grid} }} def _stdTree(self, root) -> None: item = SidebarItem( - _("Whole Collection"), + tr(TR.BROWSING_WHOLE_COLLECTION), ":/icons/collection.svg", self._filterFunc(""), item_type=SidebarItemType.COLLECTION, ) root.addChild(item) item = SidebarItem( - _("Current Deck"), + tr(TR.BROWSING_CURRENT_DECK), ":/icons/deck.svg", self._filterFunc("deck:current"), item_type=SidebarItemType.CURRENT_DECK, @@ -1252,41 +1250,44 @@ QTableView {{ gridline-color: {grid} }} def _commonFilters(self): return self._simpleFilters( - ((_("Whole Collection"), ""), (_("Current Deck"), "deck:current")) + ( + (tr(TR.BROWSING_WHOLE_COLLECTION), ""), + (tr(TR.BROWSING_CURRENT_DECK), "deck:current"), + ) ) def _todayFilters(self): - subm = SubMenu(_("Today")) + subm = SubMenu(tr(TR.BROWSING_TODAY)) subm.addChild( self._simpleFilters( ( - (_("Added Today"), "added:1"), - (_("Studied Today"), "rated:1"), - (_("Again Today"), "rated:1:1"), + (tr(TR.BROWSING_ADDED_TODAY), "added:1"), + (tr(TR.BROWSING_STUDIED_TODAY), "rated:1"), + (tr(TR.BROWSING_AGAIN_TODAY), "rated:1:1"), ) ) ) return subm def _cardStateFilters(self): - subm = SubMenu(_("Card State")) + subm = SubMenu(tr(TR.BROWSING_CARD_STATE)) subm.addChild( self._simpleFilters( ( - (_("New"), "is:new"), - (_("Learning"), "is:learn"), - (_("Review"), "is:review"), + (tr(TR.ACTIONS_NEW), "is:new"), + (tr(TR.SCHEDULING_LEARNING), "is:learn"), + (tr(TR.SCHEDULING_REVIEW), "is:review"), (tr(TR.FILTERING_IS_DUE), "is:due"), None, - (_("Suspended"), "is:suspended"), - (_("Buried"), "is:buried"), + (tr(TR.BROWSING_SUSPENDED), "is:suspended"), + (tr(TR.BROWSING_BURIED), "is:buried"), None, - (_("Red Flag"), "flag:1"), - (_("Orange Flag"), "flag:2"), - (_("Green Flag"), "flag:3"), - (_("Blue Flag"), "flag:4"), - (_("No Flag"), "flag:0"), - (_("Any Flag"), "-flag:0"), + (tr(TR.ACTIONS_RED_FLAG), "flag:1"), + (tr(TR.ACTIONS_ORANGE_FLAG), "flag:2"), + (tr(TR.ACTIONS_GREEN_FLAG), "flag:3"), + (tr(TR.ACTIONS_BLUE_FLAG), "flag:4"), + (tr(TR.BROWSING_NO_FLAG), "flag:0"), + (tr(TR.BROWSING_ANY_FLAG), "-flag:0"), ) ) ) @@ -1296,9 +1297,9 @@ QTableView {{ gridline-color: {grid} }} return label.replace("&", "&&") def _tagFilters(self): - m = SubMenu(_("Tags")) + m = SubMenu(tr(TR.EDITING_TAGS)) - m.addItem(_("Clear Unused"), self.clearUnusedTags) + m.addItem(tr(TR.BROWSING_CLEAR_UNUSED), self.clearUnusedTags) m.addSeparator() tagList = MenuList() @@ -1316,7 +1317,9 @@ QTableView {{ gridline-color: {grid} }} fullname = parent_prefix + node.name if node.children: subm = parent.addMenu(escaped_name) - subm.addItem(_("Filter"), self._filterFunc("deck", fullname)) + subm.addItem( + tr(TR.ACTIONS_FILTER), self._filterFunc("deck", fullname) + ) subm.addSeparator() addDecks(subm, node.children, fullname + "::") else: @@ -1326,15 +1329,15 @@ QTableView {{ gridline-color: {grid} }} ml = MenuList() addDecks(ml, alldecks.children, "") - root = SubMenu(_("Decks")) + root = SubMenu(tr(TR.ACTIONS_DECKS)) root.addChild(ml.chunked()) return root def _noteTypeFilters(self): - m = SubMenu(_("Note Types")) + m = SubMenu(tr(TR.NOTETYPES_NOTE_TYPES)) - m.addItem(_("Manage..."), self.mw.onNoteTypes) + m.addItem(tr(TR.ACTIONS_MANAGE), self.mw.onNoteTypes) m.addSeparator() noteTypes = MenuList() @@ -1346,14 +1349,16 @@ QTableView {{ gridline-color: {grid} }} else: subm = noteTypes.addMenu(escaped_nt_name) - subm.addItem(_("All Card Types"), self._filterFunc("note", nt["name"])) + subm.addItem( + tr(TR.BROWSING_ALL_CARD_TYPES), self._filterFunc("note", nt["name"]) + ) subm.addSeparator() # add templates for c, tmpl in enumerate(nt["tmpls"]): # T: name is a card type name. n it's order in the list of card type. # T: this is shown in browser's filter, when seeing the list of card type of a note type. - name = _("%(n)d: %(name)s") % dict( + name = tr(TR.BROWSING_ND_NAMES) % dict( n=c + 1, name=self._escapeMenuItem(tmpl["name"]) ) subm.addItem( @@ -1375,9 +1380,9 @@ QTableView {{ gridline-color: {grid} }} ml.addSeparator() if self._currentFilterIsSaved(): - ml.addItem(_("Remove Current Filter..."), self._onRemoveFilter) + ml.addItem(tr(TR.BROWSING_REMOVE_CURRENT_FILTER), self._onRemoveFilter) else: - ml.addItem(_("Save Current Filter..."), self._onSaveFilter) + ml.addItem(tr(TR.BROWSING_SAVE_CURRENT_FILTER), self._onSaveFilter) saved = self.col.get_config("savedFilters") if not saved: @@ -1390,7 +1395,7 @@ QTableView {{ gridline-color: {grid} }} return ml def _onSaveFilter(self) -> None: - name = getOnlyText(_("Please give your filter a name:")) + name = getOnlyText(tr(TR.BROWSING_PLEASE_GIVE_YOUR_FILTER_A_NAME)) if not name: return filt = self.form.searchEdit.lineEdit().text() @@ -1401,7 +1406,9 @@ QTableView {{ gridline-color: {grid} }} def _onRemoveFilter(self): name = self._currentFilterIsSaved() - if not askUser(_("Remove %s from your saved searches?") % name): + if not askUser( + tr(TR.BROWSING_REMOVE_FROM_YOUR_SAVED_SEARCHES, val="%s") % name + ): return del self.col.conf["savedFilters"][name] self.col.setMod() @@ -1490,7 +1497,7 @@ where id in %s""" % ids2str(sf) ) if mods > 1: - showInfo(_("Please select cards from only one note type.")) + showInfo(tr(TR.BROWSING_PLEASE_SELECT_CARDS_FROM_ONLY_ONE)) return return sf @@ -1544,7 +1551,7 @@ where id in %s""" nids = self.selectedNotes() if not nids: return - self.mw.checkpoint(_("Delete Notes")) + self.mw.checkpoint(tr(TR.BROWSING_DELETE_NOTES)) self.model.beginReset() # figure out where to place the cursor after the deletion curRow = self.form.tableView.selectionModel().currentIndex().row() @@ -1590,8 +1597,8 @@ where id in %s""" ret = StudyDeck( self.mw, current=current, - accept=_("Move Cards"), - title=_("Change Deck"), + accept=tr(TR.BROWSING_MOVE_CARDS), + title=tr(TR.BROWSING_CHANGE_DECK), help="browse", parent=self, ) @@ -1600,10 +1607,10 @@ where id in %s""" did = self.col.decks.id(ret.name) deck = self.col.decks.get(did) if deck["dyn"]: - showWarning(_("Cards can't be manually moved into a filtered deck.")) + showWarning(tr(TR.BROWSING_CARDS_CANT_BE_MANUALLY_MOVED_INTO)) return self.model.beginReset() - self.mw.checkpoint(_("Change Deck")) + self.mw.checkpoint(tr(TR.BROWSING_CHANGE_DECK)) self.col.set_deck(cids, did) self.model.endReset() self.mw.requireReset(reason=ResetReason.BrowserSetDeck, context=self) @@ -1616,7 +1623,7 @@ where id in %s""" def _addTags(self, tags, label, prompt, func): if prompt is None: - prompt = _("Enter tags to add:") + prompt = tr(TR.BROWSING_ENTER_TAGS_TO_ADD) if tags is None: (tags, r) = getTag(self, self.col, prompt) else: @@ -1626,7 +1633,7 @@ where id in %s""" if func is None: func = self.col.tags.bulkAdd if label is None: - label = _("Add Tags") + label = tr(TR.BROWSING_ADD_TAGS) if label: self.mw.checkpoint(label) self.model.beginReset() @@ -1636,9 +1643,12 @@ where id in %s""" def deleteTags(self, tags=None, label=None): if label is None: - label = _("Delete Tags") + label = tr(TR.BROWSING_DELETE_TAGS) self.addTags( - tags, label, _("Enter tags to delete:"), func=self.col.tags.bulkRem + tags, + label, + tr(TR.BROWSING_ENTER_TAGS_TO_DELETE), + func=self.col.tags.bulkRem, ) def clearUnusedTags(self): @@ -1731,7 +1741,7 @@ where id in %s""" + ids2str(cids) ) if not cids2: - return showInfo(_("Only new cards can be repositioned.")) + return showInfo(tr(TR.BROWSING_ONLY_NEW_CARDS_CAN_BE_REPOSITIONED)) d = QDialog(self) d.setWindowModality(Qt.WindowModal) frm = aqt.forms.reposition.Ui_Dialog() @@ -1741,14 +1751,14 @@ where id in %s""" ) pmin = pmin or 0 pmax = pmax or 0 - txt = _("Queue top: %d") % pmin - txt += "\n" + _("Queue bottom: %d") % pmax + txt = tr(TR.BROWSING_QUEUE_TOP, val="%s") % pmin + txt += "\n" + tr(TR.BROWSING_QUEUE_BOTTOM, val="%s") % pmax frm.label.setText(txt) frm.start.selectAll() if not d.exec_(): return self.model.beginReset() - self.mw.checkpoint(_("Reposition")) + self.mw.checkpoint(tr(TR.ACTIONS_REPOSITION)) self.col.sched.sortCards( cids, start=frm.start.value(), @@ -1774,7 +1784,7 @@ where id in %s""" if not d.exec_(): return self.model.beginReset() - self.mw.checkpoint(_("Reschedule")) + self.mw.checkpoint(tr(TR.BROWSING_RESCHEDULE)) if frm.asNew.isChecked(): self.col.sched.forgetCards(self.selectedCards()) else: @@ -1881,7 +1891,7 @@ where id in %s""" restore_is_checked(frm.ignoreCase, combo + "ignoreCase") frm.find.setFocus() - allfields = [_("All Fields")] + fields + allfields = [tr(TR.BROWSING_ALL_FIELDS)] + fields frm.field.addItems(allfields) restore_combo_index_for_session(frm.field, allfields, combo + "Field") qconnect(frm.buttonBox.helpRequested, self.onFindReplaceHelp) @@ -1906,7 +1916,7 @@ where id in %s""" save_is_checked(frm.re, combo + "Regex") save_is_checked(frm.ignoreCase, combo + "ignoreCase") - self.mw.checkpoint(_("Find and Replace")) + self.mw.checkpoint(tr(TR.BROWSING_FIND_AND_REPLACE)) # starts progress dialog as well self.model.beginReset() @@ -1976,7 +1986,9 @@ where id in %s""" field = fields[frm.fields.currentIndex()] self.duplicatesReport(frm.webView, field, search_text, frm, web_context) - search = frm.buttonBox.addButton(_("Search"), QDialogButtonBox.ActionRole) + search = frm.buttonBox.addButton( + tr(TR.ACTIONS_SEARCH), QDialogButtonBox.ActionRole + ) qconnect(search.clicked, onClick) d.show() @@ -1985,7 +1997,7 @@ where id in %s""" res = self.mw.col.findDupes(fname, search) if not self._dupesButton: self._dupesButton = b = frm.buttonBox.addButton( - _("Tag Duplicates"), QDialogButtonBox.ActionRole + tr(TR.BROWSING_TAG_DUPLICATES), QDialogButtonBox.ActionRole ) qconnect(b.clicked, lambda: self._onTagDupes(res)) t = "" @@ -1993,7 +2005,7 @@ where id in %s""" notes = sum(len(r[1]) for r in res) part1 = ngettext("%d group", "%d groups", groups) % groups part2 = ngettext("%d note", "%d notes", notes) % notes - t += _("Found %(a)s across %(b)s.") % dict(a=part1, b=part2) + t += tr(TR.BROWSING_FOUND_AS_ACROSS_BS) % dict(a=part1, b=part2) t += "

    " for val, nids in res: t += ( @@ -2012,15 +2024,15 @@ where id in %s""" if not res: return self.model.beginReset() - self.mw.checkpoint(_("Tag Duplicates")) + self.mw.checkpoint(tr(TR.BROWSING_TAG_DUPLICATES)) nids = set() for s, nidlist in res: nids.update(nidlist) - self.col.tags.bulkAdd(list(nids), _("duplicate")) + self.col.tags.bulkAdd(list(nids), tr(TR.BROWSING_DUPLICATE)) self.mw.progress.finish() self.model.endReset() self.mw.requireReset(reason=ResetReason.BrowserTagDupes, context=self) - tooltip(_("Notes tagged.")) + tooltip(tr(TR.BROWSING_NOTES_TAGGED)) def dupeLinkClicked(self, link): self.search_for(link) @@ -2177,10 +2189,10 @@ class ChangeModel(QDialog): map = QWidget() l = QGridLayout() combos = [] - targets = [x["name"] for x in dst] + [_("Nothing")] + targets = [x["name"] for x in dst] + [tr(TR.BROWSING_NOTHING)] indices = {} for i, x in enumerate(src): - l.addWidget(QLabel(_("Change %s to:") % x["name"]), i, 0) + l.addWidget(QLabel(tr(TR.BROWSING_CHANGE_TO, val="%s") % x["name"]), i, 0) cb = QComboBox() cb.addItems(targets) idx = min(i, len(targets) - 1) @@ -2267,7 +2279,7 @@ Are you sure you want to continue?""" ) ): return - self.browser.mw.checkpoint(_("Change Note Type")) + self.browser.mw.checkpoint(tr(TR.BROWSING_CHANGE_NOTE_TYPE)) b = self.browser b.mw.col.modSchema(check=True) b.mw.progress.start() diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 1281face9..8594f3300 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -60,13 +60,15 @@ class CardLayout(QDialog): self.mobile_emulation_enabled = False self.have_autoplayed = False self.mm._remove_from_cache(self.model["id"]) - self.mw.checkpoint(_("Card Types")) + self.mw.checkpoint(tr(TR.CARD_TEMPLATES_CARD_TYPES)) self.change_tracker = ChangeTracker(self.mw) self.setupTopArea() self.setupMainArea() self.setupButtons() self.setupShortcuts() - self.setWindowTitle(_("Card Types for %s") % self.model["name"]) + self.setWindowTitle( + tr(TR.CARD_TEMPLATES_CARD_TYPES_FOR, val="%s") % self.model["name"] + ) v1 = QVBoxLayout() v1.addWidget(self.topArea) v1.addWidget(self.mainArea) @@ -108,7 +110,9 @@ class CardLayout(QDialog): self.topArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.topAreaForm = aqt.forms.clayout_top.Ui_Form() self.topAreaForm.setupUi(self.topArea) - self.topAreaForm.templateOptions.setText(_("Options") + " " + downArrow()) + self.topAreaForm.templateOptions.setText( + tr(TR.ACTIONS_OPTIONS) + " " + downArrow() + ) qconnect(self.topAreaForm.templateOptions.clicked, self.onMore) qconnect( self.topAreaForm.templatesBox.currentIndexChanged, @@ -240,7 +244,7 @@ class CardLayout(QDialog): qconnect(widg.returnPressed, self.on_search_next) def setup_cloze_number_box(self): - names = (_("Cloze %d") % n for n in self.cloze_numbers) + names = (tr(TR.CARD_TEMPLATES_CLOZE, val="%s") % n for n in self.cloze_numbers) self.pform.cloze_number_combo.addItems(names) try: idx = self.cloze_numbers.index(self.ord + 1) @@ -381,28 +385,28 @@ class CardLayout(QDialog): def setupButtons(self): l = self.buttons = QHBoxLayout() - help = QPushButton(_("Help")) + help = QPushButton(tr(TR.ACTIONS_HELP)) help.setAutoDefault(False) l.addWidget(help) qconnect(help.clicked, self.onHelp) l.addStretch() - self.add_field_button = QPushButton(_("Add Field")) + self.add_field_button = QPushButton(tr(TR.FIELDS_ADD_FIELD)) self.add_field_button.setAutoDefault(False) l.addWidget(self.add_field_button) qconnect(self.add_field_button.clicked, self.onAddField) if not self._isCloze(): - flip = QPushButton(_("Flip")) + flip = QPushButton(tr(TR.CARD_TEMPLATES_FLIP)) flip.setAutoDefault(False) l.addWidget(flip) qconnect(flip.clicked, self.onFlip) l.addStretch() - save = QPushButton(_("Save")) + save = QPushButton(tr(TR.ACTIONS_SAVE)) save.setAutoDefault(False) save.setShortcut(QKeySequence("Ctrl+Return")) l.addWidget(save) qconnect(save.clicked, self.accept) - close = QPushButton(_("Cancel")) + close = QPushButton(tr(TR.ACTIONS_CANCEL)) close.setAutoDefault(False) l.addWidget(close) qconnect(close.clicked, self.reject) @@ -548,7 +552,7 @@ class CardLayout(QDialog): def onRemove(self): if len(self.templates) < 2: - return showInfo(_("At least one card type is required.")) + return showInfo(tr(TR.CARD_TEMPLATES_AT_LEAST_ONE_CARD_TYPE_IS)) def get_count(): return self.mm.template_use_count(self.model["id"], self.ord) @@ -558,7 +562,7 @@ class CardLayout(QDialog): template = self.current_template() cards = ngettext("%d card", "%d cards", card_cnt) % card_cnt - msg = _("Delete the '%(a)s' card type, and its %(b)s?") % dict( + msg = tr(TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND) % dict( a=template["name"], b=cards ) if not askUser(msg): @@ -583,7 +587,9 @@ class CardLayout(QDialog): def onRename(self): template = self.current_template() - name = getOnlyText(_("New name:"), default=template["name"]).replace('"', "") + name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=template["name"]).replace( + '"', "" + ) if not name.strip(): return @@ -597,7 +603,8 @@ class CardLayout(QDialog): template = self.current_template() current_pos = self.templates.index(template) + 1 pos = getOnlyText( - _("Enter new card position (1...%s):") % n, default=str(current_pos) + tr(TR.CARD_TEMPLATES_ENTER_NEW_CARD_POSITION_1, val="%s") % n, + default=str(current_pos), ) if not pos: return @@ -619,7 +626,7 @@ class CardLayout(QDialog): def _newCardName(self): n = len(self.templates) + 1 while 1: - name = _("Card %d") % n + name = tr(TR.CARD_TEMPLATES_CARD, val="%s") % n if name not in [t["name"] for t in self.templates]: break n += 1 @@ -673,29 +680,29 @@ adjust the template manually to switch the question and answer.""" m = QMenu(self) if not self._isCloze(): - a = m.addAction(_("Add Card Type...")) + a = m.addAction(tr(TR.CARD_TEMPLATES_ADD_CARD_TYPE)) qconnect(a.triggered, self.onAddCard) - a = m.addAction(_("Remove Card Type...")) + a = m.addAction(tr(TR.CARD_TEMPLATES_REMOVE_CARD_TYPE)) qconnect(a.triggered, self.onRemove) - a = m.addAction(_("Rename Card Type...")) + a = m.addAction(tr(TR.CARD_TEMPLATES_RENAME_CARD_TYPE)) qconnect(a.triggered, self.onRename) - a = m.addAction(_("Reposition Card Type...")) + a = m.addAction(tr(TR.CARD_TEMPLATES_REPOSITION_CARD_TYPE)) qconnect(a.triggered, self.onReorder) m.addSeparator() t = self.current_template() if t["did"]: - s = _(" (on)") + s = tr(TR.CARD_TEMPLATES_ON) else: - s = _(" (off)") - a = m.addAction(_("Deck Override...") + s) + s = tr(TR.CARD_TEMPLATES_OFF) + a = m.addAction(tr(TR.CARD_TEMPLATES_DECK_OVERRIDE) + s) qconnect(a.triggered, self.onTargetDeck) - a = m.addAction(_("Browser Appearance...")) + a = m.addAction(tr(TR.CARD_TEMPLATES_BROWSER_APPEARANCE)) qconnect(a.triggered, self.onBrowserDisplay) m.exec_(self.topAreaForm.templateOptions.mapToGlobal(QPoint(0, 0))) diff --git a/qt/aqt/customstudy.py b/qt/aqt/customstudy.py index bf049575e..dc8415603 100644 --- a/qt/aqt/customstudy.py +++ b/qt/aqt/customstudy.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - import aqt from anki.consts import * from anki.lang import _ @@ -50,11 +49,11 @@ class CustomStudy(QDialog): smin = 1 smax = DYN_MAX_SIZE sval = 1 - post = _("cards") + post = tr(TR.CUSTOM_STUDY_CARDS) tit = "" spShow = True typeShow = False - ok = _("OK") + ok = tr(TR.CUSTOM_STUDY_OK) def plus(num): if num == 1000: @@ -68,8 +67,10 @@ class CustomStudy(QDialog): new, self.conf["new"]["perDay"] - self.deck["newToday"][1] ) newExceeding = min(new, new - newUnderLearning) - tit = _("New cards in deck over today limit: %s") % plus(newExceeding) - pre = _("Increase today's new card limit by") + tit = tr(TR.CUSTOM_STUDY_NEW_CARDS_IN_DECK_OVER_TODAY, val="%s") % plus( + newExceeding + ) + pre = tr(TR.CUSTOM_STUDY_INCREASE_TODAYS_NEW_CARD_LIMIT_BY) sval = min(new, self.deck.get("extendNew", 10)) smin = -DYN_MAX_SIZE smax = newExceeding @@ -80,27 +81,29 @@ class CustomStudy(QDialog): rev, self.conf["rev"]["perDay"] - self.deck["revToday"][1] ) revExceeding = min(rev, rev - revUnderLearning) - tit = _("Reviews due in deck over today limit: %s") % plus(revExceeding) - pre = _("Increase today's review limit by") + tit = tr(TR.CUSTOM_STUDY_REVIEWS_DUE_IN_DECK_OVER_TODAY, val="%s") % plus( + revExceeding + ) + pre = tr(TR.CUSTOM_STUDY_INCREASE_TODAYS_REVIEW_LIMIT_BY) sval = min(rev, self.deck.get("extendRev", 10)) smin = -DYN_MAX_SIZE smax = revExceeding elif idx == RADIO_FORGOT: - pre = _("Review cards forgotten in last") - post = _("days") + pre = tr(TR.CUSTOM_STUDY_REVIEW_CARDS_FORGOTTEN_IN_LAST) + post = tr(TR.SCHEDULING_DAYS) smax = 30 elif idx == RADIO_AHEAD: - pre = _("Review ahead by") - post = _("days") + pre = tr(TR.CUSTOM_STUDY_REVIEW_AHEAD_BY) + post = tr(TR.SCHEDULING_DAYS) elif idx == RADIO_PREVIEW: - pre = _("Preview new cards added in the last") - post = _("days") + pre = tr(TR.CUSTOM_STUDY_PREVIEW_NEW_CARDS_ADDED_IN_THE) + post = tr(TR.SCHEDULING_DAYS) sval = 1 elif idx == RADIO_CRAM: - pre = _("Select") - post = _("cards from the deck") + pre = tr(TR.CUSTOM_STUDY_SELECT) + post = tr(TR.CUSTOM_STUDY_CARDS_FROM_THE_DECK) # tit = _("After pressing OK, you can choose which tags to include.") - ok = _("Choose Tags") + ok = tr(TR.CUSTOM_STUDY_CHOOSE_TAGS) sval = 100 typeShow = True sp.setVisible(spShow) @@ -138,7 +141,7 @@ class CustomStudy(QDialog): elif i == RADIO_CRAM: tags = self._getTags() # the rest create a filtered deck - cur = self.mw.col.decks.byName(_("Custom Study Session")) + cur = self.mw.col.decks.byName(tr(TR.CUSTOM_STUDY_CUSTOM_STUDY_SESSION)) if cur: if not cur["dyn"]: showInfo(tr(TR.CUSTOM_STUDY_MUST_RENAME_DECK)) @@ -150,7 +153,9 @@ class CustomStudy(QDialog): dyn = cur self.mw.col.decks.select(cur["id"]) else: - did = self.mw.col.decks.new_filtered(_("Custom Study Session")) + did = self.mw.col.decks.new_filtered( + tr(TR.CUSTOM_STUDY_CUSTOM_STUDY_SESSION) + ) dyn = self.mw.col.decks.get(did) # and then set various options if i == RADIO_FORGOT: @@ -187,7 +192,7 @@ class CustomStudy(QDialog): # generate cards self.created_custom_study = True if not self.mw.col.sched.rebuild_filtered_deck(dyn["id"]): - return showWarning(_("No cards matched the criteria you provided.")) + return showWarning(tr(TR.CUSTOM_STUDY_NO_CARDS_MATCHED_THE_CRITERIA_YOU)) self.mw.moveToState("overview") QDialog.accept(self) diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index fcb8d58e0..34a833ffb 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations from copy import deepcopy @@ -16,7 +15,7 @@ from aqt import AnkiQt, gui_hooks from aqt.qt import * from aqt.sound import av_player from aqt.toolbar import BottomBar -from aqt.utils import askUser, getOnlyText, openLink, shortcut, showWarning, tr +from aqt.utils import TR, askUser, getOnlyText, openLink, shortcut, showWarning, tr class DeckBrowserBottomBar: @@ -79,7 +78,7 @@ class DeckBrowser: elif cmd == "import": self.mw.onImport() elif cmd == "create": - deck = getOnlyText(_("Name for deck:")) + deck = getOnlyText(tr(TR.DECKS_NAME_FOR_DECK)) if deck: self.mw.col.decks.id(deck) gui_hooks.sidebar_should_refresh_decks() @@ -144,9 +143,9 @@ class DeckBrowser: buf = """ %s%s %s""" % ( - _("Deck"), + tr(TR.DECKS_DECK), tr(TR.STATISTICS_DUE_COUNT), - _("New"), + tr(TR.ACTIONS_NEW), ) buf += self._topLevelDragRow() @@ -225,13 +224,13 @@ class DeckBrowser: def _showOptions(self, did: str) -> None: m = QMenu(self.mw) - a = m.addAction(_("Rename")) + a = m.addAction(tr(TR.ACTIONS_RENAME)) qconnect(a.triggered, lambda b, did=did: self._rename(int(did))) - a = m.addAction(_("Options")) + a = m.addAction(tr(TR.ACTIONS_OPTIONS)) qconnect(a.triggered, lambda b, did=did: self._options(did)) - a = m.addAction(_("Export")) + a = m.addAction(tr(TR.ACTIONS_EXPORT)) qconnect(a.triggered, lambda b, did=did: self._export(did)) - a = m.addAction(_("Delete")) + a = m.addAction(tr(TR.ACTIONS_DELETE)) qconnect(a.triggered, lambda b, did=did: self._delete(int(did))) gui_hooks.deck_browser_will_show_options_menu(m, int(did)) m.exec_(QCursor.pos()) @@ -240,10 +239,10 @@ class DeckBrowser: self.mw.onExport(did=did) def _rename(self, did: int) -> None: - self.mw.checkpoint(_("Rename Deck")) + self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK)) deck = self.mw.col.decks.get(did) oldName = deck["name"] - newName = getOnlyText(_("New deck name:"), default=oldName) + newName = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=oldName) newName = newName.replace('"', "") if not newName or newName == oldName: return @@ -277,7 +276,7 @@ class DeckBrowser: self.show() def _delete(self, did): - self.mw.checkpoint(_("Delete Deck")) + self.mw.checkpoint(tr(TR.DECKS_DELETE_DECK)) deck = self.mw.col.decks.get(did) if not deck["dyn"]: dids = [did] + [r[1] for r in self.mw.col.decks.children(did)] @@ -293,7 +292,7 @@ class DeckBrowser: deck["dyn"] or not extra or askUser( - (_("Are you sure you wish to delete %s?") % deck["name"]) + extra + (tr(TR.DECKS_ARE_YOU_SURE_YOU_WISH_TO, val="%s") % deck["name"]) + extra ) ): self.mw.progress.start() @@ -305,9 +304,9 @@ class DeckBrowser: ###################################################################### drawLinks = [ - ["", "shared", _("Get Shared")], - ["", "create", _("Create Deck")], - ["Ctrl+Shift+I", "import", _("Import File")], + ["", "shared", tr(TR.DECKS_GET_SHARED)], + ["", "create", tr(TR.DECKS_CREATE_DECK)], + ["Ctrl+Shift+I", "import", tr(TR.DECKS_IMPORT_FILE)], ] def _drawButtons(self): @@ -315,7 +314,7 @@ class DeckBrowser: drawLinks = deepcopy(self.drawLinks) for b in drawLinks: if b[0]: - b[0] = _("Shortcut key: %s") % shortcut(b[0]) + b[0] = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % shortcut(b[0]) buf += """ """ % tuple( b diff --git a/qt/aqt/deckchooser.py b/qt/aqt/deckchooser.py index ef8a3e064..90fe10cbb 100644 --- a/qt/aqt/deckchooser.py +++ b/qt/aqt/deckchooser.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from typing import Any from anki.lang import _ from aqt import AnkiQt, gui_hooks from aqt.qt import * -from aqt.utils import shortcut +from aqt.utils import TR, shortcut, tr class DeckChooser(QHBoxLayout): @@ -26,12 +25,12 @@ class DeckChooser(QHBoxLayout): def setupDecks(self) -> None: if self.label: - self.deckLabel = QLabel(_("Deck")) + self.deckLabel = QLabel(tr(TR.DECKS_DECK)) self.addWidget(self.deckLabel) # decks box self.deck = QPushButton(clicked=self.onDeckChange) # type: ignore self.deck.setAutoDefault(False) - self.deck.setToolTip(shortcut(_("Target Deck (Ctrl+D)"))) + self.deck.setToolTip(shortcut(tr(TR.QT_MISC_TARGET_DECK_CTRLANDD))) QShortcut(QKeySequence("Ctrl+D"), self.widget, activated=self.onDeckChange) # type: ignore self.addWidget(self.deck) # starting label @@ -48,11 +47,13 @@ class DeckChooser(QHBoxLayout): did = c.odid else: did = 1 - self.setDeckName(self.mw.col.decks.nameOrNone(did) or _("Default")) + self.setDeckName( + self.mw.col.decks.nameOrNone(did) or tr(TR.QT_MISC_DEFAULT) + ) else: self.setDeckName( self.mw.col.decks.nameOrNone(self.mw.col.models.current()["did"]) - or _("Default") + or tr(TR.QT_MISC_DEFAULT) ) # layout sizePolicy = QSizePolicy(QSizePolicy.Policy(7), QSizePolicy.Policy(0)) @@ -74,7 +75,7 @@ class DeckChooser(QHBoxLayout): if not self.mw.col.conf.get("addToCur", True): self.setDeckName( self.mw.col.decks.nameOrNone(self.mw.col.models.current()["did"]) - or _("Default") + or tr(TR.QT_MISC_DEFAULT) ) def onDeckChange(self) -> None: @@ -84,8 +85,8 @@ class DeckChooser(QHBoxLayout): ret = StudyDeck( self.mw, current=current, - accept=_("Choose"), - title=_("Choose Deck"), + accept=tr(TR.ACTIONS_CHOOSE), + title=tr(TR.QT_MISC_CHOOSE_DECK), help="addingnotes", cancel=False, parent=self.widget, diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py index d01e3ea18..471bc8fc5 100644 --- a/qt/aqt/deckconf.py +++ b/qt/aqt/deckconf.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from operator import itemgetter from typing import Any, Dict @@ -13,6 +12,7 @@ from anki.lang import _, ngettext from aqt import gui_hooks from aqt.qt import * from aqt.utils import ( + TR, askUser, getOnlyText, openHelp, @@ -21,6 +21,7 @@ from aqt.utils import ( showInfo, showWarning, tooltip, + tr, ) @@ -34,7 +35,7 @@ class DeckConf(QDialog): self.form = aqt.forms.dconf.Ui_Dialog() self.form.setupUi(self) gui_hooks.deck_conf_did_setup_ui_form(self) - self.mw.checkpoint(_("Options")) + self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS)) self.setupCombos() self.setupConfs() self.setWindowModality(Qt.WindowModal) @@ -44,7 +45,7 @@ class DeckConf(QDialog): self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked, self.onRestore, ) - self.setWindowTitle(_("Options for %s") % self.deck["name"]) + self.setWindowTitle(tr(TR.ACTIONS_OPTIONS_FOR, val="%s") % self.deck["name"]) # qt doesn't size properly with altered fonts otherwise restoreGeom(self, "deckconf", adjustSize=True) gui_hooks.deck_conf_will_show(self) @@ -86,13 +87,13 @@ class DeckConf(QDialog): def confOpts(self): m = QMenu(self.mw) - a = m.addAction(_("Add")) + a = m.addAction(tr(TR.ACTIONS_ADD)) qconnect(a.triggered, self.addGroup) - a = m.addAction(_("Delete")) + a = m.addAction(tr(TR.ACTIONS_DELETE)) qconnect(a.triggered, self.remGroup) - a = m.addAction(_("Rename")) + a = m.addAction(tr(TR.ACTIONS_RENAME)) qconnect(a.triggered, self.renameGroup) - a = m.addAction(_("Set for all subdecks")) + a = m.addAction(tr(TR.SCHEDULING_SET_FOR_ALL_SUBDECKS)) qconnect(a.triggered, self.setChildren) if not self.childDids: a.setEnabled(False) @@ -118,7 +119,7 @@ class DeckConf(QDialog): self.form.count.setText(txt) def addGroup(self) -> None: - name = getOnlyText(_("New options group name:")) + name = getOnlyText(tr(TR.SCHEDULING_NEW_OPTIONS_GROUP_NAME)) if not name: return @@ -134,7 +135,7 @@ class DeckConf(QDialog): def remGroup(self) -> None: if int(self.conf["id"]) == 1: - showInfo(_("The default configuration can't be removed."), self) + showInfo(tr(TR.SCHEDULING_THE_DEFAULT_CONFIGURATION_CANT_BE_REMOVED), self) else: gui_hooks.deck_conf_will_remove_config(self, self.deck, self.conf) self.mw.col.modSchema(check=True) @@ -145,7 +146,7 @@ class DeckConf(QDialog): def renameGroup(self) -> None: old = self.conf["name"] - name = getOnlyText(_("New name:"), default=old) + name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old) if not name or name == old: return @@ -156,7 +157,7 @@ class DeckConf(QDialog): def setChildren(self): if not askUser( - _("Set all decks below %s to this option group?") % self.deck["name"] + tr(TR.SCHEDULING_SET_ALL_DECKS_BELOW_TO, val="%s") % self.deck["name"] ): return for did in self.childDids: @@ -194,7 +195,7 @@ class DeckConf(QDialog): lim = x else: lim = min(x, lim) - return _("(parent limit: %d)") % lim + return tr(TR.SCHEDULING_PARENT_LIMIT, val="%s") % lim def loadConf(self): self.conf = self.mw.col.decks.confForDid(self.deck["id"]) @@ -273,10 +274,10 @@ class DeckConf(QDialog): ret.append(i) except: # invalid, don't update - showWarning(_("Steps must be numbers.")) + showWarning(tr(TR.SCHEDULING_STEPS_MUST_BE_NUMBERS)) return if len(ret) < minSize: - showWarning(_("At least one step is required.")) + showWarning(tr(TR.SCHEDULING_AT_LEAST_ONE_STEP_IS_REQUIRED)) return conf[key] = ret diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 94f040bc6..9aae6e135 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -6,7 +6,7 @@ from typing import List, Optional import aqt from anki.lang import _ from aqt.qt import * -from aqt.utils import askUser, openHelp, restoreGeom, saveGeom, showWarning +from aqt.utils import TR, askUser, openHelp, restoreGeom, saveGeom, showWarning, tr class DeckConf(QDialog): @@ -18,14 +18,14 @@ class DeckConf(QDialog): self.form = aqt.forms.dyndconf.Ui_Dialog() self.form.setupUi(self) if first: - label = _("Build") + label = tr(TR.DECKS_BUILD) else: - label = _("Rebuild") + label = tr(TR.ACTIONS_REBUILD) self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) - self.mw.checkpoint(_("Options")) + self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS)) self.setWindowModality(Qt.WindowModal) qconnect(self.form.buttonBox.helpRequested, lambda: openHelp("filtered-decks")) - self.setWindowTitle(_("Options for %s") % self.deck["name"]) + self.setWindowTitle(tr(TR.ACTIONS_OPTIONS_FOR, val="%s") % self.deck["name"]) restoreGeom(self, "dyndeckconf") self.initialSetup() self.loadConf() @@ -154,9 +154,9 @@ it?""" ret.append(i) except: # invalid, don't update - showWarning(_("Steps must be numbers.")) + showWarning(tr(TR.SCHEDULING_STEPS_MUST_BE_NUMBERS)) return None if len(ret) < minSize: - showWarning(_("At least one step is required.")) + showWarning(tr(TR.SCHEDULING_AT_LEAST_ONE_STEP_IS_REQUIRED)) return None return ret diff --git a/qt/aqt/editcurrent.py b/qt/aqt/editcurrent.py index 0d0e82ce9..cc9ecc6a3 100644 --- a/qt/aqt/editcurrent.py +++ b/qt/aqt/editcurrent.py @@ -1,13 +1,12 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - import aqt.editor from anki.lang import _ from aqt import gui_hooks from aqt.main import ResetReason from aqt.qt import * -from aqt.utils import restoreGeom, saveGeom, tooltip +from aqt.utils import TR, restoreGeom, saveGeom, tooltip, tr class EditCurrent(QDialog): @@ -17,7 +16,7 @@ class EditCurrent(QDialog): self.mw = mw self.form = aqt.forms.editcurrent.Ui_Dialog() self.form.setupUi(self) - self.setWindowTitle(_("Edit Current")) + self.setWindowTitle(tr(TR.EDITING_EDIT_CURRENT)) self.setMinimumHeight(400) self.setMinimumWidth(250) self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut( diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index d01238853..913573a55 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -31,6 +31,7 @@ from aqt.qt import * from aqt.sound import av_player, getAudio from aqt.theme import theme_manager from aqt.utils import ( + TR, getFile, openHelp, qtMenuShortcutWorkaround, @@ -40,6 +41,7 @@ from aqt.utils import ( showInfo, showWarning, tooltip, + tr, ) from aqt.webview import AnkiWebView @@ -122,16 +124,16 @@ class Editor: self._addButton( None, "fields", - _("Customize Fields"), - _("Fields") + "...", + tr(TR.EDITING_CUSTOMIZE_FIELDS), + tr(TR.EDITING_FIELDS) + "...", disables=False, rightside=False, ), self._addButton( None, "cards", - _("Customize Card Templates (Ctrl+L)"), - _("Cards") + "...", + tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL), + tr(TR.EDITING_CARDS) + "...", disables=False, rightside=False, ), @@ -140,22 +142,37 @@ class Editor: gui_hooks.editor_did_init_left_buttons(lefttopbtns, self) righttopbtns: List[str] = [ - self._addButton("text_bold", "bold", _("Bold text (Ctrl+B)"), id="bold"), self._addButton( - "text_italic", "italic", _("Italic text (Ctrl+I)"), id="italic" + "text_bold", "bold", tr(TR.EDITING_BOLD_TEXT_CTRLANDB), id="bold" ), self._addButton( - "text_under", "underline", _("Underline text (Ctrl+U)"), id="underline" + "text_italic", + "italic", + tr(TR.EDITING_ITALIC_TEXT_CTRLANDI), + id="italic", ), self._addButton( - "text_super", "super", _("Superscript (Ctrl++)"), id="superscript" + "text_under", + "underline", + tr(TR.EDITING_UNDERLINE_TEXT_CTRLANDU), + id="underline", + ), + self._addButton( + "text_super", + "super", + tr(TR.EDITING_SUPERSCRIPT_CTRLANDAND), + id="superscript", + ), + self._addButton( + "text_sub", "sub", tr(TR.EDITING_SUBSCRIPT_CTRLAND), id="subscript" + ), + self._addButton( + "text_clear", "clear", tr(TR.EDITING_REMOVE_FORMATTING_CTRLANDR) ), - self._addButton("text_sub", "sub", _("Subscript (Ctrl+=)"), id="subscript"), - self._addButton("text_clear", "clear", _("Remove formatting (Ctrl+R)")), self._addButton( None, "colour", - _("Set foreground colour (F7)"), + tr(TR.EDITING_SET_FOREGROUND_COLOUR_F7), """
    """, ), - self._addButton("text_cloze", "cloze", _("Cloze deletion (Ctrl+Shift+C)")), self._addButton( - "paperclip", "attach", _("Attach pictures/audio/video (F3)") + "text_cloze", "cloze", tr(TR.EDITING_CLOZE_DELETION_CTRLANDSHIFTANDC) ), - self._addButton("media-record", "record", _("Record audio (F5)")), + self._addButton( + "paperclip", "attach", tr(TR.EDITING_ATTACH_PICTURESAUDIOVIDEO_F3) + ), + self._addButton("media-record", "record", tr(TR.EDITING_RECORD_AUDIO_F5)), self._addButton("more", "more"), ] @@ -197,7 +216,7 @@ class Editor: bgcol = self.mw.app.palette().window().color().name() # type: ignore # then load page self.web.stdHtml( - _html % (bgcol, bgcol, topbuts, _("Show Duplicates")), + _html % (bgcol, bgcol, topbuts, tr(TR.EDITING_SHOW_DUPLICATES)), css=["css/editor.css"], js=["js/vendor/jquery.js", "js/editor.js"], context=self, @@ -571,11 +590,13 @@ class Editor: tb.setSpacing(12) tb.setContentsMargins(2, 6, 2, 6) # tags - l = QLabel(_("Tags")) + l = QLabel(tr(TR.EDITING_TAGS)) tb.addWidget(l, 1, 0) self.tags = aqt.tagedit.TagEdit(self.widget) qconnect(self.tags.lostFocus, self.saveTags) - self.tags.setToolTip(shortcut(_("Jump to tags with Ctrl+Shift+T"))) + self.tags.setToolTip( + shortcut(tr(TR.EDITING_JUMP_TO_TAGS_WITH_CTRLANDSHIFTANDT)) + ) border = theme_manager.str_color("border") self.tags.setStyleSheet(f"border: 1px solid {border}") tb.addWidget(self.tags, 1, 1) @@ -708,12 +729,12 @@ to a cloze type first, via 'Notes>Change Note Type'""" extension_filter = " ".join( "*." + extension for extension in sorted(itertools.chain(pics, audio)) ) - key = _("Media") + " (" + extension_filter + ")" + key = tr(TR.EDITING_MEDIA) + " (" + extension_filter + ")" def accept(file): self.addMedia(file, canDelete=True) - file = getFile(self.widget, _("Add Media"), accept, key, key="media") + file = getFile(self.widget, tr(TR.EDITING_ADD_MEDIA), accept, key, key="media") self.parentWindow.activateWindow() def addMedia(self, path, canDelete=False): @@ -746,7 +767,7 @@ to a cloze type first, via 'Notes>Change Note Type'""" file = getAudio(self.widget) except Exception as e: showWarning( - _("Couldn't record audio. Have you installed 'lame'?") + tr(TR.EDITING_COULDNT_RECORD_AUDIO_HAVE_YOU_INSTALLED) + "\n\n" + repr(str(e)) ) @@ -845,13 +866,14 @@ to a cloze type first, via 'Notes>Change Note Type'""" with client.get(url) as response: if response.status_code != 200: error_msg = ( - _("Unexpected response code: %s") % response.status_code + tr(TR.QT_MISC_UNEXPECTED_RESPONSE_CODE, val="%s") + % response.status_code ) return None filecontents = response.content content_type = response.headers.get("content-type") except (urllib.error.URLError, requests.exceptions.RequestException) as e: - error_msg = _("An error occurred while opening %s") % e + error_msg = tr(TR.EDITING_AN_ERROR_OCCURRED_WHILE_OPENING, val="%s") % e return None finally: self.mw.progress.finish() @@ -948,13 +970,17 @@ to a cloze type first, via 'Notes>Change Note Type'""" m = QMenu(self.mw) for text, handler, shortcut in ( - (_("MathJax inline"), self.insertMathjaxInline, "Ctrl+M, M"), - (_("MathJax block"), self.insertMathjaxBlock, "Ctrl+M, E"), - (_("MathJax chemistry"), self.insertMathjaxChemistry, "Ctrl+M, C"), - (_("LaTeX"), self.insertLatex, "Ctrl+T, T"), - (_("LaTeX equation"), self.insertLatexEqn, "Ctrl+T, E"), - (_("LaTeX math env."), self.insertLatexMathEnv, "Ctrl+T, M"), - (_("Edit HTML"), self.onHtmlEdit, "Ctrl+Shift+X"), + (tr(TR.EDITING_MATHJAX_INLINE), self.insertMathjaxInline, "Ctrl+M, M"), + (tr(TR.EDITING_MATHJAX_BLOCK), self.insertMathjaxBlock, "Ctrl+M, E"), + ( + tr(TR.EDITING_MATHJAX_CHEMISTRY), + self.insertMathjaxChemistry, + "Ctrl+M, C", + ), + (tr(TR.EDITING_LATEX), self.insertLatex, "Ctrl+T, T"), + (tr(TR.EDITING_LATEX_EQUATION), self.insertLatexEqn, "Ctrl+T, E"), + (tr(TR.EDITING_LATEX_MATH_ENV), self.insertLatexMathEnv, "Ctrl+T, M"), + (tr(TR.EDITING_EDIT_HTML), self.onHtmlEdit, "Ctrl+Shift+X"), ): a = m.addAction(text) qconnect(a.triggered, handler) @@ -1204,11 +1230,11 @@ class EditorWebView(AnkiWebView): def contextMenuEvent(self, evt: QContextMenuEvent) -> None: m = QMenu(self) - a = m.addAction(_("Cut")) + a = m.addAction(tr(TR.EDITING_CUT)) qconnect(a.triggered, self.onCut) - a = m.addAction(_("Copy")) + a = m.addAction(tr(TR.ACTIONS_COPY)) qconnect(a.triggered, self.onCopy) - a = m.addAction(_("Paste")) + a = m.addAction(tr(TR.EDITING_PASTE)) qconnect(a.triggered, self.onPaste) gui_hooks.editor_will_show_context_menu(self, m) m.popup(QCursor.pos()) diff --git a/qt/aqt/errors.py b/qt/aqt/errors.py index b6cbc2eb6..94b606e34 100644 --- a/qt/aqt/errors.py +++ b/qt/aqt/errors.py @@ -77,11 +77,7 @@ your system's temporary folder may be incorrect.""" if "abortSchemaMod" in error: return if "10013" in error: - return showWarning( - _( - "Your firewall or antivirus program is preventing Anki from creating a connection to itself. Please add an exception for Anki." - ) - ) + return showWarning(tr(TR.QT_MISC_YOUR_FIREWALL_OR_ANTIVIRUS_PROGRAM_IS)) if "no default input" in error.lower(): return showWarning( _( @@ -94,11 +90,7 @@ your system's temporary folder may be incorrect.""" if "Beautiful Soup is not an HTTP client" in error: return if "database or disk is full" in error or "Errno 28" in error: - return showWarning( - _( - "Your computer's storage may be full. Please delete some unneeded files, then try again." - ) - ) + return showWarning(tr(TR.QT_MISC_YOUR_COMPUTERS_STORAGE_MAY_BE_FULL)) if "disk I/O error" in error: showWarning(markdown(tr(TR.ERRORS_ACCESSING_DB))) return diff --git a/qt/aqt/exporting.py b/qt/aqt/exporting.py index fc27b2d68..5691ca476 100644 --- a/qt/aqt/exporting.py +++ b/qt/aqt/exporting.py @@ -14,7 +14,7 @@ from anki import hooks from anki.exporting import Exporter, exporters from anki.lang import _, ngettext from aqt.qt import * -from aqt.utils import checkInvalidFilename, getSaveFile, showWarning, tooltip +from aqt.utils import TR, checkInvalidFilename, getSaveFile, showWarning, tooltip, tr class ExportDialog(QDialog): @@ -49,13 +49,13 @@ class ExportDialog(QDialog): self.exporterChanged(idx) # deck list if self.cids is None: - self.decks = [_("All Decks")] + self.decks = [tr(TR.EXPORTING_ALL_DECKS)] self.decks.extend(d.name for d in self.col.decks.all_names_and_ids()) else: - self.decks = [_("Selected Notes")] + self.decks = [tr(TR.EXPORTING_SELECTED_NOTES)] self.frm.deck.addItems(self.decks) # save button - b = QPushButton(_("Export...")) + b = QPushButton(tr(TR.EXPORTING_EXPORT)) self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole) # set default option if accessed through deck button if did: @@ -107,7 +107,7 @@ class ExportDialog(QDialog): self.exporter.did = self.col.decks.id(name) if self.isVerbatim: name = time.strftime("-%Y-%m-%d@%H-%M-%S", time.localtime(time.time())) - deck_name = _("collection") + name + deck_name = tr(TR.EXPORTING_COLLECTION) + name else: # Get deck name and remove invalid filename characters deck_name = self.decks[self.frm.deck.currentIndex()] @@ -121,7 +121,7 @@ class ExportDialog(QDialog): while 1: file = getSaveFile( self, - _("Export"), + tr(TR.ACTIONS_EXPORT), "export", key_str, self.exporter.ext, @@ -142,7 +142,7 @@ class ExportDialog(QDialog): f = open(file, "wb") f.close() except (OSError, IOError) as e: - showWarning(_("Couldn't save file: %s") % str(e)) + showWarning(tr(TR.EXPORTING_COULDNT_SAVE_FILE, val="%s") % str(e)) else: os.unlink(file) @@ -174,7 +174,7 @@ class ExportDialog(QDialog): def on_export_finished(self): if self.isVerbatim: - msg = _("Collection exported.") + msg = tr(TR.EXPORTING_COLLECTION_EXPORTED) self.mw.reopen() else: if self.isTextNote: diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index 84f05470b..9aa98a3d7 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -9,7 +9,7 @@ from anki.rsbackend import TemplateError from aqt import AnkiQt, gui_hooks from aqt.qt import * from aqt.schema_change_tracker import ChangeTracker -from aqt.utils import askUser, getOnlyText, openHelp, showWarning, tooltip +from aqt.utils import TR, askUser, getOnlyText, openHelp, showWarning, tooltip, tr class FieldDialog(QDialog): @@ -20,11 +20,11 @@ class FieldDialog(QDialog): self.mm = self.mw.col.models self.model = nt self.mm._remove_from_cache(self.model["id"]) - self.mw.checkpoint(_("Fields")) + self.mw.checkpoint(tr(TR.EDITING_FIELDS)) self.change_tracker = ChangeTracker(self.mw) self.form = aqt.forms.fields.Ui_Dialog() self.form.setupUi(self) - self.setWindowTitle(_("Fields for %s") % self.model["name"]) + self.setWindowTitle(tr(TR.FIELDS_FIELDS_FOR, val="%s") % self.model["name"]) self.form.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False) self.form.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False) self.form.buttonBox.button(QDialogButtonBox.Save).setAutoDefault(False) @@ -87,14 +87,14 @@ class FieldDialog(QDialog): if ignoreOrd is not None and f["ord"] == ignoreOrd: continue if f["name"] == txt: - showWarning(_("That field name is already used.")) + showWarning(tr(TR.FIELDS_THAT_FIELD_NAME_IS_ALREADY_USED)) return return txt def onRename(self): idx = self.currentIdx f = self.model["flds"][idx] - name = self._uniqueName(_("New name:"), self.currentIdx, f["name"]) + name = self._uniqueName(tr(TR.ACTIONS_NEW_NAME), self.currentIdx, f["name"]) if not name: return @@ -107,7 +107,7 @@ class FieldDialog(QDialog): self.form.fieldList.setCurrentRow(idx) def onAdd(self): - name = self._uniqueName(_("Field name:")) + name = self._uniqueName(tr(TR.FIELDS_FIELD_NAME)) if not name: return if not self.change_tracker.mark_schema(): @@ -120,10 +120,10 @@ class FieldDialog(QDialog): def onDelete(self): if len(self.model["flds"]) < 2: - return showWarning(_("Notes require at least one field.")) + return showWarning(tr(TR.FIELDS_NOTES_REQUIRE_AT_LEAST_ONE_FIELD)) count = self.mm.useCount(self.model) c = ngettext("%d note", "%d notes", count) % count - if not askUser(_("Delete field from %s?") % c): + if not askUser(tr(TR.FIELDS_DELETE_FIELD_FROM, val="%s") % c): return if not self.change_tracker.mark_schema(): return @@ -137,7 +137,9 @@ class FieldDialog(QDialog): def onPosition(self, delta=-1): idx = self.currentIdx l = len(self.model["flds"]) - txt = getOnlyText(_("New position (1...%d):") % l, default=str(idx + 1)) + txt = getOnlyText( + tr(TR.FIELDS_NEW_POSITION_1, val="%s") % l, default=str(idx + 1) + ) if not txt: return try: diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py index e02fa2d1f..58dde845c 100644 --- a/qt/aqt/importing.py +++ b/qt/aqt/importing.py @@ -1,7 +1,6 @@ # coding=utf-8 # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - import json import os import re @@ -43,14 +42,14 @@ class ChangeMap(QDialog): n = 0 setCurrent = False for field in self.model["flds"]: - item = QListWidgetItem(_("Map to %s") % field["name"]) + item = QListWidgetItem(tr(TR.IMPORTING_MAP_TO, val="%s") % field["name"]) self.frm.fields.addItem(item) if current == field["name"]: setCurrent = True self.frm.fields.setCurrentRow(n) n += 1 - self.frm.fields.addItem(QListWidgetItem(_("Map to Tags"))) - self.frm.fields.addItem(QListWidgetItem(_("Ignore field"))) + self.frm.fields.addItem(QListWidgetItem(tr(TR.IMPORTING_MAP_TO_TAGS))) + self.frm.fields.addItem(QListWidgetItem(tr(TR.IMPORTING_IGNORE_FIELD))) if not setCurrent: if current == "_tags": self.frm.fields.setCurrentRow(n) @@ -100,7 +99,7 @@ class ImportDialog(QDialog): self.frm.tagModified.setText(self.mw.pm.profile.get("tagModified", "")) self.frm.tagModified.setCol(self.mw.col) # import button - b = QPushButton(_("Import")) + b = QPushButton(tr(TR.ACTIONS_IMPORT)) self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole) self.exec_() @@ -156,24 +155,24 @@ you can enter it here. Use \\t to represent tab.""" else: d = self.importer.dialect.delimiter if d == "\t": - d = _("Tab") + d = tr(TR.IMPORTING_TAB) elif d == ",": - d = _("Comma") + d = tr(TR.IMPORTING_COMMA) elif d == " ": - d = _("Space") + d = tr(TR.STUDYING_SPACE) elif d == ";": - d = _("Semicolon") + d = tr(TR.IMPORTING_SEMICOLON) elif d == ":": - d = _("Colon") + d = tr(TR.IMPORTING_COLON) else: d = repr(d) - txt = _("Fields separated by: %s") % d + txt = tr(TR.IMPORTING_FIELDS_SEPARATED_BY, val="%s") % d self.frm.autoDetect.setText(txt) def accept(self): self.importer.mapping = self.mapping if not self.importer.mappingOk(): - showWarning(_("The first field of the note type must be mapped.")) + showWarning(tr(TR.IMPORTING_THE_FIRST_FIELD_OF_THE_NOTE)) return self.importer.importMode = self.frm.importMode.currentIndex() self.mw.pm.profile["importMode"] = self.importer.importMode @@ -186,7 +185,7 @@ you can enter it here. Use \\t to represent tab.""" self.mw.col.models.save(self.importer.model, updateReqs=False) self.mw.col.decks.select(did) self.mw.progress.start() - self.mw.checkpoint(_("Import")) + self.mw.checkpoint(tr(TR.ACTIONS_IMPORT)) def on_done(future: Future): self.mw.progress.finish() @@ -208,7 +207,7 @@ you can enter it here. Use \\t to represent tab.""" showText(msg) return else: - txt = _("Importing complete.") + "\n" + txt = tr(TR.IMPORTING_IMPORTING_COMPLETE) + "\n" if self.importer.log: txt += "\n".join(self.importer.log) self.close() @@ -248,16 +247,16 @@ you can enter it here. Use \\t to represent tab.""" self.grid.setSpacing(6) fields = self.importer.fields() for num in range(len(self.mapping)): - text = _("Field %d of file is:") % (num + 1) + text = tr(TR.IMPORTING_FIELD_OF_FILE_IS, val="%s") % (num + 1) self.grid.addWidget(QLabel(text), num, 0) if self.mapping[num] == "_tags": - text = _("mapped to Tags") + text = tr(TR.IMPORTING_MAPPED_TO_TAGS) elif self.mapping[num]: - text = _("mapped to %s") % self.mapping[num] + text = tr(TR.IMPORTING_MAPPED_TO, val="%s") % self.mapping[num] else: - text = _("") + text = tr(TR.IMPORTING_IGNORED) self.grid.addWidget(QLabel(text), num, 1) - button = QPushButton(_("Change")) + button = QPushButton(tr(TR.IMPORTING_CHANGE)) self.grid.addWidget(button, num, 2) qconnect(button.clicked, lambda _, s=self, n=num: s.changeMappingNum(n)) @@ -308,7 +307,7 @@ def showUnicodeWarning(): def onImport(mw): filt = ";;".join([x[0] for x in importing.Importers]) - file = getFile(mw, _("Import"), None, key="import", filter=filt) + file = getFile(mw, tr(TR.ACTIONS_IMPORT), None, key="import", filter=filt) if not file: return file = str(file) @@ -316,18 +315,10 @@ def onImport(mw): head, ext = os.path.splitext(file) ext = ext.lower() if ext == ".anki": - showInfo( - _( - ".anki files are from a very old version of Anki. You can import them with Anki 2.0, available on the Anki website." - ) - ) + showInfo(tr(TR.IMPORTING_ANKI_FILES_ARE_FROM_A_VERY)) return elif ext == ".anki2": - showInfo( - _( - ".anki2 files are not directly importable - please import the .apkg or .zip file you have received instead." - ) - ) + showInfo(tr(TR.IMPORTING_ANKI2_FILES_ARE_NOT_DIRECTLY_IMPORTABLE)) return importFile(mw, file) @@ -364,7 +355,7 @@ def importFile(mw, file): mw.progress.finish() msg = repr(str(e)) if msg == "'unknownFormat'": - showWarning(_("Unknown file format.")) + showWarning(tr(TR.IMPORTING_UNKNOWN_FILE_FORMAT)) else: msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n" msg += str(traceback.format_exc()) @@ -513,7 +504,7 @@ def _replaceWithApkg(mw, filename, backup): future.result() except Exception as e: print(e) - showWarning(_("The provided file is not a valid .apkg file.")) + showWarning(tr(TR.IMPORTING_THE_PROVIDED_FILE_IS_NOT_A)) return if not mw.loadCollection(): @@ -521,6 +512,6 @@ def _replaceWithApkg(mw, filename, backup): if backup: mw.col.modSchema(check=False) - tooltip(_("Importing complete.")) + tooltip(tr(TR.IMPORTING_IMPORTING_COMPLETE)) mw.taskman.run_in_background(do_import, on_done) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 7fe614c7a..0a7ace8d5 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations import enum @@ -120,7 +119,9 @@ class AnkiQt(QMainWindow): self.setupAddons(args) self.finish_ui_setup() except: - showInfo(_("Error during startup:\n%s") % traceback.format_exc()) + showInfo( + tr(TR.QT_MISC_ERROR_DURING_STARTUP, val="%s") % traceback.format_exc() + ) sys.exit(1) # must call this after ui set up if self.safeMode: @@ -291,10 +292,10 @@ class AnkiQt(QMainWindow): return not checkInvalidFilename(name) and name != "addons21" def onAddProfile(self): - name = getOnlyText(_("Name:")).strip() + name = getOnlyText(tr(TR.ACTIONS_NAME)).strip() if name: if name in self.pm.profiles(): - return showWarning(_("Name exists.")) + return showWarning(tr(TR.QT_MISC_NAME_EXISTS)) if not self.profileNameOk(name): return self.pm.create(name) @@ -302,13 +303,13 @@ class AnkiQt(QMainWindow): self.refreshProfilesList() def onRenameProfile(self): - name = getOnlyText(_("New name:"), default=self.pm.name).strip() + name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=self.pm.name).strip() if not name: return if name == self.pm.name: return if name in self.pm.profiles(): - return showWarning(_("Name exists.")) + return showWarning(tr(TR.QT_MISC_NAME_EXISTS)) if not self.profileNameOk(name): return self.pm.rename(name) @@ -317,7 +318,7 @@ class AnkiQt(QMainWindow): def onRemProfile(self): profs = self.pm.profiles() if len(profs) < 2: - return showWarning(_("There must be at least one profile.")) + return showWarning(tr(TR.QT_MISC_THERE_MUST_BE_AT_LEAST_ONE)) # sure? if not askUser( _( @@ -348,7 +349,7 @@ Replace your collection with an earlier backup?""" getFile( self.profileDiag, - _("Revert to backup"), + tr(TR.QT_MISC_REVERT_TO_BACKUP), cb=doOpen, filter="*.colpkg", dir=self.pm.backupFolder(), @@ -359,11 +360,7 @@ Replace your collection with an earlier backup?""" # move the existing collection to the trash, as it may not open self.pm.trashCollection() except: - showWarning( - _( - "Unable to move existing file to trash - please try restarting your computer." - ) - ) + showWarning(tr(TR.QT_MISC_UNABLE_TO_MOVE_EXISTING_FILE_TO)) return self.pendingImport = path @@ -553,9 +550,9 @@ close the profile or restart Anki.""" if not self.col: return if self.restoringBackup: - label = _("Closing...") + label = tr(TR.QT_MISC_CLOSING) else: - label = _("Backing Up...") + label = tr(TR.QT_MISC_BACKING_UP) self.progress.start(label=label) corrupt = False try: @@ -641,7 +638,7 @@ from the profile screen." # have two weeks passed? if (intTime() - self.pm.profile["lastOptimize"]) < 86400 * 14: return - self.progress.start(label=_("Optimizing...")) + self.progress.start(label=tr(TR.QT_MISC_OPTIMIZING)) self.col.optimize() self.pm.profile["lastOptimize"] = intTime() self.pm.save() @@ -672,7 +669,7 @@ from the profile screen." def _selectedDeck(self) -> Optional[Deck]: did = self.col.decks.selected() if not self.col.decks.nameOrNone(did): - showInfo(_("Please select a deck.")) + showInfo(tr(TR.QT_MISC_PLEASE_SELECT_A_DECK)) return None return self.col.decks.get(did) @@ -732,8 +729,8 @@ from the profile screen." return web_context = ResetRequired(self) self.web.set_bridge_command(lambda url: self.delayedMaybeReset(), web_context) - i = _("Waiting for editing to finish.") - b = self.button("refresh", _("Resume Now"), id="resume") + i = tr(TR.QT_MISC_WAITING_FOR_EDITING_TO_FINISH) + b = self.button("refresh", tr(TR.QT_MISC_RESUME_NOW), id="resume") self.web.stdHtml( """
    @@ -762,7 +759,7 @@ from the profile screen." ) -> str: class_ = "but " + class_ if key: - key = _("Shortcut key: %s") % key + key = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % key else: key = "" return """ @@ -1033,17 +1030,19 @@ title="%s" %s>%s""" % ( gui_hooks.review_did_undo(cid) else: self.reset() - tooltip(_("Reverted to state prior to '%s'.") % n.lower()) + tooltip(tr(TR.QT_MISC_REVERTED_TO_STATE_PRIOR_TO, val="%s") % n.lower()) gui_hooks.state_did_revert(n) self.maybeEnableUndo() def maybeEnableUndo(self) -> None: if self.col and self.col.undoName(): - self.form.actionUndo.setText(_("Undo %s") % self.col.undoName()) + self.form.actionUndo.setText( + tr(TR.QT_MISC_UNDO2, val="%s") % self.col.undoName() + ) self.form.actionUndo.setEnabled(True) gui_hooks.undo_state_did_change(True) else: - self.form.actionUndo.setText(_("Undo")) + self.form.actionUndo.setText(tr(TR.QT_MISC_UNDO)) self.form.actionUndo.setEnabled(False) gui_hooks.undo_state_did_change(False) @@ -1119,7 +1118,7 @@ title="%s" %s>%s""" % ( import aqt.importing if not os.path.exists(path): - showInfo(_("Please use File>Import to import this file.")) + showInfo(tr(TR.QT_MISC_PLEASE_USE_FILEIMPORT_TO_IMPORT_THIS)) return None aqt.importing.importFile(self, path) @@ -1161,9 +1160,9 @@ title="%s" %s>%s""" % ( if not search: if not deck["dyn"]: search = 'deck:"%s" ' % deck["name"] - while self.col.decks.id_for_name(_("Filtered Deck %d") % n): + while self.col.decks.id_for_name(tr(TR.QT_MISC_FILTERED_DECK, val="%s") % n): n += 1 - name = _("Filtered Deck %d") % n + name = tr(TR.QT_MISC_FILTERED_DECK, val="%s") % n did = self.col.decks.new_filtered(name) diag = aqt.dyndeckconf.DeckConf(self, first=True, search=search) if not diag.ok: @@ -1537,7 +1536,7 @@ will be lost. Continue?""" ) frm.log.appendPlainText(to_append) except UnicodeDecodeError: - to_append = _("") + to_append = tr(TR.QT_MISC_NON_UNICODE_TEXT) to_append = gui_hooks.debug_console_did_evaluate_python( to_append, text, frm ) @@ -1600,9 +1599,9 @@ will be lost. Continue?""" return None self.pendingImport = buf if is_addon: - msg = _("Add-on will be installed when a profile is opened.") + msg = tr(TR.QT_MISC_ADDON_WILL_BE_INSTALLED_WHEN_A) else: - msg = _("Deck will be imported when a profile is opened.") + msg = tr(TR.QT_MISC_DECK_WILL_BE_IMPORTED_WHEN_A) return tooltip(msg) if not self.interactiveState() or self.progress.busy(): # we can't raise the main window while in profile dialog, syncing, etc diff --git a/qt/aqt/modelchooser.py b/qt/aqt/modelchooser.py index 36d78a2dd..6f0dfedc6 100644 --- a/qt/aqt/modelchooser.py +++ b/qt/aqt/modelchooser.py @@ -6,7 +6,7 @@ from typing import Optional from anki.lang import _ from aqt import AnkiQt, gui_hooks from aqt.qt import * -from aqt.utils import shortcut +from aqt.utils import TR, shortcut, tr class ModelChooser(QHBoxLayout): @@ -37,11 +37,11 @@ class ModelChooser(QHBoxLayout): def setupModels(self) -> None: if self.label: - self.modelLabel = QLabel(_("Type")) + self.modelLabel = QLabel(tr(TR.NOTETYPES_TYPE)) self.addWidget(self.modelLabel) # models box self.models = QPushButton() - self.models.setToolTip(shortcut(_("Change Note Type (Ctrl+N)"))) + self.models.setToolTip(shortcut(tr(TR.QT_MISC_CHANGE_NOTE_TYPE_CTRLANDN))) QShortcut(QKeySequence("Ctrl+N"), self.widget, activated=self.on_activated) # type: ignore self.models.setAutoDefault(False) self.addWidget(self.models) @@ -73,7 +73,7 @@ class ModelChooser(QHBoxLayout): current = self.deck.models.current()["name"] # edit button - edit = QPushButton(_("Manage"), clicked=self.onEdit) # type: ignore + edit = QPushButton(tr(TR.QT_MISC_MANAGE), clicked=self.onEdit) # type: ignore def nameFunc(): return sorted(self.deck.models.allNames()) @@ -81,8 +81,8 @@ class ModelChooser(QHBoxLayout): ret = StudyDeck( self.mw, names=nameFunc, - accept=_("Choose"), - title=_("Choose Note Type"), + accept=tr(TR.ACTIONS_CHOOSE), + title=tr(TR.QT_MISC_CHOOSE_NOTE_TYPE), help="_notes", current=current, parent=self.widget, diff --git a/qt/aqt/models.py b/qt/aqt/models.py index dcb41f7de..402299756 100644 --- a/qt/aqt/models.py +++ b/qt/aqt/models.py @@ -14,6 +14,7 @@ from anki.rsbackend import pb from aqt import AnkiQt, gui_hooks from aqt.qt import * from aqt.utils import ( + TR, askUser, getText, maybeHideClose, @@ -21,6 +22,7 @@ from aqt.utils import ( restoreGeom, saveGeom, showInfo, + tr, ) @@ -33,7 +35,7 @@ class Models(QDialog): self.col = mw.col.weakref() assert self.col self.mm = self.col.models - self.mw.checkpoint(_("Note Types")) + self.mw.checkpoint(tr(TR.NOTETYPES_NOTE_TYPES)) self.form = aqt.forms.models.Ui_Dialog() self.form.setupUi(self) qconnect( @@ -54,20 +56,20 @@ class Models(QDialog): box = f.buttonBox default_buttons = [ - (_("Add"), self.onAdd), - (_("Rename"), self.onRename), - (_("Delete"), self.onDelete), + (tr(TR.ACTIONS_ADD), self.onAdd), + (tr(TR.ACTIONS_RENAME), self.onRename), + (tr(TR.ACTIONS_DELETE), self.onDelete), ] if self.fromMain: default_buttons.extend( [ - (_("Fields..."), self.onFields), - (_("Cards..."), self.onCards), + (tr(TR.NOTETYPES_FIELDS), self.onFields), + (tr(TR.NOTETYPES_CARDS), self.onCards), ] ) - default_buttons.append((_("Options..."), self.onAdvanced)) + default_buttons.append((tr(TR.NOTETYPES_OPTIONS), self.onAdvanced)) for label, func in gui_hooks.models_did_init_buttons(default_buttons, self): button = box.addButton(label, QDialogButtonBox.ActionRole) @@ -84,7 +86,7 @@ class Models(QDialog): def onRename(self) -> None: nt = self.current_notetype() - txt = getText(_("New name:"), default=nt["name"]) + txt = getText(tr(TR.ACTIONS_NEW_NAME), default=nt["name"]) name = txt[0].replace('"', "") if txt[1] and name: nt["name"] = name @@ -120,20 +122,20 @@ class Models(QDialog): def onAdd(self) -> None: m = AddModel(self.mw, self).get() if m: - txt = getText(_("Name:"), default=m["name"])[0].replace('"', "") + txt = getText(tr(TR.ACTIONS_NAME), default=m["name"])[0].replace('"', "") if txt: m["name"] = txt self.saveAndRefresh(m) def onDelete(self) -> None: if len(self.models) < 2: - showInfo(_("Please add another note type first."), parent=self) + showInfo(tr(TR.NOTETYPES_PLEASE_ADD_ANOTHER_NOTE_TYPE_FIRST), parent=self) return idx = self.form.modelsList.currentRow() if self.models[idx].use_count: - msg = _("Delete this note type and all its cards?") + msg = tr(TR.NOTETYPES_DELETE_THIS_NOTE_TYPE_AND_ALL) else: - msg = _("Delete this unused note type?") + msg = tr(TR.NOTETYPES_DELETE_THIS_UNUSED_NOTE_TYPE) if not askUser(msg, parent=self): return @@ -158,7 +160,7 @@ class Models(QDialog): frm.latexsvg.setChecked(nt.get("latexsvg", False)) frm.latexHeader.setText(nt["latexPre"]) frm.latexFooter.setText(nt["latexPost"]) - d.setWindowTitle(_("Options for %s") % nt["name"]) + d.setWindowTitle(tr(TR.ACTIONS_OPTIONS_FOR, val="%s") % nt["name"]) qconnect(frm.buttonBox.helpRequested, lambda: openHelp("math?id=latex")) restoreGeom(d, "modelopts") gui_hooks.models_advanced_will_show(d) @@ -209,12 +211,12 @@ class AddModel(QDialog): # standard models self.models = [] for (name, func) in stdmodels.get_stock_notetypes(self.col): - item = QListWidgetItem(_("Add: %s") % name) + item = QListWidgetItem(tr(TR.NOTETYPES_ADD, val="%s") % name) self.dialog.models.addItem(item) self.models.append((True, func)) # add copies for m in sorted(self.col.models.all(), key=itemgetter("name")): - item = QListWidgetItem(_("Clone: %s") % m["name"]) + item = QListWidgetItem(tr(TR.NOTETYPES_CLONE, val="%s") % m["name"]) self.dialog.models.addItem(item) self.models.append((False, m)) # type: ignore self.dialog.models.setCurrentRow(0) diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index 76203ef56..c53a4798a 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations from dataclasses import dataclass @@ -12,7 +11,7 @@ from anki.lang import _ from aqt import gui_hooks from aqt.sound import av_player from aqt.toolbar import BottomBar -from aqt.utils import askUserDialog, openLink, shortcut, tooltip +from aqt.utils import TR, askUserDialog, openLink, shortcut, tooltip, tr class OverviewBottomBar: @@ -67,7 +66,7 @@ class Overview: self.mw.col.startTimebox() self.mw.moveToState("review") if self.mw.state == "overview": - tooltip(_("No cards are due yet.")) + tooltip(tr(TR.STUDYING_NO_CARDS_ARE_DUE_YET)) elif url == "anki": print("anki menu") elif url == "opts": @@ -128,13 +127,13 @@ class Overview: info = self.mw.col.sched.congratulations_info() if info.have_sched_buried and info.have_user_buried: opts = [ - _("Manually Buried Cards"), - _("Buried Siblings"), - _("All Buried Cards"), - _("Cancel"), + tr(TR.STUDYING_MANUALLY_BURIED_CARDS), + tr(TR.STUDYING_BURIED_SIBLINGS), + tr(TR.STUDYING_ALL_BURIED_CARDS), + tr(TR.ACTIONS_CANCEL), ] - diag = askUserDialog(_("What would you like to unbury?"), opts) + diag = askUserDialog(tr(TR.STUDYING_WHAT_WOULD_YOU_LIKE_TO_UNBURY), opts) diag.setDefault(0) ret = diag.run() if ret == opts[0]: @@ -226,13 +225,13 @@ to their original deck.""" %s""" % ( - _("New"), + tr(TR.ACTIONS_NEW), counts[0], - _("Learning"), + tr(TR.SCHEDULING_LEARNING), counts[1], - _("To Review"), + tr(TR.STUDYING_TO_REVIEW), counts[2], - but("study", _("Study Now"), id="study", extra=" autofocus"), + but("study", tr(TR.STUDYING_STUDY_NOW), id="study", extra=" autofocus"), ) _body = """ @@ -249,20 +248,20 @@ to their original deck.""" def _renderBottom(self): links = [ - ["O", "opts", _("Options")], + ["O", "opts", tr(TR.ACTIONS_OPTIONS)], ] if self.mw.col.decks.current()["dyn"]: - links.append(["R", "refresh", _("Rebuild")]) - links.append(["E", "empty", _("Empty")]) + links.append(["R", "refresh", tr(TR.ACTIONS_REBUILD)]) + links.append(["E", "empty", tr(TR.STUDYING_EMPTY)]) else: - links.append(["C", "studymore", _("Custom Study")]) + links.append(["C", "studymore", tr(TR.ACTIONS_CUSTOM_STUDY)]) # links.append(["F", "cram", _("Filter/Cram")]) if self.mw.col.sched.haveBuried(): - links.append(["U", "unbury", _("Unbury")]) + links.append(["U", "unbury", tr(TR.STUDYING_UNBURY)]) buf = "" for b in links: if b[0]: - b[0] = _("Shortcut key: %s") % shortcut(b[0]) + b[0] = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % shortcut(b[0]) buf += """ """ % tuple( b diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index f1d796539..e95d12849 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - import anki.lang import aqt from anki.lang import _ @@ -69,7 +68,9 @@ class Preferences(QDialog): def onLangIdxChanged(self, idx): code = anki.lang.langs[idx][1] self.mw.pm.setLang(code) - showInfo(_("Please restart Anki to complete language change."), parent=self) + showInfo( + tr(TR.PREFERENCES_PLEASE_RESTART_ANKI_TO_COMPLETE_LANGUAGE), parent=self + ) # Collection options ###################################################################### @@ -117,7 +118,7 @@ class Preferences(QDialog): self.mw.pm.setGlMode("auto") else: self.mw.pm.setGlMode("software") - showInfo(_("Changes will take effect when you restart Anki.")) + showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU)) qc = d.conf qc["addToCur"] = not f.useCurrent.currentIndex() @@ -149,11 +150,7 @@ class Preferences(QDialog): if haveNew == wantNew: return - if not askUser( - _( - "This will reset any cards in learning, clear filtered decks, and change the scheduler version. Proceed?" - ) - ): + if not askUser(tr(TR.PREFERENCES_THIS_WILL_RESET_ANY_CARDS_IN)): return if wantNew: @@ -247,4 +244,4 @@ Not currently enabled; click the sync button in the main window to enable.""" self.mw.pm.set_interrupt_audio(self.form.interrupt_audio.isChecked()) if restart_required: - showInfo(_("Changes will take effect when you restart Anki.")) + showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU)) diff --git a/qt/aqt/previewer.py b/qt/aqt/previewer.py index 779d9eaf0..5b7525202 100644 --- a/qt/aqt/previewer.py +++ b/qt/aqt/previewer.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # mypy: check-untyped-defs - import json import re import time @@ -26,7 +25,7 @@ from aqt.qt import ( from aqt.reviewer import replay_audio from aqt.sound import av_player, play_clicked_audio from aqt.theme import theme_manager -from aqt.utils import restoreGeom, saveGeom +from aqt.utils import TR, restoreGeom, saveGeom, tr from aqt.webview import AnkiWebView @@ -62,7 +61,7 @@ class Previewer(QDialog): self.show() def _create_gui(self): - self.setWindowTitle(_("Preview")) + self.setWindowTitle(tr(TR.ACTIONS_PREVIEW)) qconnect(self.finished, self._on_finished) self.silentlyClose = True @@ -73,14 +72,14 @@ class Previewer(QDialog): self.bbox = QDialogButtonBox() self._replay = self.bbox.addButton( - _("Replay Audio"), QDialogButtonBox.ActionRole + tr(TR.ACTIONS_REPLAY_AUDIO), QDialogButtonBox.ActionRole ) self._replay.setAutoDefault(False) self._replay.setShortcut(QKeySequence("R")) self._replay.setToolTip(_("Shortcut key: %s" % "R")) qconnect(self._replay.clicked, self._on_replay_audio) - both_sides_button = QCheckBox(_("Back Side Only")) + both_sides_button = QCheckBox(tr(TR.QT_MISC_BACK_SIDE_ONLY)) both_sides_button.setShortcut(QKeySequence("B")) both_sides_button.setToolTip(_("Shortcut key: %s" % "B")) self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole) @@ -159,7 +158,7 @@ class Previewer(QDialog): c = self.card() func = "_showQuestion" if not c: - txt = _("(please select 1 card)") + txt = tr(TR.QT_MISC_PLEASE_SELECT_1_CARD) bodyclass = "" self._last_state = None else: @@ -240,12 +239,12 @@ class MultiCardPreviewer(Previewer): self._prev = self.bbox.addButton("<", QDialogButtonBox.ActionRole) self._prev.setAutoDefault(False) self._prev.setShortcut(QKeySequence("Left")) - self._prev.setToolTip(_("Shortcut key: Left arrow")) + self._prev.setToolTip(tr(TR.QT_MISC_SHORTCUT_KEY_LEFT_ARROW)) self._next = self.bbox.addButton(">", QDialogButtonBox.ActionRole) self._next.setAutoDefault(True) self._next.setShortcut(QKeySequence("Right")) - self._next.setToolTip(_("Shortcut key: Right arrow or Enter")) + self._next.setToolTip(tr(TR.QT_MISC_SHORTCUT_KEY_RIGHT_ARROW_OR_ENTER)) qconnect(self._prev.clicked, self._on_prev) qconnect(self._next.clicked, self._on_next) diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 351b3dc6a..1617d7be7 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -1,11 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# Profile handling -########################################################################## -# - Saves in pickles rather than json to easily store Qt window state. -# - Saves in sqlite rather than a flat file so the config can't be corrupted - import io import locale import pickle @@ -28,6 +23,12 @@ from aqt import appHelpSite from aqt.qt import * from aqt.utils import TR, locale_dir, showWarning, tr +# Profile handling +########################################################################## +# - Saves in pickles rather than json to easily store Qt window state. +# - Saves in sqlite rather than a flat file so the config can't be corrupted + + metaConf = dict( ver=0, updates=True, @@ -251,7 +252,7 @@ class ProfileManager: except: QMessageBox.warning( None, - _("Profile Corrupt"), + tr(TR.PROFILES_PROFILE_CORRUPT), _( """\ Anki could not read your profile data. Window sizes and your sync login \ @@ -304,12 +305,13 @@ details have been forgotten.""" oldFolder = midFolder else: showWarning( - _("Please remove the folder %s and try again.") % midFolder + tr(TR.PROFILES_PLEASE_REMOVE_THE_FOLDER_AND, val="%s") + % midFolder ) self.name = oldName return else: - showWarning(_("Folder already exists.")) + showWarning(tr(TR.PROFILES_FOLDER_ALREADY_EXISTS)) self.name = oldName return @@ -476,7 +478,7 @@ create table if not exists profiles def _ensureProfile(self) -> None: "Create a new profile if none exists." - self.create(_("User 1")) + self.create(tr(TR.PROFILES_USER_1)) p = os.path.join(self.base, "README.txt") with open(p, "w", encoding="utf8") as file: file.write( diff --git a/qt/aqt/progress.py b/qt/aqt/progress.py index 55b771335..c54742cae 100644 --- a/qt/aqt/progress.py +++ b/qt/aqt/progress.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations import time @@ -10,6 +9,7 @@ from typing import Optional import aqt.forms from anki.lang import _ from aqt.qt import * +from aqt.utils import TR, tr # Progress info ########################################################################## @@ -80,7 +80,7 @@ class ProgressManager: if not parent and self.mw.isVisible(): parent = self.mw - label = label or _("Processing...") + label = label or tr(TR.QT_MISC_PROCESSING) self._win = ProgressDialog(parent) self._win.form.progressBar.setMinimum(min) self._win.form.progressBar.setMaximum(max) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 9f1f7e50c..c4593183f 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations import difflib @@ -22,7 +21,14 @@ from aqt.qt import * from aqt.sound import av_player, getAudio, play_clicked_audio from aqt.theme import theme_manager from aqt.toolbar import BottomBar -from aqt.utils import askUserDialog, downArrow, qtMenuShortcutWorkaround, tooltip +from aqt.utils import ( + TR, + askUserDialog, + downArrow, + qtMenuShortcutWorkaround, + tooltip, + tr, +) from aqt.webview import AnkiWebView @@ -92,8 +98,10 @@ class Reviewer: ) mins = int(round(elapsed[0] / 60)) part2 = ngettext("%s minute.", "%s minutes.", mins) % mins - fin = _("Finish") - diag = askUserDialog("%s %s" % (part1, part2), [_("Continue"), fin]) + fin = tr(TR.STUDYING_FINISH) + diag = askUserDialog( + "%s %s" % (part1, part2), [tr(TR.STUDYING_CONTINUE), fin] + ) diag.setIcon(QMessageBox.Information) if diag.run() == fin: return self.mw.moveToState("deckBrowser") @@ -388,7 +396,7 @@ class Reviewer: Please run Tools>Empty Cards""" ) else: - warn = _("Type answer: unknown field %s") % fld + warn = tr(TR.STUDYING_TYPE_ANSWER_UNKNOWN_FIELD, val="%s") % fld return re.sub(self.typeAnsPat, warn, buf) else: # empty field, remove type answer pattern @@ -569,9 +577,9 @@ time = %(time)d; """ % dict( rem=self._remaining(), - edit=_("Edit"), - editkey=_("Shortcut key: %s") % "E", - more=_("More"), + edit=tr(TR.STUDYING_EDIT), + editkey=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "E", + more=tr(TR.STUDYING_MORE), downArrow=downArrow(), time=self.card.timeTaken() // 1000, ) @@ -581,8 +589,8 @@ time = %(time)d; %s
    """ % ( self._remaining(), - _("Shortcut key: %s") % _("Space"), - _("Show Answer"), + tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % tr(TR.STUDYING_SPACE), + tr(TR.STUDYING_SHOW_ANSWER), ) # wrap it in a table so it has the same top margin as the ease buttons middle = ( @@ -626,17 +634,21 @@ time = %(time)d; button_count = self.mw.col.sched.answerButtons(self.card) if button_count == 2: buttons_tuple: Tuple[Tuple[int, str], ...] = ( - (1, _("Again")), - (2, _("Good")), + (1, tr(TR.STUDYING_AGAIN)), + (2, tr(TR.STUDYING_GOOD)), ) elif button_count == 3: - buttons_tuple = ((1, _("Again")), (2, _("Good")), (3, _("Easy"))) + buttons_tuple = ( + (1, tr(TR.STUDYING_AGAIN)), + (2, tr(TR.STUDYING_GOOD)), + (3, tr(TR.STUDYING_EASY)), + ) else: buttons_tuple = ( - (1, _("Again")), - (2, _("Hard")), - (3, _("Good")), - (4, _("Easy")), + (1, tr(TR.STUDYING_AGAIN)), + (2, tr(TR.STUDYING_HARD)), + (3, tr(TR.STUDYING_GOOD)), + (4, tr(TR.STUDYING_EASY)), ) buttons_tuple = gui_hooks.reviewer_will_init_answer_buttons( buttons_tuple, self, self.card @@ -657,7 +669,7 @@ time = %(time)d; %s""" % ( due, extra, - _("Shortcut key: %s") % i, + tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % i, i, i, label, @@ -682,9 +694,9 @@ time = %(time)d; def onLeech(self, card: Card) -> None: # for now - s = _("Card was a leech.") + s = tr(TR.STUDYING_CARD_WAS_A_LEECH) if card.queue < 0: - s += " " + _("It has been suspended.") + s += " " + tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED) tooltip(s) # Context menu @@ -695,48 +707,48 @@ time = %(time)d; currentFlag = self.card and self.card.userFlag() opts = [ [ - _("Flag Card"), + tr(TR.STUDYING_FLAG_CARD), [ [ - _("Red Flag"), + tr(TR.ACTIONS_RED_FLAG), "Ctrl+1", lambda: self.setFlag(1), dict(checked=currentFlag == 1), ], [ - _("Orange Flag"), + tr(TR.ACTIONS_ORANGE_FLAG), "Ctrl+2", lambda: self.setFlag(2), dict(checked=currentFlag == 2), ], [ - _("Green Flag"), + tr(TR.ACTIONS_GREEN_FLAG), "Ctrl+3", lambda: self.setFlag(3), dict(checked=currentFlag == 3), ], [ - _("Blue Flag"), + tr(TR.ACTIONS_BLUE_FLAG), "Ctrl+4", lambda: self.setFlag(4), dict(checked=currentFlag == 4), ], ], ], - [_("Mark Note"), "*", self.onMark], - [_("Bury Card"), "-", self.onBuryCard], - [_("Bury Note"), "=", self.onBuryNote], - [_("Suspend Card"), "@", self.onSuspendCard], - [_("Suspend Note"), "!", self.onSuspend], - [_("Delete Note"), "Ctrl+Delete", self.onDelete], - [_("Options"), "O", self.onOptions], + [tr(TR.STUDYING_MARK_NOTE), "*", self.onMark], + [tr(TR.STUDYING_BURY_CARD), "-", self.onBuryCard], + [tr(TR.STUDYING_BURY_NOTE), "=", self.onBuryNote], + [tr(TR.ACTIONS_SUSPEND_CARD), "@", self.onSuspendCard], + [tr(TR.STUDYING_SUSPEND_NOTE), "!", self.onSuspend], + [tr(TR.STUDYING_DELETE_NOTE), "Ctrl+Delete", self.onDelete], + [tr(TR.ACTIONS_OPTIONS), "O", self.onOptions], None, - [_("Replay Audio"), "R", self.replayAudio], - [_("Pause Audio"), "5", self.on_pause_audio], - [_("Audio -5s"), "6", self.on_seek_backward], - [_("Audio +5s"), "7", self.on_seek_forward], - [_("Record Own Voice"), "Shift+V", self.onRecordVoice], - [_("Replay Own Voice"), "V", self.onReplayRecorded], + [tr(TR.ACTIONS_REPLAY_AUDIO), "R", self.replayAudio], + [tr(TR.STUDYING_PAUSE_AUDIO), "5", self.on_pause_audio], + [tr(TR.STUDYING_AUDIO_5S), "6", self.on_seek_backward], + [tr(TR.STUDYING_AUDIO_AND5S), "7", self.on_seek_forward], + [tr(TR.STUDYING_RECORD_OWN_VOICE), "Shift+V", self.onRecordVoice], + [tr(TR.STUDYING_REPLAY_OWN_VOICE), "V", self.onReplayRecorded], ] return opts @@ -793,15 +805,15 @@ time = %(time)d; self._drawMark() def onSuspend(self) -> None: - self.mw.checkpoint(_("Suspend")) + self.mw.checkpoint(tr(TR.STUDYING_SUSPEND)) self.mw.col.sched.suspend_cards([c.id for c in self.card.note().cards()]) - tooltip(_("Note suspended.")) + tooltip(tr(TR.STUDYING_NOTE_SUSPENDED)) self.mw.reset() def onSuspendCard(self) -> None: - self.mw.checkpoint(_("Suspend")) + self.mw.checkpoint(tr(TR.STUDYING_SUSPEND)) self.mw.col.sched.suspend_cards([self.card.id]) - tooltip(_("Card suspended.")) + tooltip(tr(TR.STUDYING_CARD_SUSPENDED)) self.mw.reset() def onDelete(self) -> None: @@ -809,7 +821,7 @@ time = %(time)d; # window if self.mw.state != "review" or not self.card: return - self.mw.checkpoint(_("Delete")) + self.mw.checkpoint(tr(TR.ACTIONS_DELETE)) cnt = len(self.card.note().cards()) self.mw.col.remove_notes([self.card.note().id]) self.mw.reset() @@ -821,16 +833,16 @@ time = %(time)d; ) def onBuryCard(self) -> None: - self.mw.checkpoint(_("Bury")) + self.mw.checkpoint(tr(TR.STUDYING_BURY)) self.mw.col.sched.bury_cards([self.card.id]) self.mw.reset() - tooltip(_("Card buried.")) + tooltip(tr(TR.STUDYING_CARD_BURIED)) def onBuryNote(self) -> None: - self.mw.checkpoint(_("Bury")) + self.mw.checkpoint(tr(TR.STUDYING_BURY)) self.mw.col.sched.bury_note(self.card.note()) self.mw.reset() - tooltip(_("Note buried.")) + tooltip(tr(TR.STUDYING_NOTE_BURIED)) def onRecordVoice(self) -> None: self._recordedAudio = getAudio(self.mw, encode=False) @@ -838,6 +850,6 @@ time = %(time)d; def onReplayRecorded(self) -> None: if not self._recordedAudio: - tooltip(_("You haven't recorded your voice yet.")) + tooltip(tr(TR.STUDYING_YOU_HAVENT_RECORDED_YOUR_VOICE_YET)) return av_player.play_file(self._recordedAudio) diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index 8daf31090..e80ff4383 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations from enum import Enum @@ -10,7 +9,7 @@ import aqt from anki.errors import DeckRenameError from anki.lang import _ from aqt.qt import * -from aqt.utils import getOnlyText, showWarning +from aqt.utils import TR, getOnlyText, showWarning, tr class SidebarItemType(Enum): @@ -71,7 +70,7 @@ class NewSidebarTreeView(SidebarTreeViewBase): self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.onContextMenu) # type: ignore self.context_menus = { - SidebarItemType.DECK: ((_("Rename"), self.rename_deck),), + SidebarItemType.DECK: ((tr(TR.ACTIONS_RENAME), self.rename_deck),), } def onContextMenu(self, point: QPoint) -> None: @@ -92,10 +91,10 @@ class NewSidebarTreeView(SidebarTreeViewBase): m.exec_(QCursor.pos()) def rename_deck(self, item: "aqt.browser.SidebarItem") -> None: - self.mw.checkpoint(_("Rename Deck")) + self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK)) deck = self.mw.col.decks.get(item.id) old_name = deck["name"] - new_name = getOnlyText(_("New deck name:"), default=old_name) + new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name) new_name = new_name.replace('"', "") if not new_name or new_name == old_name: return diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index 1c28b5e43..83a0a9fcb 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -23,7 +23,7 @@ from aqt import gui_hooks from aqt.mpv import MPV, MPVBase, MPVCommandError from aqt.qt import * from aqt.taskman import TaskManager -from aqt.utils import restoreGeom, saveGeom, showWarning, startup_info +from aqt.utils import TR, restoreGeom, saveGeom, showWarning, startup_info, tr try: import pyaudio @@ -318,11 +318,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method try: ret.result() except FileNotFoundError: - showWarning( - _( - "Sound and video on cards will not function until mpv or mplayer is installed." - ) - ) + showWarning(tr(TR.MEDIA_SOUND_AND_VIDEO_ON_CARDS_WILL)) # must call cb() here, as we don't currently have another way # to flag to av_player that we've stopped cb() @@ -499,7 +495,7 @@ class _Recorder: finally: self.cleanup() if ret: - raise Exception(_("Error running %s") % " ".join(cmd)) + raise Exception(tr(TR.MEDIA_ERROR_RUNNING, val="%s") % " ".join(cmd)) def cleanup(self) -> None: if os.path.exists(processingSrc): @@ -596,10 +592,10 @@ def getAudio(parent: QWidget, encode: bool = True) -> Optional[str]: restoreGeom(mb, "audioRecorder") mb.setWindowTitle("Anki") mb.setIconPixmap(QPixmap(":/icons/media-record.png")) - but = QPushButton(_("Save")) + but = QPushButton(tr(TR.ACTIONS_SAVE)) mb.addButton(but, QMessageBox.AcceptRole) but.setDefault(True) - but = QPushButton(_("Cancel")) + but = QPushButton(tr(TR.ACTIONS_CANCEL)) mb.addButton(but, QMessageBox.RejectRole) mb.setEscapeButton(but) t = time.time() @@ -607,7 +603,7 @@ def getAudio(parent: QWidget, encode: bool = True) -> Optional[str]: time.sleep(r.startupDelay) QApplication.instance().processEvents() # type: ignore while not mb.clickedButton(): - txt = _("Recording...
    Time: %0.1f") + txt = tr(TR.MEDIA_RECORDINGTIME) mb.setText(txt % (time.time() - t)) mb.show() QApplication.instance().processEvents() # type: ignore diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index 78482ab48..0b4d0b400 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -1,12 +1,20 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - import aqt from anki.lang import _ from aqt import gui_hooks from aqt.qt import * -from aqt.utils import getOnlyText, openHelp, restoreGeom, saveGeom, shortcut, showInfo +from aqt.utils import ( + TR, + getOnlyText, + openHelp, + restoreGeom, + saveGeom, + shortcut, + showInfo, + tr, +) class StudyDeck(QDialog): @@ -43,9 +51,9 @@ class StudyDeck(QDialog): for b in buttons: self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole) else: - b = QPushButton(_("Add")) + b = QPushButton(tr(TR.ACTIONS_ADD)) b.setShortcut(QKeySequence("Ctrl+N")) - b.setToolTip(shortcut(_("Add New Deck (Ctrl+N)"))) + b.setToolTip(shortcut(tr(TR.DECKS_ADD_NEW_DECK_CTRLANDN))) self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole) qconnect(b.clicked, self.onAddDeck) if title: @@ -64,7 +72,7 @@ class StudyDeck(QDialog): self.origNames = names() self.name = None self.ok = self.form.buttonBox.addButton( - accept or _("Study"), QDialogButtonBox.AcceptRole + accept or tr(TR.DECKS_STUDY), QDialogButtonBox.AcceptRole ) self.setWindowModality(Qt.WindowModal) qconnect(self.form.buttonBox.helpRequested, lambda: openHelp(help)) @@ -132,7 +140,7 @@ class StudyDeck(QDialog): gui_hooks.state_did_reset.remove(self.onReset) row = self.form.list.currentRow() if row < 0: - showInfo(_("Please select something.")) + showInfo(tr(TR.DECKS_PLEASE_SELECT_SOMETHING)) return self.name = self.names[self.form.list.currentRow()] QDialog.accept(self) @@ -148,7 +156,7 @@ class StudyDeck(QDialog): default = self.form.filter.text() else: default = self.names[self.form.list.currentRow()] - n = getOnlyText(_("New deck name:"), default=default) + n = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=default) n = n.strip() if n: did = self.mw.col.decks.id(n) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 3a9f668df..54fc60894 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations from typing import Any, Dict, Optional @@ -12,6 +11,7 @@ from anki.rsbackend import SyncStatus from aqt import gui_hooks from aqt.qt import * from aqt.sync import get_sync_status +from aqt.utils import TR, tr from aqt.webview import AnkiWebView @@ -102,30 +102,30 @@ class Toolbar: links = [ self.create_link( "decks", - _("Decks"), + tr(TR.ACTIONS_DECKS), self._deckLinkHandler, - tip=_("Shortcut key: %s") % "D", + tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "D", id="decks", ), self.create_link( "add", - _("Add"), + tr(TR.ACTIONS_ADD), self._addLinkHandler, - tip=_("Shortcut key: %s") % "A", + tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "A", id="add", ), self.create_link( "browse", - _("Browse"), + tr(TR.QT_MISC_BROWSE), self._browseLinkHandler, - tip=_("Shortcut key: %s") % "B", + tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "B", id="browse", ), self.create_link( "stats", - _("Stats"), + tr(TR.QT_MISC_STATS), self._statsLinkHandler, - tip=_("Shortcut key: %s") % "T", + tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "T", id="stats", ), ] @@ -140,8 +140,8 @@ class Toolbar: ###################################################################### def _create_sync_link(self) -> str: - name = _("Sync") - title = _("Shortcut key: %s") % "Y" + name = tr(TR.QT_MISC_SYNC) + title = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "Y" label = "sync" self.link_handlers[label] = self._syncLinkHandler diff --git a/qt/aqt/update.py b/qt/aqt/update.py index 8b179f65f..51eb57668 100644 --- a/qt/aqt/update.py +++ b/qt/aqt/update.py @@ -9,7 +9,7 @@ import aqt from anki.lang import _ from anki.utils import platDesc, versionWithBuild from aqt.qt import * -from aqt.utils import openLink, showText +from aqt.utils import TR, openLink, showText, tr class LatestVersionFinder(QThread): @@ -56,12 +56,12 @@ class LatestVersionFinder(QThread): def askAndUpdate(mw, ver): - baseStr = _("""

    Anki Updated

    Anki %s has been released.

    """) % ver + baseStr = tr(TR.QT_MISC_ANKI_UPDATEDANKI_HAS_BEEN_RELEASED, val="%s") % ver msg = QMessageBox(mw) msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) # type: ignore msg.setIcon(QMessageBox.Information) - msg.setText(baseStr + _("Would you like to download it now?")) - button = QPushButton(_("Ignore this update")) + msg.setText(baseStr + tr(TR.QT_MISC_WOULD_YOU_LIKE_TO_DOWNLOAD_IT)) + button = QPushButton(tr(TR.QT_MISC_IGNORE_THIS_UPDATE)) msg.addButton(button, QMessageBox.RejectRole) msg.setDefaultButton(QMessageBox.Yes) ret = msg.exec_() diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 615a2a204..d7679f173 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - from __future__ import annotations import os @@ -54,7 +53,7 @@ def openHelp(section): def openLink(link): - tooltip(_("Loading..."), period=1000) + tooltip(tr(TR.QT_MISC_LOADING), period=1000) with noBundledLibs(): QDesktopServices.openUrl(QUrl(link)) @@ -145,7 +144,7 @@ def showText( def onCopy(): QApplication.clipboard().setText(text.toPlainText()) - btn = QPushButton(_("Copy to Clipboard")) + btn = QPushButton(tr(TR.QT_MISC_COPY_TO_CLIPBOARD)) qconnect(btn.clicked, onCopy) box.addButton(btn, QDialogButtonBox.ActionRole) @@ -209,8 +208,8 @@ class ButtonedDialog(QMessageBox): for b in buttons: self._buttons.append(self.addButton(b, QMessageBox.AcceptRole)) if help: - self.addButton(_("Help"), QMessageBox.HelpRole) - buttons.append(_("Help")) + self.addButton(tr(TR.ACTIONS_HELP), QMessageBox.HelpRole) + buttons.append(tr(TR.ACTIONS_HELP)) # self.setLayout(v) def run(self): @@ -408,9 +407,7 @@ def getSaveFile(parent, title, dir_description, key, ext, fname=None): aqt.mw.pm.profile[config_key] = dir # check if it exists if os.path.exists(file): - if not askUser( - _("This file exists. Are you sure you want to overwrite it?"), parent - ): + if not askUser(tr(TR.QT_MISC_THIS_FILE_EXISTS_ARE_YOU_SURE), parent): return None return file @@ -656,7 +653,7 @@ def closeTooltip(): def checkInvalidFilename(str, dirsep=True): bad = invalidFilename(str, dirsep) if bad: - showWarning(_("The following character can not be used: %s") % bad) + showWarning(tr(TR.QT_MISC_THE_FOLLOWING_CHARACTER_CAN_NOT_BE, val="%s") % bad) return True return False diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index e02924cbe..de2363a6d 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - import dataclasses import json import re @@ -14,7 +13,7 @@ from anki.utils import isLin, isMac, isWin from aqt import gui_hooks from aqt.qt import * from aqt.theme import theme_manager -from aqt.utils import openLink, showInfo +from aqt.utils import TR, openLink, showInfo, tr serverbaseurl = re.compile(r"^.+:\/\/[^\/]+") @@ -286,7 +285,7 @@ class AnkiWebView(QWebEngineView): def contextMenuEvent(self, evt: QContextMenuEvent) -> None: m = QMenu(self) - a = m.addAction(_("Copy")) + a = m.addAction(tr(TR.ACTIONS_COPY)) qconnect(a.triggered, self.onCopy) gui_hooks.webview_will_show_context_menu(self, m) m.popup(QCursor.pos()) diff --git a/repos.bzl b/repos.bzl index c922129bd..11254554a 100644 --- a/repos.bzl +++ b/repos.bzl @@ -129,44 +129,49 @@ def register_repos(): qtpo_i18n_commit = "872d7f0f6bde52577e8fc795dd85699b0eeb97d5" qtpo_i18n_shallow_since = "1605564627 +0000" - new_git_repository( - name = "rslib_ftl", - build_file_content = """ + i18n_build_content = """ filegroup( name = "files", srcs = glob(["**/*.ftl"]), visibility = ["//visibility:public"], ) - exports_files(["l10n.toml"]) - """, - commit = core_i18n_commit, - shallow_since = core_i18n_shallow_since, - remote = "https://github.com/ankitects/anki-core-i18n", +""" + + native.new_local_repository( + name = "rslib_ftl", + path = "../anki-i18n/core", + build_file_content = i18n_build_content, ) - if not native.existing_rule("extra_ftl"): - new_git_repository( - name = "extra_ftl", - build_file_content = """ -filegroup( - name = "files", - srcs = glob(["**/*.ftl"]), - visibility = ["//visibility:public"], -) + # new_git_repository( + # name = "rslib_ftl", + # build_file_content = i18n_build_content, + # commit = core_i18n_commit, + # shallow_since = core_i18n_shallow_since, + # remote = "https://github.com/ankitects/anki-core-i18n", + # ) -exports_files(["l10n.toml"]) -""", - commit = qtftl_i18n_commit, - shallow_since = qtftl_i18n_shallow_since, - remote = "https://github.com/ankitects/anki-desktop-ftl", + if not native.existing_rule("extra_ftl"): + native.new_local_repository( + name = "extra_ftl", + path = "../anki-i18n/qtftl", + build_file_content = i18n_build_content, ) + # new_git_repository( + # name = "extra_ftl", + # build_file_content = i18n_build_content, + # commit = qtftl_i18n_commit, + # shallow_since = qtftl_i18n_shallow_since, + # remote = "https://github.com/ankitects/anki-desktop-ftl", + # ) + new_git_repository( name = "aqt_po", build_file_content = """ exports_files(glob(["**/*.pot", "**/*.po"]), - visibility = ["//visibility:public"], +visibility = ["//visibility:public"], ) """, commit = qtpo_i18n_commit, diff --git a/rslib/ftl/actions.ftl b/rslib/ftl/actions.ftl new file mode 100644 index 000000000..5822bb7ab --- /dev/null +++ b/rslib/ftl/actions.ftl @@ -0,0 +1,32 @@ +actions-add = Add +actions-blue-flag = Blue Flag +actions-cancel = Cancel +actions-choose = Choose +actions-close = Close +actions-copy = Copy +actions-custom-study = Custom Study +actions-decks = Decks +actions-delete = Delete +actions-export = Export +actions-filter = Filter +actions-green-flag = Green Flag +actions-help = Help +actions-import = Import +actions-manage = Manage... +actions-name = Name: +actions-new = New +actions-new-name = New name: +actions-options = Options +actions-options-for = Options for { $val } +actions-orange-flag = Orange Flag +actions-preview = Preview +actions-rebuild = Rebuild +actions-red-flag = Red Flag +actions-rename = Rename +actions-rename-deck = Rename Deck +actions-replay-audio = Replay Audio +actions-reposition = Reposition +actions-save = Save +actions-search = Search +actions-shortcut-key = Shortcut key: { $val } +actions-suspend-card = Suspend Card diff --git a/rslib/ftl/adding.ftl b/rslib/ftl/adding.ftl new file mode 100644 index 000000000..03ea6b865 --- /dev/null +++ b/rslib/ftl/adding.ftl @@ -0,0 +1,9 @@ +adding-add-shortcut-ctrlandenter = Add (shortcut: ctrl+enter) +adding-added = Added +adding-close-and-lose-current-input = Close and lose current input? +adding-edit = Edit "{ $val }" +adding-history = History +adding-note-deleted = (Note deleted) +adding-shortcut = Shortcut: { $val } +adding-the-first-field-is-empty = The first field is empty. +adding-you-have-a-cloze-deletion-note = You have a cloze deletion note type but have not made any cloze deletions. Proceed? diff --git a/rslib/ftl/browsing.ftl b/rslib/ftl/browsing.ftl new file mode 100644 index 000000000..d5e120ecc --- /dev/null +++ b/rslib/ftl/browsing.ftl @@ -0,0 +1,104 @@ +browsing-add-notes = Add Notes... +browsing-add-tags = Add Tags +browsing-add-tags2 = Add Tags... +browsing-added-today = Added Today +browsing-addon = Add-on +browsing-again-today = Again Today +browsing-all-card-types = All Card Types +browsing-all-fields = All Fields +browsing-answer = Answer +browsing-any-cards-mapped-to-nothing-will = Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? +browsing-any-flag = Any Flag +browsing-browser-appearance = Browser Appearance +browsing-browser-options = Browser Options +browsing-buried = Buried +browsing-card = Card +browsing-card-list = Card List +browsing-card-state = Card State +browsing-cards-cant-be-manually-moved-into = Cards can't be manually moved into a filtered deck. +browsing-change-deck = Change Deck +browsing-change-deck2 = Change Deck... +browsing-change-note-type = Change Note Type +browsing-change-note-type2 = Change Note Type... +browsing-change-to = Change { $val } to: +browsing-clear-unused = Clear Unused +browsing-clear-unused-tags = Clear Unused Tags +browsing-created = Created +browsing-ctrlandshiftande = Ctrl+Shift+E +browsing-current-deck = Current Deck +browsing-current-note-type = Current note type: +browsing-delete-notes = Delete Notes +browsing-delete-tags = Delete Tags +browsing-duplicate = duplicate +browsing-ease = Ease +browsing-end = End +browsing-enter-tags-to-add = Enter tags to add: +browsing-enter-tags-to-delete = Enter tags to delete: +browsing-filter = Filter... +browsing-filtered = (filtered) +browsing-find = Find: +browsing-find-and-replace = Find and Replace +browsing-find-duplicates = Find Duplicates +browsing-first-card = First Card +browsing-flag = Flag +browsing-font = Font: +browsing-font-size = Font Size: +browsing-found-as-across-bs = Found %(a)s across %(b)s. +browsing-home = Home +browsing-ignore-case = Ignore case +browsing-in = In: +browsing-interval = Interval +browsing-last-card = Last Card +browsing-learning = (learning) +browsing-line-size = Line Size: +browsing-manage-note-types = Manage Note Types... +browsing-move-cards = Move Cards +browsing-move-cards-to-deck = Move cards to deck: +browsing-nd-names = %(n)d: %(name)s +browsing-new = (new) +browsing-new-note-type = New note type: +browsing-no-flag = No Flag +browsing-note = Note +browsing-notes-tagged = Notes tagged. +browsing-nothing = Nothing +browsing-only-new-cards-can-be-repositioned = Only new cards can be repositioned. +browsing-optional-filter = Optional filter: +browsing-override-back-template = Override back template: +browsing-override-font = Override font: +browsing-override-front-template = Override front template: +browsing-place-at-end-of-new-card = Place at end of new card queue +browsing-place-in-review-queue-with-interval = Place in review queue with interval between: +browsing-please-give-your-filter-a-name = Please give your filter a name: +browsing-please-select-cards-from-only-one = Please select cards from only one note type. +browsing-preview-selected-card = Preview Selected Card ({ $val }) +browsing-question = Question +browsing-queue-bottom = Queue bottom: { $val } +browsing-queue-top = Queue top: { $val } +browsing-randomize-order = Randomize order +browsing-remove-current-filter = Remove Current Filter... +browsing-remove-from-your-saved-searches = Remove { $val } from your saved searches? +browsing-remove-tags = Remove Tags... +browsing-replace-with = Replace With: +browsing-reposition = Reposition... +browsing-reposition-new-cards = Reposition New Cards +browsing-reschedule = Reschedule +browsing-save-current-filter = Save Current Filter... +browsing-search-in = Search in: +browsing-search-within-formatting-slow = Search within formatting (slow) +browsing-shift-position-of-existing-cards = Shift position of existing cards +browsing-sidebar = Sidebar +browsing-sort-field = Sort Field +browsing-sorting-on-this-column-is-not = Sorting on this column is not supported. Please choose another. +browsing-start-position = Start position: +browsing-step = Step: +browsing-studied-today = Studied Today +browsing-suspended = Suspended +browsing-tag-duplicates = Tag Duplicates +browsing-target-field = Target field: +browsing-today = Today +browsing-toggle-mark = Toggle Mark +browsing-toggle-suspend = Toggle Suspend +browsing-treat-input-as-regular-expression = Treat input as regular expression +browsing-type-here-to-search = +browsing-whole-collection = Whole Collection +browsing-you-must-have-at-least-one = You must have at least one column. diff --git a/rslib/ftl/card-templates.ftl b/rslib/ftl/card-templates.ftl index ee8964be2..2beb4e868 100644 --- a/rslib/ftl/card-templates.ftl +++ b/rslib/ftl/card-templates.ftl @@ -21,3 +21,22 @@ card-templates-preview-settings = Options card-templates-invalid-template-number = Card template { $number } has a problem. card-templates-changes-saved = Changes saved. card-templates-discard-changes = Discard changes? +card-templates-add-card-type = Add Card Type... +card-templates-anki-couldnt-find-the-line-between = Anki couldn't find the line between the question and answer. Please adjust the template manually to switch the question and answer. +card-templates-at-least-one-card-type-is = At least one card type is required. +card-templates-browser-appearance = Browser Appearance... +card-templates-card = Card { $val } +card-templates-card-types = Card Types +card-templates-card-types-for = Card Types for { $val } +card-templates-cloze = Cloze { $val } +card-templates-deck-override = Deck Override... +card-templates-delete-the-as-card-type-and = Delete the '%(a)s' card type, and its %(b)s? +card-templates-enter-deck-to-place-new = Enter deck to place new { $val } cards in, or leave blank: +card-templates-enter-new-card-position-1 = Enter new card position (1...{ $val }): +card-templates-flip = Flip +card-templates-form = Form +card-templates-off = (off) +card-templates-on = (on) +card-templates-remove-card-type = Remove Card Type... +card-templates-rename-card-type = Rename Card Type... +card-templates-reposition-card-type = Reposition Card Type... diff --git a/rslib/ftl/custom-study.ftl b/rslib/ftl/custom-study.ftl index 91c790959..7355ae166 100644 --- a/rslib/ftl/custom-study.ftl +++ b/rslib/ftl/custom-study.ftl @@ -1 +1,28 @@ custom-study-must-rename-deck = Please rename the existing Custom Study deck first. +custom-study-all-cards-in-random-order-dont = All cards in random order (don't reschedule) +custom-study-all-review-cards-in-random-order = All review cards in random order +custom-study-cards = cards +custom-study-cards-from-the-deck = cards from the deck +custom-study-choose-tags = Choose Tags +custom-study-custom-study-session = Custom Study Session +custom-study-due-cards-only = Due cards only +custom-study-increase-todays-new-card-limit = Increase today's new card limit +custom-study-increase-todays-new-card-limit-by = Increase today's new card limit by +custom-study-increase-todays-review-card-limit = Increase today's review card limit +custom-study-increase-todays-review-limit-by = Increase today's review limit by +custom-study-new-cards-in-deck-over-today = New cards in deck over today limit: { $val } +custom-study-new-cards-only = New cards only +custom-study-no-cards-matched-the-criteria-you = No cards matched the criteria you provided. +custom-study-ok = OK +custom-study-preview-new-cards = Preview new cards +custom-study-preview-new-cards-added-in-the = Preview new cards added in the last +custom-study-require-one-or-more-of-these = Require one or more of these tags: +custom-study-review-ahead = Review ahead +custom-study-review-ahead-by = Review ahead by +custom-study-review-cards-forgotten-in-last = Review cards forgotten in last +custom-study-review-forgotten-cards = Review forgotten cards +custom-study-reviews-due-in-deck-over-today = Reviews due in deck over today limit: { $val } +custom-study-select = Select +custom-study-select-tags-to-exclude = Select tags to exclude: +custom-study-selective-study = Selective Study +custom-study-study-by-card-state-or-tag = Study by card state or tag diff --git a/rslib/ftl/decks.ftl b/rslib/ftl/decks.ftl new file mode 100644 index 000000000..c6e263fd3 --- /dev/null +++ b/rslib/ftl/decks.ftl @@ -0,0 +1,33 @@ +decks-add-new-deck-ctrlandn = Add New Deck (Ctrl+N) +decks-are-you-sure-you-wish-to = Are you sure you wish to delete { $val }? +decks-build = Build +decks-cards-selected-by = cards selected by +decks-create-deck = Create Deck +decks-custom-steps-in-minutes = Custom steps (in minutes) +decks-deck = Deck +decks-decreasing-intervals = Decreasing intervals +decks-delete-deck = Delete Deck +decks-enable-second-filter = Enable second filter +decks-filter = Filter: +decks-filter-2 = Filter 2 +decks-get-shared = Get Shared +decks-import-file = Import File +decks-increasing-intervals = Increasing intervals +decks-latest-added-first = Latest added first +decks-limit-to = Limit to +decks-minutes = minutes +decks-most-lapses = Most lapses +decks-name-for-deck = Name for deck: +decks-new-deck-name = New deck name: +decks-no-deck = [no deck] +decks-oldest-seen-first = Oldest seen first +decks-order-added = Order added +decks-order-due = Order due +decks-please-select-something = Please select something. +decks-random = Random +decks-relative-overdueness = Relative overdueness +decks-repeat-failed-cards-after = Repeat failed cards after +decks-reschedule-cards-based-on-my-answers = Reschedule cards based on my answers in this deck +decks-study = Study +decks-study-deck = Study Deck +decks-the-provided-search-did-not-match = The provided search did not match any cards. Would you like to revise it? diff --git a/rslib/ftl/editing.ftl b/rslib/ftl/editing.ftl new file mode 100644 index 000000000..b968bc8f0 --- /dev/null +++ b/rslib/ftl/editing.ftl @@ -0,0 +1,35 @@ +editing-add-media = Add Media +editing-an-error-occurred-while-opening = An error occurred while opening { $val } +editing-attach-picturesaudiovideo-f3 = Attach pictures/audio/video (F3) +editing-bold-text-ctrlandb = Bold text (Ctrl+B) +editing-cards = Cards +editing-change-colour-f8 = Change colour (F8) +editing-cloze-deletion-ctrlandshiftandc = Cloze deletion (Ctrl+Shift+C) +editing-couldnt-record-audio-have-you-installed = Couldn't record audio. Have you installed 'lame'? +editing-customize-card-templates-ctrlandl = Customize Card Templates (Ctrl+L) +editing-customize-fields = Customize Fields +editing-cut = Cut +editing-edit-current = Edit Current +editing-edit-html = Edit HTML +editing-fields = Fields +editing-html-editor = HTML Editor +editing-italic-text-ctrlandi = Italic text (Ctrl+I) +editing-jump-to-tags-with-ctrlandshiftandt = Jump to tags with Ctrl+Shift+T +editing-latex = LaTeX +editing-latex-equation = LaTeX equation +editing-latex-math-env = LaTeX math env. +editing-mathjax-block = MathJax block +editing-mathjax-chemistry = MathJax chemistry +editing-mathjax-inline = MathJax inline +editing-media = Media +editing-paste = Paste +editing-record-audio-f5 = Record audio (F5) +editing-remove-formatting-ctrlandr = Remove formatting (Ctrl+R) +editing-set-foreground-colour-f7 = Set foreground colour (F7) +editing-show-duplicates = Show Duplicates +editing-subscript-ctrland = Subscript (Ctrl+=) +editing-superscript-ctrlandand = Superscript (Ctrl++) +editing-tags = Tags +editing-to-make-a-cloze-deletion-on = To make a cloze deletion on an existing note, you need to change it to a cloze type first, via 'Notes>Change Note Type' +editing-underline-text-ctrlandu = Underline text (Ctrl+U) +editing-warning-cloze-deletions-will-not-work = Warning, cloze deletions will not work until you switch the type at the top to Cloze. diff --git a/rslib/ftl/exporting.ftl b/rslib/ftl/exporting.ftl new file mode 100644 index 000000000..f22b7f0e6 --- /dev/null +++ b/rslib/ftl/exporting.ftl @@ -0,0 +1,17 @@ +exporting-all-decks = All Decks +exporting-anki-20-deck = Anki 2.0 Deck +exporting-anki-collection-package = Anki Collection Package +exporting-anki-deck-package = Anki Deck Package +exporting-cards-in-plain-text = Cards in Plain Text +exporting-collection = collection +exporting-collection-exported = Collection exported. +exporting-couldnt-save-file = Couldn't save file: { $val } +exporting-export = Export... +exporting-export-format = Export format: +exporting-include = Include: +exporting-include-html-and-media-references = Include HTML and media references +exporting-include-media = Include media +exporting-include-scheduling-information = Include scheduling information +exporting-include-tags = Include tags +exporting-notes-in-plain-text = Notes in Plain Text +exporting-selected-notes = Selected Notes diff --git a/rslib/ftl/fields.ftl b/rslib/ftl/fields.ftl new file mode 100644 index 000000000..ddeb41e33 --- /dev/null +++ b/rslib/ftl/fields.ftl @@ -0,0 +1,14 @@ +fields-add-field = Add Field +fields-delete-field-from = Delete field from { $val }? +fields-editing-font = Editing Font +fields-field = Field: +fields-field-name = Field name: +fields-fields-for = Fields for { $val } +fields-font = Font: +fields-new-position-1 = New position (1...{ $val }): +fields-notes-require-at-least-one-field = Notes require at least one field. +fields-remember-last-input-when-adding = Remember last input when adding +fields-reverse-text-direction-rtl = Reverse text direction (RTL) +fields-size = Size: +fields-sort-by-this-field-in-the = Sort by this field in the browser +fields-that-field-name-is-already-used = That field name is already used. diff --git a/rslib/ftl/importing.ftl b/rslib/ftl/importing.ftl index e2917506e..969f4893d 100644 --- a/rslib/ftl/importing.ftl +++ b/rslib/ftl/importing.ftl @@ -1 +1,49 @@ importing-failed-debug-info = Import failed. Debugging info: +importing-aborted = Aborted: { $val } +importing-added-duplicate-with-first-field = Added duplicate with first field: { $val } +importing-allow-html-in-fields = Allow HTML in fields +importing-anki-files-are-from-a-very = .anki files are from a very old version of Anki. You can import them with Anki 2.0, available on the Anki website. +importing-anki2-files-are-not-directly-importable = .anki2 files are not directly importable - please import the .apkg or .zip file you have received instead. +importing-appeared-twice-in-file = Appeared twice in file: { $val } +importing-by-default-anki-will-detect-the = By default, Anki will detect the character between fields, such as a tab, comma, and so on. If Anki is detecting the character incorrectly, you can enter it here. Use \t to represent tab. +importing-change = Change +importing-colon = Colon +importing-comma = Comma +importing-empty-first-field = Empty first field: { $val } +importing-field-mapping = Field mapping +importing-field-of-file-is = Field { $val } of file is: +importing-fields-separated-by = Fields separated by: { $val } +importing-file-version-unknown-trying-import-anyway = File version unknown, trying import anyway. +importing-first-field-matched = First field matched: { $val } +importing-identical = Identical +importing-ignore-field = Ignore field +importing-ignore-lines-where-first-field-matches = Ignore lines where first field matches existing note +importing-ignored = +importing-import-even-if-existing-note-has = Import even if existing note has same first field +importing-import-options = Import options +importing-importing-complete = Importing complete. +importing-invalid-file-please-restore-from-backup = Invalid file. Please restore from backup. +importing-map-to = Map to { $val } +importing-map-to-tags = Map to Tags +importing-mapped-to = mapped to { $val } +importing-mapped-to-tags = mapped to Tags +importing-multicharacter-separators-are-not-supported-please = Multi-character separators are not supported. Please enter one character only. +importing-notes-added-from-file = Notes added from file: { $val } +importing-notes-found-in-file = Notes found in file: { $val } +importing-notes-skipped-as-theyre-already-in = Notes skipped, as they're already in your collection: { $val } +importing-notes-that-could-not-be-imported = Notes that could not be imported as note type has changed: { $val } +importing-notes-updated-as-file-had-newer = Notes updated, as file had newer version: { $val } +importing-rows-had-num1d-fields-expected-num2d = '%(row)s' had %(num1)d fields, expected %(num2)d +importing-selected-file-was-not-in-utf8 = Selected file was not in UTF-8 format. Please see the importing section of the manual. +importing-semicolon = Semicolon +importing-skipped = Skipped +importing-tab = Tab +importing-tag-modified-notes = Tag modified notes: +importing-the-first-field-of-the-note = The first field of the note type must be mapped. +importing-the-provided-file-is-not-a = The provided file is not a valid .apkg file. +importing-this-file-does-not-appear-to = This file does not appear to be a valid .apkg file. If you're getting this error from a file downloaded from AnkiWeb, chances are that your download failed. Please try again, and if the problem persists, please try again with a different browser. +importing-this-will-delete-your-existing-collection = This will delete your existing collection and replace it with the data in the file you're importing. Are you sure? +importing-unable-to-import-from-a-readonly = Unable to import from a read-only file. +importing-unknown-file-format = Unknown file format. +importing-update-existing-notes-when-first-field = Update existing notes when first field matches +importing-updated = Updated diff --git a/rslib/ftl/media.ftl b/rslib/ftl/media.ftl new file mode 100644 index 000000000..7abb5756a --- /dev/null +++ b/rslib/ftl/media.ftl @@ -0,0 +1,7 @@ +media-error-executing = Error executing { $val }. +media-error-running = Error running { $val } +media-for-security-reasons-is-not = For security reasons, '{ $val }' is not allowed on cards. You can still use it by placing the command in a different package, and importing that package in the LaTeX header instead. +media-generated-file = Generated file: { $val } +media-have-you-installed-latex-and-dvipngdvisvgm = Have you installed latex and dvipng/dvisvgm? +media-recordingtime = Recording...
    Time: %0.1f +media-sound-and-video-on-cards-will = Sound and video on cards will not function until mpv or mplayer is installed. diff --git a/rslib/ftl/notetypes.ftl b/rslib/ftl/notetypes.ftl index 924bf3e54..d20d7fff9 100644 --- a/rslib/ftl/notetypes.ftl +++ b/rslib/ftl/notetypes.ftl @@ -19,3 +19,18 @@ notetypes-cloze-name = Cloze notetypes-card-1-name = Card 1 notetypes-card-2-name = Card 2 +notetypes-add = Add: { $val } +notetypes-add-note-type = Add Note Type +notetypes-cards = Cards... +notetypes-clone = Clone: { $val } +notetypes-copy = { $val } copy +notetypes-create-scalable-images-with-dvisvgm = Create scalable images with dvisvgm +notetypes-delete-this-note-type-and-all = Delete this note type and all its cards? +notetypes-delete-this-unused-note-type = Delete this unused note type? +notetypes-fields = Fields... +notetypes-footer = Footer +notetypes-header = Header +notetypes-note-types = Note Types +notetypes-options = Options... +notetypes-please-add-another-note-type-first = Please add another note type first. +notetypes-type = Type diff --git a/rslib/ftl/preferences.ftl b/rslib/ftl/preferences.ftl new file mode 100644 index 000000000..10ee2b028 --- /dev/null +++ b/rslib/ftl/preferences.ftl @@ -0,0 +1,39 @@ +preferences-anki-21-scheduler-beta = Anki 2.1 scheduler (beta) +preferences-automatically-sync-on-profile-openclose = Automatically sync on profile open/close +preferences-backups = Backups +preferences-backups2 = backups +preferences-backupsanki-will-create-a-backup-of =

    Backups
    Anki will create a backup of your collection each time it is closed.

    +preferences-basic = Basic +preferences-change-deck-depending-on-note-type = Change deck depending on note type +preferences-changes-will-take-effect-when-you = Changes will take effect when you restart Anki. +preferences-hardware-acceleration-faster-may-cause-display = Hardware acceleration (faster, may cause display issues) +preferences-hours-past-midnight = hours past midnight +preferences-interface-language = Interface language: +preferences-interrupt-current-audio-when-answering = Interrupt current audio when answering +preferences-keep = Keep +preferences-learn-ahead-limit = Learn ahead limit +preferences-mins = mins +preferences-network = Network +preferences-next-day-starts-at = Next day starts at +preferences-night-mode = Night mode +preferences-note-media-is-not-backed-up = Note: Media is not backed up. Please create a periodic backup of your Anki folder to be safe. +preferences-on-next-sync-force-changes-in = On next sync, force changes in one direction +preferences-paste-clipboard-images-as-png = Paste clipboard images as PNG +preferences-paste-without-shift-key-strips-formatting = Paste without shift key strips formatting +preferences-periodically-sync-media = Periodically sync media +preferences-please-restart-anki-to-complete-language = Please restart Anki to complete language change. +preferences-preferences = Preferences +preferences-scheduling = Scheduling +preferences-show-learning-cards-with-larger-steps = Show learning cards with larger steps before reviews +preferences-show-next-review-time-above-answer = Show next review time above answer buttons +preferences-show-play-buttons-on-cards-with = Show play buttons on cards with audio +preferences-show-remaining-card-count-during-review = Show remaining card count during review +preferences-some-settings-will-take-effect-after = Some settings will take effect after you restart Anki. +preferences-synchronisation = Synchronisation +preferences-synchronizationnot-currently-enabled-click-the-sync = Synchronization
    Not currently enabled; click the sync button in the main window to enable. +preferences-synchronize-audio-and-images-too = Synchronize audio and images too +preferences-this-will-reset-any-cards-in = This will reset any cards in learning, clear filtered decks, and change the scheduler version. Proceed? +preferences-timebox-time-limit = Timebox time limit +preferences-user-interface-size = User interface size +preferences-when-adding-default-to-current-deck = When adding, default to current deck +preferences-you-can-restore-backups-via-fileswitch = You can restore backups via File>Switch Profile. diff --git a/rslib/ftl/profiles.ftl b/rslib/ftl/profiles.ftl new file mode 100644 index 000000000..46b17545b --- /dev/null +++ b/rslib/ftl/profiles.ftl @@ -0,0 +1,10 @@ +profiles-anki-could-not-read-your-profile = Anki could not read your profile data. Window sizes and your sync login details have been forgotten. +profiles-anki-could-not-rename-your-profile = Anki could not rename your profile because it could not rename the profile folder on disk. Please ensure you have permission to write to Documents/Anki and no other programs are accessing your profile folders, then try again. +profiles-folder-already-exists = Folder already exists. +profiles-open = Open +profiles-open-backup = Open Backup... +profiles-please-remove-the-folder-and = Please remove the folder { $val } and try again. +profiles-profile-corrupt = Profile Corrupt +profiles-profiles = Profiles +profiles-quit = Quit +profiles-user-1 = User 1 diff --git a/rslib/ftl/scheduling.ftl b/rslib/ftl/scheduling.ftl index e639dfa9a..b41311af9 100644 --- a/rslib/ftl/scheduling.ftl +++ b/rslib/ftl/scheduling.ftl @@ -90,3 +90,51 @@ scheduling-how-to-custom-study = If you wish to study outside of the regular sch # used in scheduling-how-to-custom-study # "... you can use the custom study feature." scheduling-custom-study = custom study +scheduling-always-include-question-side-when-replaying = Always include question side when replaying audio +scheduling-at-least-one-step-is-required = At least one step is required. +scheduling-automatically-play-audio = Automatically play audio +scheduling-bury-related-new-cards-until-the = Bury related new cards until the next day +scheduling-bury-related-reviews-until-the-next = Bury related reviews until the next day +scheduling-days = days +scheduling-description = Description +scheduling-description-to-show-on-overview-screen = Description to show on overview screen, for current deck: +scheduling-easy-bonus = Easy bonus +scheduling-easy-interval = Easy interval +scheduling-end = (end) +scheduling-general = General +scheduling-graduating-interval = Graduating interval +scheduling-hard-interval = Hard interval +scheduling-ignore-answer-times-longer-than = Ignore answer times longer than +scheduling-interval-modifier = Interval modifier +scheduling-lapses = Lapses +scheduling-lapses2 = lapses +scheduling-learning = Learning +scheduling-leech-action = Leech action +scheduling-leech-threshold = Leech threshold +scheduling-maximum-interval = Maximum interval +scheduling-maximum-reviewsday = Maximum reviews/day +scheduling-minimum-interval = Minimum interval +scheduling-mix-new-cards-and-reviews = Mix new cards and reviews +scheduling-new-cards = New Cards +scheduling-new-cardsday = New cards/day +scheduling-new-interval = New interval +scheduling-new-options-group-name = New options group name: +scheduling-options-group = Options group: +scheduling-order = Order +scheduling-parent-limit = (parent limit: { $val }) +scheduling-review = Review +scheduling-reviews = Reviews +scheduling-seconds = seconds +scheduling-set-all-decks-below-to = Set all decks below { $val } to this option group? +scheduling-set-for-all-subdecks = Set for all subdecks +scheduling-show-answer-timer = Show answer timer +scheduling-show-new-cards-after-reviews = Show new cards after reviews +scheduling-show-new-cards-before-reviews = Show new cards before reviews +scheduling-show-new-cards-in-order-added = Show new cards in order added +scheduling-show-new-cards-in-random-order = Show new cards in random order +scheduling-starting-ease = Starting ease +scheduling-steps-in-minutes = Steps (in minutes) +scheduling-steps-must-be-numbers = Steps must be numbers. +scheduling-tag-only = Tag Only +scheduling-the-default-configuration-cant-be-removed = The default configuration can't be removed. +scheduling-your-changes-will-affect-multiple-decks = Your changes will affect multiple decks. If you wish to change only the current deck, please add a new options group first. diff --git a/rslib/ftl/studying.ftl b/rslib/ftl/studying.ftl new file mode 100644 index 000000000..5963d268a --- /dev/null +++ b/rslib/ftl/studying.ftl @@ -0,0 +1,44 @@ +studying-again = Again +studying-all-buried-cards = All Buried Cards +studying-audio-5s = Audio -5s +studying-audio-and5s = Audio +5s +studying-buried-siblings = Buried Siblings +studying-bury = Bury +studying-bury-card = Bury Card +studying-bury-note = Bury Note +studying-card-buried = Card buried. +studying-card-suspended = Card suspended. +studying-card-was-a-leech = Card was a leech. +studying-cards-will-be-automatically-returned-to = Cards will be automatically returned to their original decks after you review them. +studying-continue = Continue +studying-delete-note = Delete Note +studying-deleting-this-deck-from-the-deck = Deleting this deck from the deck list will return all remaining cards to their original deck. +studying-easy = Easy +studying-edit = Edit +studying-empty = Empty +studying-finish = Finish +studying-flag-card = Flag Card +studying-good = Good +studying-hard = Hard +studying-it-has-been-suspended = It has been suspended. +studying-manually-buried-cards = Manually Buried Cards +studying-mark-note = Mark Note +studying-more = More +studying-no-cards-are-due-yet = No cards are due yet. +studying-note-buried = Note buried. +studying-note-suspended = Note suspended. +studying-pause-audio = Pause Audio +studying-please-run-toolsempty-cards = Please run Tools>Empty Cards +studying-record-own-voice = Record Own Voice +studying-replay-own-voice = Replay Own Voice +studying-show-answer = Show Answer +studying-space = Space +studying-study-now = Study Now +studying-suspend = Suspend +studying-suspend-note = Suspend Note +studying-this-is-a-special-deck-for = This is a special deck for studying outside of the normal schedule. +studying-to-review = To Review +studying-type-answer-unknown-field = Type answer: unknown field { $val } +studying-unbury = Unbury +studying-what-would-you-like-to-unbury = What would you like to unbury? +studying-you-havent-recorded-your-voice-yet = You haven't recorded your voice yet.