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() + " Backups
"
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 = _(""
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 = """
""" % (
- _("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),
"""
%s %s
%s
%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 =
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 =
Anki will create a backup of your collection each time it is closed.
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.