merge bulk of qt/ - designer files still to do

This commit is contained in:
Damien Elmes 2020-11-17 17:42:43 +10:00
parent 6a5c397866
commit 6418993840
49 changed files with 1094 additions and 545 deletions

View file

@ -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 += "<p>" + _("Version %s") % versionWithBuild() + "<br>"
abouttext += "<p>" + tr(TR.ABOUT_VERSION, val="%s") % versionWithBuild() + "<br>"
abouttext += ("Python %s Qt %s PyQt %s<br>") % (
platform.python_version(),
QT_VERSION_STR,
PYQT_VERSION_STR,
)
abouttext += (_("<a href='%s'>Visit website</a>") % aqt.appWebsite) + "</span>"
abouttext += (tr(TR.ABOUT_VISIT_WEBSITE, val="%s") % aqt.appWebsite) + "</span>"
# automatically sorted; add new lines at the end
allusers = sorted(

View file

@ -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()

View file

@ -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 <i>%(id)s</i>: %(error)s")
template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS)
else:
template = _("Error installing <i>%(base)s</i>: %(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 += "<br><br>" + 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 = "<br>".join(log)
if advise_restart:
log_html += "<br><br>" + _(
"<b>Please restart Anki to complete the installation.</b>"
)
log_html += "<br><br>" + 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(
"<br><br>".join(errs + [msg]),
parent=parent,
textFormat="rich",
title=_("Add-on installation error"),
title=tr(TR.ADDONS_ADDON_INSTALLATION_ERROR),
)
return not errs

View file

@ -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 = _("<type here to search; hit enter to show current deck>")
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 += "<p><ol>"
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()

View file

@ -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)))

View file

@ -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)

View file

@ -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 = """
<tr><th colspan=5 align=start>%s</th><th class=count>%s</th>
<th class=count>%s</th><th class=optscol></th></tr>""" % (
_("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 += """
<button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(
b

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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),
"""
<div id="forecolor"
style="display: inline-block; background: #000; border-radius: 5px;"
@ -165,17 +182,19 @@ class Editor:
self._addButton(
None,
"changeCol",
_("Change colour (F8)"),
tr(TR.EDITING_CHANGE_COLOUR_F8),
"""
<div style="display: inline-block; border-radius: 5px;"
class="topbut rainbow"
>""",
),
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())

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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 <b>%d</b> 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 <b>Tags</b>")
text = tr(TR.IMPORTING_MAPPED_TO_TAGS)
elif self.mapping[num]:
text = _("mapped to <b>%s</b>") % self.mapping[num]
text = tr(TR.IMPORTING_MAPPED_TO, val="%s") % self.mapping[num]
else:
text = _("<ignored>")
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)

View file

@ -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(
"""
<center><div style="height: 100%%">
@ -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</button>""" % (
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</button>""" % (
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</button>""" % (
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 = _("<non-unicode text>")
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

View file

@ -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,

View file

@ -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)

View file

@ -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."""
</table>
</td><td align=center>
%s</td></tr></table>""" % (
_("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 += """
<button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple(
b

View file

@ -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))

View file

@ -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)

View file

@ -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(

View file

@ -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)

View file

@ -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;
</script>
""" % 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;
<span class=stattxt>%s</span><br>
<button title="%s" id="ansbut" class="focus" onclick='pycmd("ans");'>%s</button>""" % (
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</button></td>""" % (
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)

View file

@ -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

View file

@ -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...<br>Time: %0.1f")
txt = tr(TR.MEDIA_RECORDINGTIME)
mb.setText(txt % (time.time() - t))
mb.show()
QApplication.instance().processEvents() # type: ignore

View file

@ -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)

View file

@ -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

View file

@ -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 = _("""<h1>Anki Updated</h1>Anki %s has been released.<br><br>""") % 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_()

View file

@ -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

View file

@ -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())

View file

@ -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,

32
rslib/ftl/actions.ftl Normal file
View file

@ -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

9
rslib/ftl/adding.ftl Normal file
View file

@ -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?

104
rslib/ftl/browsing.ftl Normal file
View file

@ -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 = <b>Find</b>:
browsing-find-and-replace = Find and Replace
browsing-find-duplicates = Find Duplicates
browsing-first-card = First Card
browsing-flag = Flag
browsing-font = <b>Font</b>:
browsing-font-size = <b>Font Size</b>:
browsing-found-as-across-bs = Found %(a)s across %(b)s.
browsing-home = Home
browsing-ignore-case = Ignore case
browsing-in = <b>In</b>:
browsing-interval = Interval
browsing-last-card = Last Card
browsing-learning = (learning)
browsing-line-size = <b>Line Size</b>:
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 = <b>Replace With</b>:
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 = <type here to search; hit enter to show current deck>
browsing-whole-collection = Whole Collection
browsing-you-must-have-at-least-one = You must have at least one column.

View file

@ -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...

View file

@ -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

33
rslib/ftl/decks.ftl Normal file
View file

@ -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?

35
rslib/ftl/editing.ftl Normal file
View file

@ -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.

17
rslib/ftl/exporting.ftl Normal file
View file

@ -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 = <b>Export format</b>:
exporting-include = <b>Include</b>:
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

14
rslib/ftl/fields.ftl Normal file
View file

@ -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.

View file

@ -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 <b>{ $val }</b> 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 = <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 <b>{ $val }</b>
importing-mapped-to-tags = mapped to <b>Tags</b>
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

7
rslib/ftl/media.ftl Normal file
View file

@ -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...<br>Time: %0.1f
media-sound-and-video-on-cards-will = Sound and video on cards will not function until mpv or mplayer is installed.

View file

@ -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

39
rslib/ftl/preferences.ftl Normal file
View file

@ -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 = <html><head/><body><p><span style=" font-weight:600;">Backups</span><br/>Anki will create a backup of your collection each time it is closed.</p></body></html>
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 = <b>Synchronisation</b>
preferences-synchronizationnot-currently-enabled-click-the-sync = <b>Synchronization</b><br> 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.

10
rslib/ftl/profiles.ftl Normal file
View file

@ -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

View file

@ -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.

44
rslib/ftl/studying.ftl Normal file
View file

@ -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.