mirror of
https://github.com/ankitects/anki.git
synced 2025-12-14 07:10:59 -05:00
merge bulk of qt/ - designer files still to do
This commit is contained in:
parent
6a5c397866
commit
6418993840
49 changed files with 1094 additions and 545 deletions
|
|
@ -9,7 +9,7 @@ from anki.lang import _
|
||||||
from anki.utils import versionWithBuild
|
from anki.utils import versionWithBuild
|
||||||
from aqt.addons import AddonManager, AddonMeta
|
from aqt.addons import AddonManager, AddonMeta
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import supportText, tooltip
|
from aqt.utils import TR, supportText, tooltip, tr
|
||||||
|
|
||||||
|
|
||||||
class ClosableQDialog(QDialog):
|
class ClosableQDialog(QDialog):
|
||||||
|
|
@ -83,9 +83,9 @@ def show(mw):
|
||||||
"""
|
"""
|
||||||
info = " " + " ".join(info.splitlines(True))
|
info = " " + " ".join(info.splitlines(True))
|
||||||
QApplication.clipboard().setText(info)
|
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)
|
qconnect(btn.clicked, onCopy)
|
||||||
abt.buttonBox.addButton(btn, QDialogButtonBox.ActionRole)
|
abt.buttonBox.addButton(btn, QDialogButtonBox.ActionRole)
|
||||||
abt.buttonBox.button(QDialogButtonBox.Ok).setFocus()
|
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 "
|
"Anki is licensed under the AGPL3 license. Please see "
|
||||||
"the license file in the source distribution for more information."
|
"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>") % (
|
abouttext += ("Python %s Qt %s PyQt %s<br>") % (
|
||||||
platform.python_version(),
|
platform.python_version(),
|
||||||
QT_VERSION_STR,
|
QT_VERSION_STR,
|
||||||
PYQT_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
|
# automatically sorted; add new lines at the end
|
||||||
allusers = sorted(
|
allusers = sorted(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from aqt.main import ResetReason
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
TR,
|
||||||
addCloseShortcut,
|
addCloseShortcut,
|
||||||
askUser,
|
askUser,
|
||||||
downArrow,
|
downArrow,
|
||||||
|
|
@ -25,6 +26,7 @@ from aqt.utils import (
|
||||||
shortcut,
|
shortcut,
|
||||||
showWarning,
|
showWarning,
|
||||||
tooltip,
|
tooltip,
|
||||||
|
tr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,7 +37,7 @@ class AddCards(QDialog):
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.form = aqt.forms.addcards.Ui_Dialog()
|
self.form = aqt.forms.addcards.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
self.setWindowTitle(_("Add"))
|
self.setWindowTitle(tr(TR.ACTIONS_ADD))
|
||||||
self.setMinimumHeight(300)
|
self.setMinimumHeight(300)
|
||||||
self.setMinimumWidth(400)
|
self.setMinimumWidth(400)
|
||||||
self.setupChoosers()
|
self.setupChoosers()
|
||||||
|
|
@ -67,26 +69,26 @@ class AddCards(QDialog):
|
||||||
bb = self.form.buttonBox
|
bb = self.form.buttonBox
|
||||||
ar = QDialogButtonBox.ActionRole
|
ar = QDialogButtonBox.ActionRole
|
||||||
# add
|
# add
|
||||||
self.addButton = bb.addButton(_("Add"), ar)
|
self.addButton = bb.addButton(tr(TR.ACTIONS_ADD), ar)
|
||||||
qconnect(self.addButton.clicked, self.addCards)
|
qconnect(self.addButton.clicked, self.addCards)
|
||||||
self.addButton.setShortcut(QKeySequence("Ctrl+Return"))
|
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
|
# close
|
||||||
self.closeButton = QPushButton(_("Close"))
|
self.closeButton = QPushButton(tr(TR.ACTIONS_CLOSE))
|
||||||
self.closeButton.setAutoDefault(False)
|
self.closeButton.setAutoDefault(False)
|
||||||
bb.addButton(self.closeButton, QDialogButtonBox.RejectRole)
|
bb.addButton(self.closeButton, QDialogButtonBox.RejectRole)
|
||||||
# help
|
# 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)
|
self.helpButton.setAutoDefault(False)
|
||||||
bb.addButton(self.helpButton, QDialogButtonBox.HelpRole)
|
bb.addButton(self.helpButton, QDialogButtonBox.HelpRole)
|
||||||
# history
|
# history
|
||||||
b = bb.addButton(_("History") + " " + downArrow(), ar)
|
b = bb.addButton(tr(TR.ADDING_HISTORY) + " " + downArrow(), ar)
|
||||||
if isMac:
|
if isMac:
|
||||||
sc = "Ctrl+Shift+H"
|
sc = "Ctrl+Shift+H"
|
||||||
else:
|
else:
|
||||||
sc = "Ctrl+H"
|
sc = "Ctrl+H"
|
||||||
b.setShortcut(QKeySequence(sc))
|
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)
|
qconnect(b.clicked, self.onHistory)
|
||||||
b.setEnabled(False)
|
b.setEnabled(False)
|
||||||
self.historyButton = b
|
self.historyButton = b
|
||||||
|
|
@ -151,7 +153,7 @@ class AddCards(QDialog):
|
||||||
a = m.addAction(line)
|
a = m.addAction(line)
|
||||||
qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid))
|
qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid))
|
||||||
else:
|
else:
|
||||||
a = m.addAction(_("(Note deleted)"))
|
a = m.addAction(tr(TR.ADDING_NOTE_DELETED))
|
||||||
a.setEnabled(False)
|
a.setEnabled(False)
|
||||||
gui_hooks.add_cards_will_show_history_menu(self, m)
|
gui_hooks.add_cards_will_show_history_menu(self, m)
|
||||||
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
|
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
|
||||||
|
|
@ -166,7 +168,7 @@ class AddCards(QDialog):
|
||||||
ret = note.dupeOrEmpty()
|
ret = note.dupeOrEmpty()
|
||||||
problem = None
|
problem = None
|
||||||
if ret == 1:
|
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)
|
problem = gui_hooks.add_cards_will_add_note(problem, note)
|
||||||
if problem is not None:
|
if problem is not None:
|
||||||
showWarning(problem, help="AddItems#AddError")
|
showWarning(problem, help="AddItems#AddError")
|
||||||
|
|
@ -199,7 +201,7 @@ class AddCards(QDialog):
|
||||||
# workaround for PyQt focus bug
|
# workaround for PyQt focus bug
|
||||||
self.editor.hideCompleters()
|
self.editor.hideCompleters()
|
||||||
|
|
||||||
tooltip(_("Added"), period=500)
|
tooltip(tr(TR.ADDING_ADDED), period=500)
|
||||||
av_player.stop_and_clear_queue()
|
av_player.stop_and_clear_queue()
|
||||||
self.onReset(keep=True)
|
self.onReset(keep=True)
|
||||||
self.mw.col.autosave()
|
self.mw.col.autosave()
|
||||||
|
|
@ -229,7 +231,7 @@ class AddCards(QDialog):
|
||||||
def ifCanClose(self, onOk: Callable) -> None:
|
def ifCanClose(self, onOk: Callable) -> None:
|
||||||
def afterSave():
|
def afterSave():
|
||||||
ok = self.editor.fieldsAreBlank(self.previousNote) or askUser(
|
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:
|
if ok:
|
||||||
onOk()
|
onOk()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
@ -310,7 +309,7 @@ and have been disabled: %(found)s"
|
||||||
meta = self.addon_meta(dir)
|
meta = self.addon_meta(dir)
|
||||||
name = meta.human_name()
|
name = meta.human_name()
|
||||||
if not meta.enabled:
|
if not meta.enabled:
|
||||||
name += _(" (disabled)")
|
name += tr(TR.ADDONS_DISABLED)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
# Conflict resolution
|
# Conflict resolution
|
||||||
|
|
@ -423,10 +422,7 @@ and have been disabled: %(found)s"
|
||||||
return True
|
return True
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
showWarning(
|
showWarning(
|
||||||
_(
|
tr(TR.ADDONS_UNABLE_TO_UPDATE_OR_DELETE_ADDON, val="%s") % e,
|
||||||
"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,
|
|
||||||
textFormat="plain",
|
textFormat="plain",
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
@ -468,16 +464,16 @@ and have been disabled: %(found)s"
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
|
|
||||||
messages = {
|
messages = {
|
||||||
"zip": _("Corrupt add-on file."),
|
"zip": tr(TR.ADDONS_CORRUPT_ADDON_FILE),
|
||||||
"manifest": _("Invalid add-on manifest."),
|
"manifest": tr(TR.ADDONS_INVALID_ADDON_MANIFEST),
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = messages.get(result.errmsg, _("Unknown error: {}".format(result.errmsg)))
|
msg = messages.get(result.errmsg, _("Unknown error: {}".format(result.errmsg)))
|
||||||
|
|
||||||
if mode == "download": # preserve old format strings for i18n
|
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:
|
else:
|
||||||
template = _("Error installing <i>%(base)s</i>: %(error)s")
|
template = tr(TR.ADDONS_ERROR_INSTALLING_BASES_ERRORS)
|
||||||
|
|
||||||
name = base
|
name = base
|
||||||
|
|
||||||
|
|
@ -488,24 +484,22 @@ and have been disabled: %(found)s"
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
|
|
||||||
if mode == "download": # preserve old format strings for i18n
|
if mode == "download": # preserve old format strings for i18n
|
||||||
template = _("Downloaded %(fname)s")
|
template = tr(TR.ADDONS_DOWNLOADED_FNAMES)
|
||||||
else:
|
else:
|
||||||
template = _("Installed %(name)s")
|
template = tr(TR.ADDONS_INSTALLED_NAMES)
|
||||||
|
|
||||||
name = result.name or base
|
name = result.name or base
|
||||||
strings = [template % dict(name=name, fname=name)]
|
strings = [template % dict(name=name, fname=name)]
|
||||||
|
|
||||||
if result.conflicts:
|
if result.conflicts:
|
||||||
strings.append(
|
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)
|
+ ", ".join(self.addonName(f) for f in result.conflicts)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not result.compatible:
|
if not result.compatible:
|
||||||
strings.append(
|
strings.append(tr(TR.ADDONS_THIS_ADDON_IS_NOT_COMPATIBLE_WITH))
|
||||||
_("This add-on is not compatible with your version of Anki.")
|
|
||||||
)
|
|
||||||
|
|
||||||
return strings
|
return strings
|
||||||
|
|
||||||
|
|
@ -743,9 +737,13 @@ class AddonsDialog(QDialog):
|
||||||
name = addon.human_name()
|
name = addon.human_name()
|
||||||
|
|
||||||
if not addon.enabled:
|
if not addon.enabled:
|
||||||
return name + " " + _("(disabled)")
|
return name + " " + tr(TR.ADDONS_DISABLED2)
|
||||||
elif not addon.compatible():
|
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
|
return name
|
||||||
|
|
||||||
|
|
@ -804,7 +802,7 @@ class AddonsDialog(QDialog):
|
||||||
def onlyOneSelected(self) -> Optional[str]:
|
def onlyOneSelected(self) -> Optional[str]:
|
||||||
dirs = self.selectedAddons()
|
dirs = self.selectedAddons()
|
||||||
if len(dirs) != 1:
|
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 None
|
||||||
return dirs[0]
|
return dirs[0]
|
||||||
|
|
||||||
|
|
@ -820,7 +818,7 @@ class AddonsDialog(QDialog):
|
||||||
if re.match(r"^\d+$", addon):
|
if re.match(r"^\d+$", addon):
|
||||||
openLink(aqt.appShared + "info/{}".format(addon))
|
openLink(aqt.appShared + "info/{}".format(addon))
|
||||||
else:
|
else:
|
||||||
showWarning(_("Add-on was not downloaded from AnkiWeb."))
|
showWarning(tr(TR.ADDONS_ADDON_WAS_NOT_DOWNLOADED_FROM_ANKIWEB))
|
||||||
|
|
||||||
def onViewFiles(self) -> None:
|
def onViewFiles(self) -> None:
|
||||||
# if nothing selected, open top level folder
|
# if nothing selected, open top level folder
|
||||||
|
|
@ -865,13 +863,13 @@ class AddonsDialog(QDialog):
|
||||||
if log:
|
if log:
|
||||||
show_log_to_user(self, log)
|
show_log_to_user(self, log)
|
||||||
else:
|
else:
|
||||||
tooltip(_("No updates available."))
|
tooltip(tr(TR.ADDONS_NO_UPDATES_AVAILABLE))
|
||||||
|
|
||||||
def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]:
|
def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]:
|
||||||
if not paths:
|
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(
|
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:
|
if not paths:
|
||||||
return False
|
return False
|
||||||
|
|
@ -882,7 +880,7 @@ class AddonsDialog(QDialog):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def check_for_updates(self) -> 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)
|
check_and_prompt_for_updates(self, self.mgr, self.after_downloading)
|
||||||
|
|
||||||
def onConfig(self) -> None:
|
def onConfig(self) -> None:
|
||||||
|
|
@ -899,7 +897,7 @@ class AddonsDialog(QDialog):
|
||||||
|
|
||||||
conf = self.mgr.getConfig(addon)
|
conf = self.mgr.getConfig(addon)
|
||||||
if conf is None:
|
if conf is None:
|
||||||
showInfo(_("Add-on has no configuration."))
|
showInfo(tr(TR.ADDONS_ADDON_HAS_NO_CONFIGURATION))
|
||||||
return
|
return
|
||||||
|
|
||||||
ConfigEditor(self, addon, conf)
|
ConfigEditor(self, addon, conf)
|
||||||
|
|
@ -919,7 +917,7 @@ class GetAddons(QDialog):
|
||||||
self.form = aqt.forms.getaddons.Ui_Dialog()
|
self.form = aqt.forms.getaddons.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
b = self.form.buttonBox.addButton(
|
b = self.form.buttonBox.addButton(
|
||||||
_("Browse Add-ons"), QDialogButtonBox.ActionRole
|
tr(TR.ADDONS_BROWSE_ADDONS), QDialogButtonBox.ActionRole
|
||||||
)
|
)
|
||||||
qconnect(b.clicked, self.onBrowse)
|
qconnect(b.clicked, self.onBrowse)
|
||||||
restoreGeom(self, "getaddons", adjustSize=True)
|
restoreGeom(self, "getaddons", adjustSize=True)
|
||||||
|
|
@ -934,7 +932,7 @@ class GetAddons(QDialog):
|
||||||
try:
|
try:
|
||||||
ids = [int(n) for n in self.form.code.text().split()]
|
ids = [int(n) for n in self.form.code.text().split()]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
showWarning(_("Invalid code."))
|
showWarning(tr(TR.ADDONS_INVALID_CODE))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.ids = ids
|
self.ids = ids
|
||||||
|
|
@ -1007,21 +1005,22 @@ def describe_log_entry(id_and_entry: DownloadLogEntry) -> str:
|
||||||
if isinstance(entry, DownloadError):
|
if isinstance(entry, DownloadError):
|
||||||
if entry.status_code is not None:
|
if entry.status_code is not None:
|
||||||
if entry.status_code in (403, 404):
|
if entry.status_code in (403, 404):
|
||||||
buf += _(
|
buf += tr(TR.ADDONS_INVALID_CODE_OR_ADDON_NOT_AVAILABLE)
|
||||||
"Invalid code, or add-on not available for your version of Anki."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
buf += _("Unexpected response code: %s") % entry.status_code
|
|
||||||
else:
|
else:
|
||||||
buf += (
|
buf += (
|
||||||
_("Please check your internet connection.")
|
tr(TR.QT_MISC_UNEXPECTED_RESPONSE_CODE, val="%s")
|
||||||
|
% entry.status_code
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
buf += (
|
||||||
|
tr(TR.ADDONS_PLEASE_CHECK_YOUR_INTERNET_CONNECTION)
|
||||||
+ "\n\n"
|
+ "\n\n"
|
||||||
+ str(entry.exception)
|
+ str(entry.exception)
|
||||||
)
|
)
|
||||||
elif isinstance(entry, InstallError):
|
elif isinstance(entry, InstallError):
|
||||||
buf += entry.errmsg
|
buf += entry.errmsg
|
||||||
else:
|
else:
|
||||||
buf += _("Installed successfully.")
|
buf += tr(TR.ADDONS_INSTALLED_SUCCESSFULLY)
|
||||||
|
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
|
|
@ -1091,7 +1090,7 @@ class DownloaderInstaller(QObject):
|
||||||
# and "%(kb)0.2f" is the number of downloaded
|
# and "%(kb)0.2f" is the number of downloaded
|
||||||
# kilobytes. This lead for example to "Downloading 3/5
|
# kilobytes. This lead for example to "Downloading 3/5
|
||||||
# (27KB)"
|
# (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)
|
% 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)
|
have_problem = download_encountered_problem(log)
|
||||||
|
|
||||||
if have_problem:
|
if have_problem:
|
||||||
text = _("One or more errors occurred:")
|
text = tr(TR.ADDONS_ONE_OR_MORE_ERRORS_OCCURRED)
|
||||||
else:
|
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)
|
text += "<br><br>" + download_log_to_html(log)
|
||||||
|
|
||||||
if have_problem:
|
if have_problem:
|
||||||
|
|
@ -1265,7 +1264,7 @@ def prompt_to_update(
|
||||||
) -> None:
|
) -> None:
|
||||||
names = map(lambda x: mgr.addonName(str(x)), ids)
|
names = map(lambda x: mgr.addonName(str(x)), ids)
|
||||||
if not askUser(
|
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\n"
|
||||||
+ "\n".join(names)
|
+ "\n".join(names)
|
||||||
):
|
):
|
||||||
|
|
@ -1307,7 +1306,7 @@ class ConfigEditor(QDialog):
|
||||||
def onRestoreDefaults(self) -> None:
|
def onRestoreDefaults(self) -> None:
|
||||||
default_conf = self.mgr.addonConfigDefaults(self.addon)
|
default_conf = self.mgr.addonConfigDefaults(self.addon)
|
||||||
self.updateText(default_conf)
|
self.updateText(default_conf)
|
||||||
tooltip(_("Restored defaults"), parent=self)
|
tooltip(tr(TR.ADDONS_RESTORED_DEFAULTS), parent=self)
|
||||||
|
|
||||||
def setupFonts(self) -> None:
|
def setupFonts(self) -> None:
|
||||||
font_mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
font_mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||||
|
|
@ -1373,11 +1372,11 @@ class ConfigEditor(QDialog):
|
||||||
showInfo(msg)
|
showInfo(msg)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showInfo(_("Invalid configuration: ") + repr(e))
|
showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + repr(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(new_conf, dict):
|
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
|
return
|
||||||
|
|
||||||
if new_conf != self.conf:
|
if new_conf != self.conf:
|
||||||
|
|
@ -1417,7 +1416,7 @@ def installAddonPackages(
|
||||||
not showInfo(
|
not showInfo(
|
||||||
q,
|
q,
|
||||||
parent=parent,
|
parent=parent,
|
||||||
title=_("Install Anki add-on"),
|
title=tr(TR.ADDONS_INSTALL_ANKI_ADDON),
|
||||||
type="warning",
|
type="warning",
|
||||||
customBtns=[QMessageBox.No, QMessageBox.Yes],
|
customBtns=[QMessageBox.No, QMessageBox.Yes],
|
||||||
)
|
)
|
||||||
|
|
@ -1430,9 +1429,7 @@ def installAddonPackages(
|
||||||
if log:
|
if log:
|
||||||
log_html = "<br>".join(log)
|
log_html = "<br>".join(log)
|
||||||
if advise_restart:
|
if advise_restart:
|
||||||
log_html += "<br><br>" + _(
|
log_html += "<br><br>" + tr(TR.ADDONS_PLEASE_RESTART_ANKI_TO_COMPLETE_THE)
|
||||||
"<b>Please restart Anki to complete the installation.</b>"
|
|
||||||
)
|
|
||||||
if len(log) == 1 and not strictly_modal:
|
if len(log) == 1 and not strictly_modal:
|
||||||
tooltip(log_html, parent=parent)
|
tooltip(log_html, parent=parent)
|
||||||
else:
|
else:
|
||||||
|
|
@ -1440,15 +1437,15 @@ def installAddonPackages(
|
||||||
log_html,
|
log_html,
|
||||||
parent=parent,
|
parent=parent,
|
||||||
textFormat="rich",
|
textFormat="rich",
|
||||||
title=_("Installation complete"),
|
title=tr(TR.ADDONS_INSTALLATION_COMPLETE),
|
||||||
)
|
)
|
||||||
if errs:
|
if errs:
|
||||||
msg = _("Please report this to the respective add-on author(s).")
|
msg = tr(TR.ADDONS_PLEASE_REPORT_THIS_TO_THE_RESPECTIVE)
|
||||||
showWarning(
|
showWarning(
|
||||||
"<br><br>".join(errs + [msg]),
|
"<br><br>".join(errs + [msg]),
|
||||||
parent=parent,
|
parent=parent,
|
||||||
textFormat="rich",
|
textFormat="rich",
|
||||||
title=_("Add-on installation error"),
|
title=tr(TR.ADDONS_ADDON_INSTALLATION_ERROR),
|
||||||
)
|
)
|
||||||
|
|
||||||
return not errs
|
return not errs
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import html
|
import html
|
||||||
|
|
@ -34,6 +33,7 @@ from aqt.qt import *
|
||||||
from aqt.sidebar import NewSidebarTreeView, SidebarItemType, SidebarTreeViewBase
|
from aqt.sidebar import NewSidebarTreeView, SidebarItemType, SidebarTreeViewBase
|
||||||
from aqt.theme import theme_manager
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
TR,
|
||||||
MenuList,
|
MenuList,
|
||||||
SubMenu,
|
SubMenu,
|
||||||
askUser,
|
askUser,
|
||||||
|
|
@ -169,7 +169,7 @@ class DataModel(QAbstractTableModel):
|
||||||
break
|
break
|
||||||
# give the user a hint an invalid column was added by an add-on
|
# give the user a hint an invalid column was added by an add-on
|
||||||
if not txt:
|
if not txt:
|
||||||
txt = _("Add-on")
|
txt = tr(TR.BROWSING_ADDON)
|
||||||
return txt
|
return txt
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
@ -331,13 +331,13 @@ class DataModel(QAbstractTableModel):
|
||||||
return c.model()["name"]
|
return c.model()["name"]
|
||||||
elif type == "cardIvl":
|
elif type == "cardIvl":
|
||||||
if c.type == CARD_TYPE_NEW:
|
if c.type == CARD_TYPE_NEW:
|
||||||
return _("(new)")
|
return tr(TR.BROWSING_NEW)
|
||||||
elif c.type == CARD_TYPE_LRN:
|
elif c.type == CARD_TYPE_LRN:
|
||||||
return _("(learning)")
|
return tr(TR.BROWSING_LEARNING)
|
||||||
return self.col.format_timespan(c.ivl * 86400)
|
return self.col.format_timespan(c.ivl * 86400)
|
||||||
elif type == "cardEase":
|
elif type == "cardEase":
|
||||||
if c.type == CARD_TYPE_NEW:
|
if c.type == CARD_TYPE_NEW:
|
||||||
return _("(new)")
|
return tr(TR.BROWSING_NEW)
|
||||||
return "%d%%" % (c.factor / 10)
|
return "%d%%" % (c.factor / 10)
|
||||||
elif type == "deck":
|
elif type == "deck":
|
||||||
if c.odid:
|
if c.odid:
|
||||||
|
|
@ -366,7 +366,7 @@ class DataModel(QAbstractTableModel):
|
||||||
|
|
||||||
def nextDue(self, c, index):
|
def nextDue(self, c, index):
|
||||||
if c.odid:
|
if c.odid:
|
||||||
return _("(filtered)")
|
return tr(TR.BROWSING_FILTERED)
|
||||||
elif c.queue == QUEUE_TYPE_LRN:
|
elif c.queue == QUEUE_TYPE_LRN:
|
||||||
date = c.due
|
date = c.due
|
||||||
elif c.queue == QUEUE_TYPE_NEW or c.type == CARD_TYPE_NEW:
|
elif c.queue == QUEUE_TYPE_NEW or c.type == CARD_TYPE_NEW:
|
||||||
|
|
@ -621,7 +621,7 @@ class Browser(QMainWindow):
|
||||||
f = self.form
|
f = self.form
|
||||||
qconnect(f.previewButton.clicked, self.onTogglePreview)
|
qconnect(f.previewButton.clicked, self.onTogglePreview)
|
||||||
f.previewButton.setToolTip(
|
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")
|
f.previewButton.setShortcut("Ctrl+Shift+P")
|
||||||
|
|
||||||
|
|
@ -737,21 +737,21 @@ class Browser(QMainWindow):
|
||||||
|
|
||||||
def setupColumns(self):
|
def setupColumns(self):
|
||||||
self.columns = [
|
self.columns = [
|
||||||
("question", _("Question")),
|
("question", tr(TR.BROWSING_QUESTION)),
|
||||||
("answer", _("Answer")),
|
("answer", tr(TR.BROWSING_ANSWER)),
|
||||||
("template", _("Card")),
|
("template", tr(TR.BROWSING_CARD)),
|
||||||
("deck", _("Deck")),
|
("deck", tr(TR.DECKS_DECK)),
|
||||||
("noteFld", _("Sort Field")),
|
("noteFld", tr(TR.BROWSING_SORT_FIELD)),
|
||||||
("noteCrt", _("Created")),
|
("noteCrt", tr(TR.BROWSING_CREATED)),
|
||||||
("noteMod", tr(TR.SEARCH_NOTE_MODIFIED)),
|
("noteMod", tr(TR.SEARCH_NOTE_MODIFIED)),
|
||||||
("cardMod", tr(TR.SEARCH_CARD_MODIFIED)),
|
("cardMod", tr(TR.SEARCH_CARD_MODIFIED)),
|
||||||
("cardDue", tr(TR.STATISTICS_DUE_DATE)),
|
("cardDue", tr(TR.STATISTICS_DUE_DATE)),
|
||||||
("cardIvl", _("Interval")),
|
("cardIvl", tr(TR.BROWSING_INTERVAL)),
|
||||||
("cardEase", _("Ease")),
|
("cardEase", tr(TR.BROWSING_EASE)),
|
||||||
("cardReps", _("Reviews")),
|
("cardReps", tr(TR.SCHEDULING_REVIEWS)),
|
||||||
("cardLapses", _("Lapses")),
|
("cardLapses", tr(TR.SCHEDULING_LAPSES)),
|
||||||
("noteTags", _("Tags")),
|
("noteTags", tr(TR.EDITING_TAGS)),
|
||||||
("note", _("Note")),
|
("note", tr(TR.BROWSING_NOTE)),
|
||||||
]
|
]
|
||||||
self.columns.sort(key=itemgetter(1))
|
self.columns.sort(key=itemgetter(1))
|
||||||
|
|
||||||
|
|
@ -762,7 +762,7 @@ class Browser(QMainWindow):
|
||||||
qconnect(self.form.searchButton.clicked, self.onSearchActivated)
|
qconnect(self.form.searchButton.clicked, self.onSearchActivated)
|
||||||
qconnect(self.form.searchEdit.lineEdit().returnPressed, self.onSearchActivated)
|
qconnect(self.form.searchEdit.lineEdit().returnPressed, self.onSearchActivated)
|
||||||
self.form.searchEdit.setCompleter(None)
|
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.form.searchEdit.addItems(
|
||||||
[self._searchPrompt] + self.mw.pm.profile["searchHistory"]
|
[self._searchPrompt] + self.mw.pm.profile["searchHistory"]
|
||||||
)
|
)
|
||||||
|
|
@ -938,9 +938,7 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
type = self.model.activeCols[idx]
|
type = self.model.activeCols[idx]
|
||||||
noSort = ("question", "answer")
|
noSort = ("question", "answer")
|
||||||
if type in noSort:
|
if type in noSort:
|
||||||
showInfo(
|
showInfo(tr(TR.BROWSING_SORTING_ON_THIS_COLUMN_IS_NOT))
|
||||||
_("Sorting on this column is not supported. Please " "choose another.")
|
|
||||||
)
|
|
||||||
type = self.col.conf["sortType"]
|
type = self.col.conf["sortType"]
|
||||||
if self.col.conf["sortType"] != type:
|
if self.col.conf["sortType"] != type:
|
||||||
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 type in self.model.activeCols:
|
||||||
if len(self.model.activeCols) < 2:
|
if len(self.model.activeCols) < 2:
|
||||||
self.model.endReset()
|
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)
|
self.model.activeCols.remove(type)
|
||||||
adding = False
|
adding = False
|
||||||
else:
|
else:
|
||||||
|
|
@ -1028,7 +1026,7 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def setupSidebar(self) -> None:
|
def setupSidebar(self) -> None:
|
||||||
dw = self.sidebarDockWidget = QDockWidget(_("Sidebar"), self)
|
dw = self.sidebarDockWidget = QDockWidget(tr(TR.BROWSING_SIDEBAR), self)
|
||||||
dw.setFeatures(QDockWidget.DockWidgetClosable)
|
dw.setFeatures(QDockWidget.DockWidgetClosable)
|
||||||
dw.setObjectName("Sidebar")
|
dw.setObjectName("Sidebar")
|
||||||
dw.setAllowedAreas(Qt.LeftDockWidgetArea)
|
dw.setAllowedAreas(Qt.LeftDockWidgetArea)
|
||||||
|
|
@ -1105,14 +1103,14 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
|
|
||||||
def _stdTree(self, root) -> None:
|
def _stdTree(self, root) -> None:
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
_("Whole Collection"),
|
tr(TR.BROWSING_WHOLE_COLLECTION),
|
||||||
":/icons/collection.svg",
|
":/icons/collection.svg",
|
||||||
self._filterFunc(""),
|
self._filterFunc(""),
|
||||||
item_type=SidebarItemType.COLLECTION,
|
item_type=SidebarItemType.COLLECTION,
|
||||||
)
|
)
|
||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
_("Current Deck"),
|
tr(TR.BROWSING_CURRENT_DECK),
|
||||||
":/icons/deck.svg",
|
":/icons/deck.svg",
|
||||||
self._filterFunc("deck:current"),
|
self._filterFunc("deck:current"),
|
||||||
item_type=SidebarItemType.CURRENT_DECK,
|
item_type=SidebarItemType.CURRENT_DECK,
|
||||||
|
|
@ -1252,41 +1250,44 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
|
|
||||||
def _commonFilters(self):
|
def _commonFilters(self):
|
||||||
return self._simpleFilters(
|
return self._simpleFilters(
|
||||||
((_("Whole Collection"), ""), (_("Current Deck"), "deck:current"))
|
(
|
||||||
|
(tr(TR.BROWSING_WHOLE_COLLECTION), ""),
|
||||||
|
(tr(TR.BROWSING_CURRENT_DECK), "deck:current"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _todayFilters(self):
|
def _todayFilters(self):
|
||||||
subm = SubMenu(_("Today"))
|
subm = SubMenu(tr(TR.BROWSING_TODAY))
|
||||||
subm.addChild(
|
subm.addChild(
|
||||||
self._simpleFilters(
|
self._simpleFilters(
|
||||||
(
|
(
|
||||||
(_("Added Today"), "added:1"),
|
(tr(TR.BROWSING_ADDED_TODAY), "added:1"),
|
||||||
(_("Studied Today"), "rated:1"),
|
(tr(TR.BROWSING_STUDIED_TODAY), "rated:1"),
|
||||||
(_("Again Today"), "rated:1:1"),
|
(tr(TR.BROWSING_AGAIN_TODAY), "rated:1:1"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return subm
|
return subm
|
||||||
|
|
||||||
def _cardStateFilters(self):
|
def _cardStateFilters(self):
|
||||||
subm = SubMenu(_("Card State"))
|
subm = SubMenu(tr(TR.BROWSING_CARD_STATE))
|
||||||
subm.addChild(
|
subm.addChild(
|
||||||
self._simpleFilters(
|
self._simpleFilters(
|
||||||
(
|
(
|
||||||
(_("New"), "is:new"),
|
(tr(TR.ACTIONS_NEW), "is:new"),
|
||||||
(_("Learning"), "is:learn"),
|
(tr(TR.SCHEDULING_LEARNING), "is:learn"),
|
||||||
(_("Review"), "is:review"),
|
(tr(TR.SCHEDULING_REVIEW), "is:review"),
|
||||||
(tr(TR.FILTERING_IS_DUE), "is:due"),
|
(tr(TR.FILTERING_IS_DUE), "is:due"),
|
||||||
None,
|
None,
|
||||||
(_("Suspended"), "is:suspended"),
|
(tr(TR.BROWSING_SUSPENDED), "is:suspended"),
|
||||||
(_("Buried"), "is:buried"),
|
(tr(TR.BROWSING_BURIED), "is:buried"),
|
||||||
None,
|
None,
|
||||||
(_("Red Flag"), "flag:1"),
|
(tr(TR.ACTIONS_RED_FLAG), "flag:1"),
|
||||||
(_("Orange Flag"), "flag:2"),
|
(tr(TR.ACTIONS_ORANGE_FLAG), "flag:2"),
|
||||||
(_("Green Flag"), "flag:3"),
|
(tr(TR.ACTIONS_GREEN_FLAG), "flag:3"),
|
||||||
(_("Blue Flag"), "flag:4"),
|
(tr(TR.ACTIONS_BLUE_FLAG), "flag:4"),
|
||||||
(_("No Flag"), "flag:0"),
|
(tr(TR.BROWSING_NO_FLAG), "flag:0"),
|
||||||
(_("Any Flag"), "-flag:0"),
|
(tr(TR.BROWSING_ANY_FLAG), "-flag:0"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -1296,9 +1297,9 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
return label.replace("&", "&&")
|
return label.replace("&", "&&")
|
||||||
|
|
||||||
def _tagFilters(self):
|
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()
|
m.addSeparator()
|
||||||
|
|
||||||
tagList = MenuList()
|
tagList = MenuList()
|
||||||
|
|
@ -1316,7 +1317,9 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
fullname = parent_prefix + node.name
|
fullname = parent_prefix + node.name
|
||||||
if node.children:
|
if node.children:
|
||||||
subm = parent.addMenu(escaped_name)
|
subm = parent.addMenu(escaped_name)
|
||||||
subm.addItem(_("Filter"), self._filterFunc("deck", fullname))
|
subm.addItem(
|
||||||
|
tr(TR.ACTIONS_FILTER), self._filterFunc("deck", fullname)
|
||||||
|
)
|
||||||
subm.addSeparator()
|
subm.addSeparator()
|
||||||
addDecks(subm, node.children, fullname + "::")
|
addDecks(subm, node.children, fullname + "::")
|
||||||
else:
|
else:
|
||||||
|
|
@ -1326,15 +1329,15 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
ml = MenuList()
|
ml = MenuList()
|
||||||
addDecks(ml, alldecks.children, "")
|
addDecks(ml, alldecks.children, "")
|
||||||
|
|
||||||
root = SubMenu(_("Decks"))
|
root = SubMenu(tr(TR.ACTIONS_DECKS))
|
||||||
root.addChild(ml.chunked())
|
root.addChild(ml.chunked())
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
|
||||||
def _noteTypeFilters(self):
|
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()
|
m.addSeparator()
|
||||||
|
|
||||||
noteTypes = MenuList()
|
noteTypes = MenuList()
|
||||||
|
|
@ -1346,14 +1349,16 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
else:
|
else:
|
||||||
subm = noteTypes.addMenu(escaped_nt_name)
|
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()
|
subm.addSeparator()
|
||||||
|
|
||||||
# add templates
|
# add templates
|
||||||
for c, tmpl in enumerate(nt["tmpls"]):
|
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: 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.
|
# 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"])
|
n=c + 1, name=self._escapeMenuItem(tmpl["name"])
|
||||||
)
|
)
|
||||||
subm.addItem(
|
subm.addItem(
|
||||||
|
|
@ -1375,9 +1380,9 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
ml.addSeparator()
|
ml.addSeparator()
|
||||||
|
|
||||||
if self._currentFilterIsSaved():
|
if self._currentFilterIsSaved():
|
||||||
ml.addItem(_("Remove Current Filter..."), self._onRemoveFilter)
|
ml.addItem(tr(TR.BROWSING_REMOVE_CURRENT_FILTER), self._onRemoveFilter)
|
||||||
else:
|
else:
|
||||||
ml.addItem(_("Save Current Filter..."), self._onSaveFilter)
|
ml.addItem(tr(TR.BROWSING_SAVE_CURRENT_FILTER), self._onSaveFilter)
|
||||||
|
|
||||||
saved = self.col.get_config("savedFilters")
|
saved = self.col.get_config("savedFilters")
|
||||||
if not saved:
|
if not saved:
|
||||||
|
|
@ -1390,7 +1395,7 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
return ml
|
return ml
|
||||||
|
|
||||||
def _onSaveFilter(self) -> None:
|
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:
|
if not name:
|
||||||
return
|
return
|
||||||
filt = self.form.searchEdit.lineEdit().text()
|
filt = self.form.searchEdit.lineEdit().text()
|
||||||
|
|
@ -1401,7 +1406,9 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
|
|
||||||
def _onRemoveFilter(self):
|
def _onRemoveFilter(self):
|
||||||
name = self._currentFilterIsSaved()
|
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
|
return
|
||||||
del self.col.conf["savedFilters"][name]
|
del self.col.conf["savedFilters"][name]
|
||||||
self.col.setMod()
|
self.col.setMod()
|
||||||
|
|
@ -1490,7 +1497,7 @@ where id in %s"""
|
||||||
% ids2str(sf)
|
% ids2str(sf)
|
||||||
)
|
)
|
||||||
if mods > 1:
|
if mods > 1:
|
||||||
showInfo(_("Please select cards from only one note type."))
|
showInfo(tr(TR.BROWSING_PLEASE_SELECT_CARDS_FROM_ONLY_ONE))
|
||||||
return
|
return
|
||||||
return sf
|
return sf
|
||||||
|
|
||||||
|
|
@ -1544,7 +1551,7 @@ where id in %s"""
|
||||||
nids = self.selectedNotes()
|
nids = self.selectedNotes()
|
||||||
if not nids:
|
if not nids:
|
||||||
return
|
return
|
||||||
self.mw.checkpoint(_("Delete Notes"))
|
self.mw.checkpoint(tr(TR.BROWSING_DELETE_NOTES))
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
# figure out where to place the cursor after the deletion
|
# figure out where to place the cursor after the deletion
|
||||||
curRow = self.form.tableView.selectionModel().currentIndex().row()
|
curRow = self.form.tableView.selectionModel().currentIndex().row()
|
||||||
|
|
@ -1590,8 +1597,8 @@ where id in %s"""
|
||||||
ret = StudyDeck(
|
ret = StudyDeck(
|
||||||
self.mw,
|
self.mw,
|
||||||
current=current,
|
current=current,
|
||||||
accept=_("Move Cards"),
|
accept=tr(TR.BROWSING_MOVE_CARDS),
|
||||||
title=_("Change Deck"),
|
title=tr(TR.BROWSING_CHANGE_DECK),
|
||||||
help="browse",
|
help="browse",
|
||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
|
|
@ -1600,10 +1607,10 @@ where id in %s"""
|
||||||
did = self.col.decks.id(ret.name)
|
did = self.col.decks.id(ret.name)
|
||||||
deck = self.col.decks.get(did)
|
deck = self.col.decks.get(did)
|
||||||
if deck["dyn"]:
|
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
|
return
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
self.mw.checkpoint(_("Change Deck"))
|
self.mw.checkpoint(tr(TR.BROWSING_CHANGE_DECK))
|
||||||
self.col.set_deck(cids, did)
|
self.col.set_deck(cids, did)
|
||||||
self.model.endReset()
|
self.model.endReset()
|
||||||
self.mw.requireReset(reason=ResetReason.BrowserSetDeck, context=self)
|
self.mw.requireReset(reason=ResetReason.BrowserSetDeck, context=self)
|
||||||
|
|
@ -1616,7 +1623,7 @@ where id in %s"""
|
||||||
|
|
||||||
def _addTags(self, tags, label, prompt, func):
|
def _addTags(self, tags, label, prompt, func):
|
||||||
if prompt is None:
|
if prompt is None:
|
||||||
prompt = _("Enter tags to add:")
|
prompt = tr(TR.BROWSING_ENTER_TAGS_TO_ADD)
|
||||||
if tags is None:
|
if tags is None:
|
||||||
(tags, r) = getTag(self, self.col, prompt)
|
(tags, r) = getTag(self, self.col, prompt)
|
||||||
else:
|
else:
|
||||||
|
|
@ -1626,7 +1633,7 @@ where id in %s"""
|
||||||
if func is None:
|
if func is None:
|
||||||
func = self.col.tags.bulkAdd
|
func = self.col.tags.bulkAdd
|
||||||
if label is None:
|
if label is None:
|
||||||
label = _("Add Tags")
|
label = tr(TR.BROWSING_ADD_TAGS)
|
||||||
if label:
|
if label:
|
||||||
self.mw.checkpoint(label)
|
self.mw.checkpoint(label)
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
|
|
@ -1636,9 +1643,12 @@ where id in %s"""
|
||||||
|
|
||||||
def deleteTags(self, tags=None, label=None):
|
def deleteTags(self, tags=None, label=None):
|
||||||
if label is None:
|
if label is None:
|
||||||
label = _("Delete Tags")
|
label = tr(TR.BROWSING_DELETE_TAGS)
|
||||||
self.addTags(
|
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):
|
def clearUnusedTags(self):
|
||||||
|
|
@ -1731,7 +1741,7 @@ where id in %s"""
|
||||||
+ ids2str(cids)
|
+ ids2str(cids)
|
||||||
)
|
)
|
||||||
if not cids2:
|
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 = QDialog(self)
|
||||||
d.setWindowModality(Qt.WindowModal)
|
d.setWindowModality(Qt.WindowModal)
|
||||||
frm = aqt.forms.reposition.Ui_Dialog()
|
frm = aqt.forms.reposition.Ui_Dialog()
|
||||||
|
|
@ -1741,14 +1751,14 @@ where id in %s"""
|
||||||
)
|
)
|
||||||
pmin = pmin or 0
|
pmin = pmin or 0
|
||||||
pmax = pmax or 0
|
pmax = pmax or 0
|
||||||
txt = _("Queue top: %d") % pmin
|
txt = tr(TR.BROWSING_QUEUE_TOP, val="%s") % pmin
|
||||||
txt += "\n" + _("Queue bottom: %d") % pmax
|
txt += "\n" + tr(TR.BROWSING_QUEUE_BOTTOM, val="%s") % pmax
|
||||||
frm.label.setText(txt)
|
frm.label.setText(txt)
|
||||||
frm.start.selectAll()
|
frm.start.selectAll()
|
||||||
if not d.exec_():
|
if not d.exec_():
|
||||||
return
|
return
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
self.mw.checkpoint(_("Reposition"))
|
self.mw.checkpoint(tr(TR.ACTIONS_REPOSITION))
|
||||||
self.col.sched.sortCards(
|
self.col.sched.sortCards(
|
||||||
cids,
|
cids,
|
||||||
start=frm.start.value(),
|
start=frm.start.value(),
|
||||||
|
|
@ -1774,7 +1784,7 @@ where id in %s"""
|
||||||
if not d.exec_():
|
if not d.exec_():
|
||||||
return
|
return
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
self.mw.checkpoint(_("Reschedule"))
|
self.mw.checkpoint(tr(TR.BROWSING_RESCHEDULE))
|
||||||
if frm.asNew.isChecked():
|
if frm.asNew.isChecked():
|
||||||
self.col.sched.forgetCards(self.selectedCards())
|
self.col.sched.forgetCards(self.selectedCards())
|
||||||
else:
|
else:
|
||||||
|
|
@ -1881,7 +1891,7 @@ where id in %s"""
|
||||||
restore_is_checked(frm.ignoreCase, combo + "ignoreCase")
|
restore_is_checked(frm.ignoreCase, combo + "ignoreCase")
|
||||||
|
|
||||||
frm.find.setFocus()
|
frm.find.setFocus()
|
||||||
allfields = [_("All Fields")] + fields
|
allfields = [tr(TR.BROWSING_ALL_FIELDS)] + fields
|
||||||
frm.field.addItems(allfields)
|
frm.field.addItems(allfields)
|
||||||
restore_combo_index_for_session(frm.field, allfields, combo + "Field")
|
restore_combo_index_for_session(frm.field, allfields, combo + "Field")
|
||||||
qconnect(frm.buttonBox.helpRequested, self.onFindReplaceHelp)
|
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.re, combo + "Regex")
|
||||||
save_is_checked(frm.ignoreCase, combo + "ignoreCase")
|
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
|
# starts progress dialog as well
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
|
|
||||||
|
|
@ -1976,7 +1986,9 @@ where id in %s"""
|
||||||
field = fields[frm.fields.currentIndex()]
|
field = fields[frm.fields.currentIndex()]
|
||||||
self.duplicatesReport(frm.webView, field, search_text, frm, web_context)
|
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)
|
qconnect(search.clicked, onClick)
|
||||||
d.show()
|
d.show()
|
||||||
|
|
||||||
|
|
@ -1985,7 +1997,7 @@ where id in %s"""
|
||||||
res = self.mw.col.findDupes(fname, search)
|
res = self.mw.col.findDupes(fname, search)
|
||||||
if not self._dupesButton:
|
if not self._dupesButton:
|
||||||
self._dupesButton = b = frm.buttonBox.addButton(
|
self._dupesButton = b = frm.buttonBox.addButton(
|
||||||
_("Tag Duplicates"), QDialogButtonBox.ActionRole
|
tr(TR.BROWSING_TAG_DUPLICATES), QDialogButtonBox.ActionRole
|
||||||
)
|
)
|
||||||
qconnect(b.clicked, lambda: self._onTagDupes(res))
|
qconnect(b.clicked, lambda: self._onTagDupes(res))
|
||||||
t = ""
|
t = ""
|
||||||
|
|
@ -1993,7 +2005,7 @@ where id in %s"""
|
||||||
notes = sum(len(r[1]) for r in res)
|
notes = sum(len(r[1]) for r in res)
|
||||||
part1 = ngettext("%d group", "%d groups", groups) % groups
|
part1 = ngettext("%d group", "%d groups", groups) % groups
|
||||||
part2 = ngettext("%d note", "%d notes", notes) % notes
|
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>"
|
t += "<p><ol>"
|
||||||
for val, nids in res:
|
for val, nids in res:
|
||||||
t += (
|
t += (
|
||||||
|
|
@ -2012,15 +2024,15 @@ where id in %s"""
|
||||||
if not res:
|
if not res:
|
||||||
return
|
return
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
self.mw.checkpoint(_("Tag Duplicates"))
|
self.mw.checkpoint(tr(TR.BROWSING_TAG_DUPLICATES))
|
||||||
nids = set()
|
nids = set()
|
||||||
for s, nidlist in res:
|
for s, nidlist in res:
|
||||||
nids.update(nidlist)
|
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.mw.progress.finish()
|
||||||
self.model.endReset()
|
self.model.endReset()
|
||||||
self.mw.requireReset(reason=ResetReason.BrowserTagDupes, context=self)
|
self.mw.requireReset(reason=ResetReason.BrowserTagDupes, context=self)
|
||||||
tooltip(_("Notes tagged."))
|
tooltip(tr(TR.BROWSING_NOTES_TAGGED))
|
||||||
|
|
||||||
def dupeLinkClicked(self, link):
|
def dupeLinkClicked(self, link):
|
||||||
self.search_for(link)
|
self.search_for(link)
|
||||||
|
|
@ -2177,10 +2189,10 @@ class ChangeModel(QDialog):
|
||||||
map = QWidget()
|
map = QWidget()
|
||||||
l = QGridLayout()
|
l = QGridLayout()
|
||||||
combos = []
|
combos = []
|
||||||
targets = [x["name"] for x in dst] + [_("Nothing")]
|
targets = [x["name"] for x in dst] + [tr(TR.BROWSING_NOTHING)]
|
||||||
indices = {}
|
indices = {}
|
||||||
for i, x in enumerate(src):
|
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 = QComboBox()
|
||||||
cb.addItems(targets)
|
cb.addItems(targets)
|
||||||
idx = min(i, len(targets) - 1)
|
idx = min(i, len(targets) - 1)
|
||||||
|
|
@ -2267,7 +2279,7 @@ Are you sure you want to continue?"""
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
self.browser.mw.checkpoint(_("Change Note Type"))
|
self.browser.mw.checkpoint(tr(TR.BROWSING_CHANGE_NOTE_TYPE))
|
||||||
b = self.browser
|
b = self.browser
|
||||||
b.mw.col.modSchema(check=True)
|
b.mw.col.modSchema(check=True)
|
||||||
b.mw.progress.start()
|
b.mw.progress.start()
|
||||||
|
|
|
||||||
|
|
@ -60,13 +60,15 @@ class CardLayout(QDialog):
|
||||||
self.mobile_emulation_enabled = False
|
self.mobile_emulation_enabled = False
|
||||||
self.have_autoplayed = False
|
self.have_autoplayed = False
|
||||||
self.mm._remove_from_cache(self.model["id"])
|
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.change_tracker = ChangeTracker(self.mw)
|
||||||
self.setupTopArea()
|
self.setupTopArea()
|
||||||
self.setupMainArea()
|
self.setupMainArea()
|
||||||
self.setupButtons()
|
self.setupButtons()
|
||||||
self.setupShortcuts()
|
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 = QVBoxLayout()
|
||||||
v1.addWidget(self.topArea)
|
v1.addWidget(self.topArea)
|
||||||
v1.addWidget(self.mainArea)
|
v1.addWidget(self.mainArea)
|
||||||
|
|
@ -108,7 +110,9 @@ class CardLayout(QDialog):
|
||||||
self.topArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
|
self.topArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
|
||||||
self.topAreaForm = aqt.forms.clayout_top.Ui_Form()
|
self.topAreaForm = aqt.forms.clayout_top.Ui_Form()
|
||||||
self.topAreaForm.setupUi(self.topArea)
|
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.templateOptions.clicked, self.onMore)
|
||||||
qconnect(
|
qconnect(
|
||||||
self.topAreaForm.templatesBox.currentIndexChanged,
|
self.topAreaForm.templatesBox.currentIndexChanged,
|
||||||
|
|
@ -240,7 +244,7 @@ class CardLayout(QDialog):
|
||||||
qconnect(widg.returnPressed, self.on_search_next)
|
qconnect(widg.returnPressed, self.on_search_next)
|
||||||
|
|
||||||
def setup_cloze_number_box(self):
|
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)
|
self.pform.cloze_number_combo.addItems(names)
|
||||||
try:
|
try:
|
||||||
idx = self.cloze_numbers.index(self.ord + 1)
|
idx = self.cloze_numbers.index(self.ord + 1)
|
||||||
|
|
@ -381,28 +385,28 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
def setupButtons(self):
|
def setupButtons(self):
|
||||||
l = self.buttons = QHBoxLayout()
|
l = self.buttons = QHBoxLayout()
|
||||||
help = QPushButton(_("Help"))
|
help = QPushButton(tr(TR.ACTIONS_HELP))
|
||||||
help.setAutoDefault(False)
|
help.setAutoDefault(False)
|
||||||
l.addWidget(help)
|
l.addWidget(help)
|
||||||
qconnect(help.clicked, self.onHelp)
|
qconnect(help.clicked, self.onHelp)
|
||||||
l.addStretch()
|
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)
|
self.add_field_button.setAutoDefault(False)
|
||||||
l.addWidget(self.add_field_button)
|
l.addWidget(self.add_field_button)
|
||||||
qconnect(self.add_field_button.clicked, self.onAddField)
|
qconnect(self.add_field_button.clicked, self.onAddField)
|
||||||
if not self._isCloze():
|
if not self._isCloze():
|
||||||
flip = QPushButton(_("Flip"))
|
flip = QPushButton(tr(TR.CARD_TEMPLATES_FLIP))
|
||||||
flip.setAutoDefault(False)
|
flip.setAutoDefault(False)
|
||||||
l.addWidget(flip)
|
l.addWidget(flip)
|
||||||
qconnect(flip.clicked, self.onFlip)
|
qconnect(flip.clicked, self.onFlip)
|
||||||
l.addStretch()
|
l.addStretch()
|
||||||
save = QPushButton(_("Save"))
|
save = QPushButton(tr(TR.ACTIONS_SAVE))
|
||||||
save.setAutoDefault(False)
|
save.setAutoDefault(False)
|
||||||
save.setShortcut(QKeySequence("Ctrl+Return"))
|
save.setShortcut(QKeySequence("Ctrl+Return"))
|
||||||
l.addWidget(save)
|
l.addWidget(save)
|
||||||
qconnect(save.clicked, self.accept)
|
qconnect(save.clicked, self.accept)
|
||||||
|
|
||||||
close = QPushButton(_("Cancel"))
|
close = QPushButton(tr(TR.ACTIONS_CANCEL))
|
||||||
close.setAutoDefault(False)
|
close.setAutoDefault(False)
|
||||||
l.addWidget(close)
|
l.addWidget(close)
|
||||||
qconnect(close.clicked, self.reject)
|
qconnect(close.clicked, self.reject)
|
||||||
|
|
@ -548,7 +552,7 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
def onRemove(self):
|
def onRemove(self):
|
||||||
if len(self.templates) < 2:
|
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():
|
def get_count():
|
||||||
return self.mm.template_use_count(self.model["id"], self.ord)
|
return self.mm.template_use_count(self.model["id"], self.ord)
|
||||||
|
|
@ -558,7 +562,7 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
template = self.current_template()
|
template = self.current_template()
|
||||||
cards = ngettext("%d card", "%d cards", card_cnt) % card_cnt
|
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
|
a=template["name"], b=cards
|
||||||
)
|
)
|
||||||
if not askUser(msg):
|
if not askUser(msg):
|
||||||
|
|
@ -583,7 +587,9 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
def onRename(self):
|
def onRename(self):
|
||||||
template = self.current_template()
|
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():
|
if not name.strip():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -597,7 +603,8 @@ class CardLayout(QDialog):
|
||||||
template = self.current_template()
|
template = self.current_template()
|
||||||
current_pos = self.templates.index(template) + 1
|
current_pos = self.templates.index(template) + 1
|
||||||
pos = getOnlyText(
|
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:
|
if not pos:
|
||||||
return
|
return
|
||||||
|
|
@ -619,7 +626,7 @@ class CardLayout(QDialog):
|
||||||
def _newCardName(self):
|
def _newCardName(self):
|
||||||
n = len(self.templates) + 1
|
n = len(self.templates) + 1
|
||||||
while 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]:
|
if name not in [t["name"] for t in self.templates]:
|
||||||
break
|
break
|
||||||
n += 1
|
n += 1
|
||||||
|
|
@ -673,29 +680,29 @@ adjust the template manually to switch the question and answer."""
|
||||||
m = QMenu(self)
|
m = QMenu(self)
|
||||||
|
|
||||||
if not self._isCloze():
|
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)
|
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)
|
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)
|
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)
|
qconnect(a.triggered, self.onReorder)
|
||||||
|
|
||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
|
|
||||||
t = self.current_template()
|
t = self.current_template()
|
||||||
if t["did"]:
|
if t["did"]:
|
||||||
s = _(" (on)")
|
s = tr(TR.CARD_TEMPLATES_ON)
|
||||||
else:
|
else:
|
||||||
s = _(" (off)")
|
s = tr(TR.CARD_TEMPLATES_OFF)
|
||||||
a = m.addAction(_("Deck Override...") + s)
|
a = m.addAction(tr(TR.CARD_TEMPLATES_DECK_OVERRIDE) + s)
|
||||||
qconnect(a.triggered, self.onTargetDeck)
|
qconnect(a.triggered, self.onTargetDeck)
|
||||||
|
|
||||||
a = m.addAction(_("Browser Appearance..."))
|
a = m.addAction(tr(TR.CARD_TEMPLATES_BROWSER_APPEARANCE))
|
||||||
qconnect(a.triggered, self.onBrowserDisplay)
|
qconnect(a.triggered, self.onBrowserDisplay)
|
||||||
|
|
||||||
m.exec_(self.topAreaForm.templateOptions.mapToGlobal(QPoint(0, 0)))
|
m.exec_(self.topAreaForm.templateOptions.mapToGlobal(QPoint(0, 0)))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
|
@ -50,11 +49,11 @@ class CustomStudy(QDialog):
|
||||||
smin = 1
|
smin = 1
|
||||||
smax = DYN_MAX_SIZE
|
smax = DYN_MAX_SIZE
|
||||||
sval = 1
|
sval = 1
|
||||||
post = _("cards")
|
post = tr(TR.CUSTOM_STUDY_CARDS)
|
||||||
tit = ""
|
tit = ""
|
||||||
spShow = True
|
spShow = True
|
||||||
typeShow = False
|
typeShow = False
|
||||||
ok = _("OK")
|
ok = tr(TR.CUSTOM_STUDY_OK)
|
||||||
|
|
||||||
def plus(num):
|
def plus(num):
|
||||||
if num == 1000:
|
if num == 1000:
|
||||||
|
|
@ -68,8 +67,10 @@ class CustomStudy(QDialog):
|
||||||
new, self.conf["new"]["perDay"] - self.deck["newToday"][1]
|
new, self.conf["new"]["perDay"] - self.deck["newToday"][1]
|
||||||
)
|
)
|
||||||
newExceeding = min(new, new - newUnderLearning)
|
newExceeding = min(new, new - newUnderLearning)
|
||||||
tit = _("New cards in deck over today limit: %s") % plus(newExceeding)
|
tit = tr(TR.CUSTOM_STUDY_NEW_CARDS_IN_DECK_OVER_TODAY, val="%s") % plus(
|
||||||
pre = _("Increase today's new card limit by")
|
newExceeding
|
||||||
|
)
|
||||||
|
pre = tr(TR.CUSTOM_STUDY_INCREASE_TODAYS_NEW_CARD_LIMIT_BY)
|
||||||
sval = min(new, self.deck.get("extendNew", 10))
|
sval = min(new, self.deck.get("extendNew", 10))
|
||||||
smin = -DYN_MAX_SIZE
|
smin = -DYN_MAX_SIZE
|
||||||
smax = newExceeding
|
smax = newExceeding
|
||||||
|
|
@ -80,27 +81,29 @@ class CustomStudy(QDialog):
|
||||||
rev, self.conf["rev"]["perDay"] - self.deck["revToday"][1]
|
rev, self.conf["rev"]["perDay"] - self.deck["revToday"][1]
|
||||||
)
|
)
|
||||||
revExceeding = min(rev, rev - revUnderLearning)
|
revExceeding = min(rev, rev - revUnderLearning)
|
||||||
tit = _("Reviews due in deck over today limit: %s") % plus(revExceeding)
|
tit = tr(TR.CUSTOM_STUDY_REVIEWS_DUE_IN_DECK_OVER_TODAY, val="%s") % plus(
|
||||||
pre = _("Increase today's review limit by")
|
revExceeding
|
||||||
|
)
|
||||||
|
pre = tr(TR.CUSTOM_STUDY_INCREASE_TODAYS_REVIEW_LIMIT_BY)
|
||||||
sval = min(rev, self.deck.get("extendRev", 10))
|
sval = min(rev, self.deck.get("extendRev", 10))
|
||||||
smin = -DYN_MAX_SIZE
|
smin = -DYN_MAX_SIZE
|
||||||
smax = revExceeding
|
smax = revExceeding
|
||||||
elif idx == RADIO_FORGOT:
|
elif idx == RADIO_FORGOT:
|
||||||
pre = _("Review cards forgotten in last")
|
pre = tr(TR.CUSTOM_STUDY_REVIEW_CARDS_FORGOTTEN_IN_LAST)
|
||||||
post = _("days")
|
post = tr(TR.SCHEDULING_DAYS)
|
||||||
smax = 30
|
smax = 30
|
||||||
elif idx == RADIO_AHEAD:
|
elif idx == RADIO_AHEAD:
|
||||||
pre = _("Review ahead by")
|
pre = tr(TR.CUSTOM_STUDY_REVIEW_AHEAD_BY)
|
||||||
post = _("days")
|
post = tr(TR.SCHEDULING_DAYS)
|
||||||
elif idx == RADIO_PREVIEW:
|
elif idx == RADIO_PREVIEW:
|
||||||
pre = _("Preview new cards added in the last")
|
pre = tr(TR.CUSTOM_STUDY_PREVIEW_NEW_CARDS_ADDED_IN_THE)
|
||||||
post = _("days")
|
post = tr(TR.SCHEDULING_DAYS)
|
||||||
sval = 1
|
sval = 1
|
||||||
elif idx == RADIO_CRAM:
|
elif idx == RADIO_CRAM:
|
||||||
pre = _("Select")
|
pre = tr(TR.CUSTOM_STUDY_SELECT)
|
||||||
post = _("cards from the deck")
|
post = tr(TR.CUSTOM_STUDY_CARDS_FROM_THE_DECK)
|
||||||
# tit = _("After pressing OK, you can choose which tags to include.")
|
# tit = _("After pressing OK, you can choose which tags to include.")
|
||||||
ok = _("Choose Tags")
|
ok = tr(TR.CUSTOM_STUDY_CHOOSE_TAGS)
|
||||||
sval = 100
|
sval = 100
|
||||||
typeShow = True
|
typeShow = True
|
||||||
sp.setVisible(spShow)
|
sp.setVisible(spShow)
|
||||||
|
|
@ -138,7 +141,7 @@ class CustomStudy(QDialog):
|
||||||
elif i == RADIO_CRAM:
|
elif i == RADIO_CRAM:
|
||||||
tags = self._getTags()
|
tags = self._getTags()
|
||||||
# the rest create a filtered deck
|
# 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 cur:
|
||||||
if not cur["dyn"]:
|
if not cur["dyn"]:
|
||||||
showInfo(tr(TR.CUSTOM_STUDY_MUST_RENAME_DECK))
|
showInfo(tr(TR.CUSTOM_STUDY_MUST_RENAME_DECK))
|
||||||
|
|
@ -150,7 +153,9 @@ class CustomStudy(QDialog):
|
||||||
dyn = cur
|
dyn = cur
|
||||||
self.mw.col.decks.select(cur["id"])
|
self.mw.col.decks.select(cur["id"])
|
||||||
else:
|
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)
|
dyn = self.mw.col.decks.get(did)
|
||||||
# and then set various options
|
# and then set various options
|
||||||
if i == RADIO_FORGOT:
|
if i == RADIO_FORGOT:
|
||||||
|
|
@ -187,7 +192,7 @@ class CustomStudy(QDialog):
|
||||||
# generate cards
|
# generate cards
|
||||||
self.created_custom_study = True
|
self.created_custom_study = True
|
||||||
if not self.mw.col.sched.rebuild_filtered_deck(dyn["id"]):
|
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")
|
self.mw.moveToState("overview")
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
@ -16,7 +15,7 @@ from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
from aqt.toolbar import BottomBar
|
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:
|
class DeckBrowserBottomBar:
|
||||||
|
|
@ -79,7 +78,7 @@ class DeckBrowser:
|
||||||
elif cmd == "import":
|
elif cmd == "import":
|
||||||
self.mw.onImport()
|
self.mw.onImport()
|
||||||
elif cmd == "create":
|
elif cmd == "create":
|
||||||
deck = getOnlyText(_("Name for deck:"))
|
deck = getOnlyText(tr(TR.DECKS_NAME_FOR_DECK))
|
||||||
if deck:
|
if deck:
|
||||||
self.mw.col.decks.id(deck)
|
self.mw.col.decks.id(deck)
|
||||||
gui_hooks.sidebar_should_refresh_decks()
|
gui_hooks.sidebar_should_refresh_decks()
|
||||||
|
|
@ -144,9 +143,9 @@ class DeckBrowser:
|
||||||
buf = """
|
buf = """
|
||||||
<tr><th colspan=5 align=start>%s</th><th class=count>%s</th>
|
<tr><th colspan=5 align=start>%s</th><th class=count>%s</th>
|
||||||
<th class=count>%s</th><th class=optscol></th></tr>""" % (
|
<th class=count>%s</th><th class=optscol></th></tr>""" % (
|
||||||
_("Deck"),
|
tr(TR.DECKS_DECK),
|
||||||
tr(TR.STATISTICS_DUE_COUNT),
|
tr(TR.STATISTICS_DUE_COUNT),
|
||||||
_("New"),
|
tr(TR.ACTIONS_NEW),
|
||||||
)
|
)
|
||||||
buf += self._topLevelDragRow()
|
buf += self._topLevelDragRow()
|
||||||
|
|
||||||
|
|
@ -225,13 +224,13 @@ class DeckBrowser:
|
||||||
|
|
||||||
def _showOptions(self, did: str) -> None:
|
def _showOptions(self, did: str) -> None:
|
||||||
m = QMenu(self.mw)
|
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)))
|
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))
|
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))
|
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)))
|
qconnect(a.triggered, lambda b, did=did: self._delete(int(did)))
|
||||||
gui_hooks.deck_browser_will_show_options_menu(m, int(did))
|
gui_hooks.deck_browser_will_show_options_menu(m, int(did))
|
||||||
m.exec_(QCursor.pos())
|
m.exec_(QCursor.pos())
|
||||||
|
|
@ -240,10 +239,10 @@ class DeckBrowser:
|
||||||
self.mw.onExport(did=did)
|
self.mw.onExport(did=did)
|
||||||
|
|
||||||
def _rename(self, did: int) -> None:
|
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)
|
deck = self.mw.col.decks.get(did)
|
||||||
oldName = deck["name"]
|
oldName = deck["name"]
|
||||||
newName = getOnlyText(_("New deck name:"), default=oldName)
|
newName = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=oldName)
|
||||||
newName = newName.replace('"', "")
|
newName = newName.replace('"', "")
|
||||||
if not newName or newName == oldName:
|
if not newName or newName == oldName:
|
||||||
return
|
return
|
||||||
|
|
@ -277,7 +276,7 @@ class DeckBrowser:
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def _delete(self, did):
|
def _delete(self, did):
|
||||||
self.mw.checkpoint(_("Delete Deck"))
|
self.mw.checkpoint(tr(TR.DECKS_DELETE_DECK))
|
||||||
deck = self.mw.col.decks.get(did)
|
deck = self.mw.col.decks.get(did)
|
||||||
if not deck["dyn"]:
|
if not deck["dyn"]:
|
||||||
dids = [did] + [r[1] for r in self.mw.col.decks.children(did)]
|
dids = [did] + [r[1] for r in self.mw.col.decks.children(did)]
|
||||||
|
|
@ -293,7 +292,7 @@ class DeckBrowser:
|
||||||
deck["dyn"]
|
deck["dyn"]
|
||||||
or not extra
|
or not extra
|
||||||
or askUser(
|
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()
|
self.mw.progress.start()
|
||||||
|
|
@ -305,9 +304,9 @@ class DeckBrowser:
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
drawLinks = [
|
drawLinks = [
|
||||||
["", "shared", _("Get Shared")],
|
["", "shared", tr(TR.DECKS_GET_SHARED)],
|
||||||
["", "create", _("Create Deck")],
|
["", "create", tr(TR.DECKS_CREATE_DECK)],
|
||||||
["Ctrl+Shift+I", "import", _("Import File")],
|
["Ctrl+Shift+I", "import", tr(TR.DECKS_IMPORT_FILE)],
|
||||||
]
|
]
|
||||||
|
|
||||||
def _drawButtons(self):
|
def _drawButtons(self):
|
||||||
|
|
@ -315,7 +314,7 @@ class DeckBrowser:
|
||||||
drawLinks = deepcopy(self.drawLinks)
|
drawLinks = deepcopy(self.drawLinks)
|
||||||
for b in drawLinks:
|
for b in drawLinks:
|
||||||
if b[0]:
|
if b[0]:
|
||||||
b[0] = _("Shortcut key: %s") % shortcut(b[0])
|
b[0] = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % shortcut(b[0])
|
||||||
buf += """
|
buf += """
|
||||||
<button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(
|
<button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(
|
||||||
b
|
b
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import shortcut
|
from aqt.utils import TR, shortcut, tr
|
||||||
|
|
||||||
|
|
||||||
class DeckChooser(QHBoxLayout):
|
class DeckChooser(QHBoxLayout):
|
||||||
|
|
@ -26,12 +25,12 @@ class DeckChooser(QHBoxLayout):
|
||||||
|
|
||||||
def setupDecks(self) -> None:
|
def setupDecks(self) -> None:
|
||||||
if self.label:
|
if self.label:
|
||||||
self.deckLabel = QLabel(_("Deck"))
|
self.deckLabel = QLabel(tr(TR.DECKS_DECK))
|
||||||
self.addWidget(self.deckLabel)
|
self.addWidget(self.deckLabel)
|
||||||
# decks box
|
# decks box
|
||||||
self.deck = QPushButton(clicked=self.onDeckChange) # type: ignore
|
self.deck = QPushButton(clicked=self.onDeckChange) # type: ignore
|
||||||
self.deck.setAutoDefault(False)
|
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
|
QShortcut(QKeySequence("Ctrl+D"), self.widget, activated=self.onDeckChange) # type: ignore
|
||||||
self.addWidget(self.deck)
|
self.addWidget(self.deck)
|
||||||
# starting label
|
# starting label
|
||||||
|
|
@ -48,11 +47,13 @@ class DeckChooser(QHBoxLayout):
|
||||||
did = c.odid
|
did = c.odid
|
||||||
else:
|
else:
|
||||||
did = 1
|
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:
|
else:
|
||||||
self.setDeckName(
|
self.setDeckName(
|
||||||
self.mw.col.decks.nameOrNone(self.mw.col.models.current()["did"])
|
self.mw.col.decks.nameOrNone(self.mw.col.models.current()["did"])
|
||||||
or _("Default")
|
or tr(TR.QT_MISC_DEFAULT)
|
||||||
)
|
)
|
||||||
# layout
|
# layout
|
||||||
sizePolicy = QSizePolicy(QSizePolicy.Policy(7), QSizePolicy.Policy(0))
|
sizePolicy = QSizePolicy(QSizePolicy.Policy(7), QSizePolicy.Policy(0))
|
||||||
|
|
@ -74,7 +75,7 @@ class DeckChooser(QHBoxLayout):
|
||||||
if not self.mw.col.conf.get("addToCur", True):
|
if not self.mw.col.conf.get("addToCur", True):
|
||||||
self.setDeckName(
|
self.setDeckName(
|
||||||
self.mw.col.decks.nameOrNone(self.mw.col.models.current()["did"])
|
self.mw.col.decks.nameOrNone(self.mw.col.models.current()["did"])
|
||||||
or _("Default")
|
or tr(TR.QT_MISC_DEFAULT)
|
||||||
)
|
)
|
||||||
|
|
||||||
def onDeckChange(self) -> None:
|
def onDeckChange(self) -> None:
|
||||||
|
|
@ -84,8 +85,8 @@ class DeckChooser(QHBoxLayout):
|
||||||
ret = StudyDeck(
|
ret = StudyDeck(
|
||||||
self.mw,
|
self.mw,
|
||||||
current=current,
|
current=current,
|
||||||
accept=_("Choose"),
|
accept=tr(TR.ACTIONS_CHOOSE),
|
||||||
title=_("Choose Deck"),
|
title=tr(TR.QT_MISC_CHOOSE_DECK),
|
||||||
help="addingnotes",
|
help="addingnotes",
|
||||||
cancel=False,
|
cancel=False,
|
||||||
parent=self.widget,
|
parent=self.widget,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
@ -13,6 +12,7 @@ from anki.lang import _, ngettext
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
TR,
|
||||||
askUser,
|
askUser,
|
||||||
getOnlyText,
|
getOnlyText,
|
||||||
openHelp,
|
openHelp,
|
||||||
|
|
@ -21,6 +21,7 @@ from aqt.utils import (
|
||||||
showInfo,
|
showInfo,
|
||||||
showWarning,
|
showWarning,
|
||||||
tooltip,
|
tooltip,
|
||||||
|
tr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ class DeckConf(QDialog):
|
||||||
self.form = aqt.forms.dconf.Ui_Dialog()
|
self.form = aqt.forms.dconf.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
gui_hooks.deck_conf_did_setup_ui_form(self)
|
gui_hooks.deck_conf_did_setup_ui_form(self)
|
||||||
self.mw.checkpoint(_("Options"))
|
self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS))
|
||||||
self.setupCombos()
|
self.setupCombos()
|
||||||
self.setupConfs()
|
self.setupConfs()
|
||||||
self.setWindowModality(Qt.WindowModal)
|
self.setWindowModality(Qt.WindowModal)
|
||||||
|
|
@ -44,7 +45,7 @@ class DeckConf(QDialog):
|
||||||
self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked,
|
self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked,
|
||||||
self.onRestore,
|
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
|
# qt doesn't size properly with altered fonts otherwise
|
||||||
restoreGeom(self, "deckconf", adjustSize=True)
|
restoreGeom(self, "deckconf", adjustSize=True)
|
||||||
gui_hooks.deck_conf_will_show(self)
|
gui_hooks.deck_conf_will_show(self)
|
||||||
|
|
@ -86,13 +87,13 @@ class DeckConf(QDialog):
|
||||||
|
|
||||||
def confOpts(self):
|
def confOpts(self):
|
||||||
m = QMenu(self.mw)
|
m = QMenu(self.mw)
|
||||||
a = m.addAction(_("Add"))
|
a = m.addAction(tr(TR.ACTIONS_ADD))
|
||||||
qconnect(a.triggered, self.addGroup)
|
qconnect(a.triggered, self.addGroup)
|
||||||
a = m.addAction(_("Delete"))
|
a = m.addAction(tr(TR.ACTIONS_DELETE))
|
||||||
qconnect(a.triggered, self.remGroup)
|
qconnect(a.triggered, self.remGroup)
|
||||||
a = m.addAction(_("Rename"))
|
a = m.addAction(tr(TR.ACTIONS_RENAME))
|
||||||
qconnect(a.triggered, self.renameGroup)
|
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)
|
qconnect(a.triggered, self.setChildren)
|
||||||
if not self.childDids:
|
if not self.childDids:
|
||||||
a.setEnabled(False)
|
a.setEnabled(False)
|
||||||
|
|
@ -118,7 +119,7 @@ class DeckConf(QDialog):
|
||||||
self.form.count.setText(txt)
|
self.form.count.setText(txt)
|
||||||
|
|
||||||
def addGroup(self) -> None:
|
def addGroup(self) -> None:
|
||||||
name = getOnlyText(_("New options group name:"))
|
name = getOnlyText(tr(TR.SCHEDULING_NEW_OPTIONS_GROUP_NAME))
|
||||||
if not name:
|
if not name:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -134,7 +135,7 @@ class DeckConf(QDialog):
|
||||||
|
|
||||||
def remGroup(self) -> None:
|
def remGroup(self) -> None:
|
||||||
if int(self.conf["id"]) == 1:
|
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:
|
else:
|
||||||
gui_hooks.deck_conf_will_remove_config(self, self.deck, self.conf)
|
gui_hooks.deck_conf_will_remove_config(self, self.deck, self.conf)
|
||||||
self.mw.col.modSchema(check=True)
|
self.mw.col.modSchema(check=True)
|
||||||
|
|
@ -145,7 +146,7 @@ class DeckConf(QDialog):
|
||||||
|
|
||||||
def renameGroup(self) -> None:
|
def renameGroup(self) -> None:
|
||||||
old = self.conf["name"]
|
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:
|
if not name or name == old:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -156,7 +157,7 @@ class DeckConf(QDialog):
|
||||||
|
|
||||||
def setChildren(self):
|
def setChildren(self):
|
||||||
if not askUser(
|
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
|
return
|
||||||
for did in self.childDids:
|
for did in self.childDids:
|
||||||
|
|
@ -194,7 +195,7 @@ class DeckConf(QDialog):
|
||||||
lim = x
|
lim = x
|
||||||
else:
|
else:
|
||||||
lim = min(x, lim)
|
lim = min(x, lim)
|
||||||
return _("(parent limit: %d)") % lim
|
return tr(TR.SCHEDULING_PARENT_LIMIT, val="%s") % lim
|
||||||
|
|
||||||
def loadConf(self):
|
def loadConf(self):
|
||||||
self.conf = self.mw.col.decks.confForDid(self.deck["id"])
|
self.conf = self.mw.col.decks.confForDid(self.deck["id"])
|
||||||
|
|
@ -273,10 +274,10 @@ class DeckConf(QDialog):
|
||||||
ret.append(i)
|
ret.append(i)
|
||||||
except:
|
except:
|
||||||
# invalid, don't update
|
# invalid, don't update
|
||||||
showWarning(_("Steps must be numbers."))
|
showWarning(tr(TR.SCHEDULING_STEPS_MUST_BE_NUMBERS))
|
||||||
return
|
return
|
||||||
if len(ret) < minSize:
|
if len(ret) < minSize:
|
||||||
showWarning(_("At least one step is required."))
|
showWarning(tr(TR.SCHEDULING_AT_LEAST_ONE_STEP_IS_REQUIRED))
|
||||||
return
|
return
|
||||||
conf[key] = ret
|
conf[key] = ret
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from typing import List, Optional
|
||||||
import aqt
|
import aqt
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt.qt 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):
|
class DeckConf(QDialog):
|
||||||
|
|
@ -18,14 +18,14 @@ class DeckConf(QDialog):
|
||||||
self.form = aqt.forms.dyndconf.Ui_Dialog()
|
self.form = aqt.forms.dyndconf.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
if first:
|
if first:
|
||||||
label = _("Build")
|
label = tr(TR.DECKS_BUILD)
|
||||||
else:
|
else:
|
||||||
label = _("Rebuild")
|
label = tr(TR.ACTIONS_REBUILD)
|
||||||
self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole)
|
self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole)
|
||||||
self.mw.checkpoint(_("Options"))
|
self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS))
|
||||||
self.setWindowModality(Qt.WindowModal)
|
self.setWindowModality(Qt.WindowModal)
|
||||||
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp("filtered-decks"))
|
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")
|
restoreGeom(self, "dyndeckconf")
|
||||||
self.initialSetup()
|
self.initialSetup()
|
||||||
self.loadConf()
|
self.loadConf()
|
||||||
|
|
@ -154,9 +154,9 @@ it?"""
|
||||||
ret.append(i)
|
ret.append(i)
|
||||||
except:
|
except:
|
||||||
# invalid, don't update
|
# invalid, don't update
|
||||||
showWarning(_("Steps must be numbers."))
|
showWarning(tr(TR.SCHEDULING_STEPS_MUST_BE_NUMBERS))
|
||||||
return None
|
return None
|
||||||
if len(ret) < minSize:
|
if len(ret) < minSize:
|
||||||
showWarning(_("At least one step is required."))
|
showWarning(tr(TR.SCHEDULING_AT_LEAST_ONE_STEP_IS_REQUIRED))
|
||||||
return None
|
return None
|
||||||
return ret
|
return ret
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import aqt.editor
|
import aqt.editor
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.main import ResetReason
|
from aqt.main import ResetReason
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import restoreGeom, saveGeom, tooltip
|
from aqt.utils import TR, restoreGeom, saveGeom, tooltip, tr
|
||||||
|
|
||||||
|
|
||||||
class EditCurrent(QDialog):
|
class EditCurrent(QDialog):
|
||||||
|
|
@ -17,7 +16,7 @@ class EditCurrent(QDialog):
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.form = aqt.forms.editcurrent.Ui_Dialog()
|
self.form = aqt.forms.editcurrent.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
self.setWindowTitle(_("Edit Current"))
|
self.setWindowTitle(tr(TR.EDITING_EDIT_CURRENT))
|
||||||
self.setMinimumHeight(400)
|
self.setMinimumHeight(400)
|
||||||
self.setMinimumWidth(250)
|
self.setMinimumWidth(250)
|
||||||
self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
|
self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ from aqt.qt import *
|
||||||
from aqt.sound import av_player, getAudio
|
from aqt.sound import av_player, getAudio
|
||||||
from aqt.theme import theme_manager
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
TR,
|
||||||
getFile,
|
getFile,
|
||||||
openHelp,
|
openHelp,
|
||||||
qtMenuShortcutWorkaround,
|
qtMenuShortcutWorkaround,
|
||||||
|
|
@ -40,6 +41,7 @@ from aqt.utils import (
|
||||||
showInfo,
|
showInfo,
|
||||||
showWarning,
|
showWarning,
|
||||||
tooltip,
|
tooltip,
|
||||||
|
tr,
|
||||||
)
|
)
|
||||||
from aqt.webview import AnkiWebView
|
from aqt.webview import AnkiWebView
|
||||||
|
|
||||||
|
|
@ -122,16 +124,16 @@ class Editor:
|
||||||
self._addButton(
|
self._addButton(
|
||||||
None,
|
None,
|
||||||
"fields",
|
"fields",
|
||||||
_("Customize Fields"),
|
tr(TR.EDITING_CUSTOMIZE_FIELDS),
|
||||||
_("Fields") + "...",
|
tr(TR.EDITING_FIELDS) + "...",
|
||||||
disables=False,
|
disables=False,
|
||||||
rightside=False,
|
rightside=False,
|
||||||
),
|
),
|
||||||
self._addButton(
|
self._addButton(
|
||||||
None,
|
None,
|
||||||
"cards",
|
"cards",
|
||||||
_("Customize Card Templates (Ctrl+L)"),
|
tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL),
|
||||||
_("Cards") + "...",
|
tr(TR.EDITING_CARDS) + "...",
|
||||||
disables=False,
|
disables=False,
|
||||||
rightside=False,
|
rightside=False,
|
||||||
),
|
),
|
||||||
|
|
@ -140,22 +142,37 @@ class Editor:
|
||||||
gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)
|
gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)
|
||||||
|
|
||||||
righttopbtns: List[str] = [
|
righttopbtns: List[str] = [
|
||||||
self._addButton("text_bold", "bold", _("Bold text (Ctrl+B)"), id="bold"),
|
|
||||||
self._addButton(
|
self._addButton(
|
||||||
"text_italic", "italic", _("Italic text (Ctrl+I)"), id="italic"
|
"text_bold", "bold", tr(TR.EDITING_BOLD_TEXT_CTRLANDB), id="bold"
|
||||||
),
|
),
|
||||||
self._addButton(
|
self._addButton(
|
||||||
"text_under", "underline", _("Underline text (Ctrl+U)"), id="underline"
|
"text_italic",
|
||||||
|
"italic",
|
||||||
|
tr(TR.EDITING_ITALIC_TEXT_CTRLANDI),
|
||||||
|
id="italic",
|
||||||
),
|
),
|
||||||
self._addButton(
|
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(
|
self._addButton(
|
||||||
None,
|
None,
|
||||||
"colour",
|
"colour",
|
||||||
_("Set foreground colour (F7)"),
|
tr(TR.EDITING_SET_FOREGROUND_COLOUR_F7),
|
||||||
"""
|
"""
|
||||||
<div id="forecolor"
|
<div id="forecolor"
|
||||||
style="display: inline-block; background: #000; border-radius: 5px;"
|
style="display: inline-block; background: #000; border-radius: 5px;"
|
||||||
|
|
@ -165,17 +182,19 @@ class Editor:
|
||||||
self._addButton(
|
self._addButton(
|
||||||
None,
|
None,
|
||||||
"changeCol",
|
"changeCol",
|
||||||
_("Change colour (F8)"),
|
tr(TR.EDITING_CHANGE_COLOUR_F8),
|
||||||
"""
|
"""
|
||||||
<div style="display: inline-block; border-radius: 5px;"
|
<div style="display: inline-block; border-radius: 5px;"
|
||||||
class="topbut rainbow"
|
class="topbut rainbow"
|
||||||
>""",
|
>""",
|
||||||
),
|
),
|
||||||
self._addButton("text_cloze", "cloze", _("Cloze deletion (Ctrl+Shift+C)")),
|
|
||||||
self._addButton(
|
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"),
|
self._addButton("more", "more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -197,7 +216,7 @@ class Editor:
|
||||||
bgcol = self.mw.app.palette().window().color().name() # type: ignore
|
bgcol = self.mw.app.palette().window().color().name() # type: ignore
|
||||||
# then load page
|
# then load page
|
||||||
self.web.stdHtml(
|
self.web.stdHtml(
|
||||||
_html % (bgcol, bgcol, topbuts, _("Show Duplicates")),
|
_html % (bgcol, bgcol, topbuts, tr(TR.EDITING_SHOW_DUPLICATES)),
|
||||||
css=["css/editor.css"],
|
css=["css/editor.css"],
|
||||||
js=["js/vendor/jquery.js", "js/editor.js"],
|
js=["js/vendor/jquery.js", "js/editor.js"],
|
||||||
context=self,
|
context=self,
|
||||||
|
|
@ -571,11 +590,13 @@ class Editor:
|
||||||
tb.setSpacing(12)
|
tb.setSpacing(12)
|
||||||
tb.setContentsMargins(2, 6, 2, 6)
|
tb.setContentsMargins(2, 6, 2, 6)
|
||||||
# tags
|
# tags
|
||||||
l = QLabel(_("Tags"))
|
l = QLabel(tr(TR.EDITING_TAGS))
|
||||||
tb.addWidget(l, 1, 0)
|
tb.addWidget(l, 1, 0)
|
||||||
self.tags = aqt.tagedit.TagEdit(self.widget)
|
self.tags = aqt.tagedit.TagEdit(self.widget)
|
||||||
qconnect(self.tags.lostFocus, self.saveTags)
|
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")
|
border = theme_manager.str_color("border")
|
||||||
self.tags.setStyleSheet(f"border: 1px solid {border}")
|
self.tags.setStyleSheet(f"border: 1px solid {border}")
|
||||||
tb.addWidget(self.tags, 1, 1)
|
tb.addWidget(self.tags, 1, 1)
|
||||||
|
|
@ -708,12 +729,12 @@ to a cloze type first, via 'Notes>Change Note Type'"""
|
||||||
extension_filter = " ".join(
|
extension_filter = " ".join(
|
||||||
"*." + extension for extension in sorted(itertools.chain(pics, audio))
|
"*." + extension for extension in sorted(itertools.chain(pics, audio))
|
||||||
)
|
)
|
||||||
key = _("Media") + " (" + extension_filter + ")"
|
key = tr(TR.EDITING_MEDIA) + " (" + extension_filter + ")"
|
||||||
|
|
||||||
def accept(file):
|
def accept(file):
|
||||||
self.addMedia(file, canDelete=True)
|
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()
|
self.parentWindow.activateWindow()
|
||||||
|
|
||||||
def addMedia(self, path, canDelete=False):
|
def addMedia(self, path, canDelete=False):
|
||||||
|
|
@ -746,7 +767,7 @@ to a cloze type first, via 'Notes>Change Note Type'"""
|
||||||
file = getAudio(self.widget)
|
file = getAudio(self.widget)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showWarning(
|
showWarning(
|
||||||
_("Couldn't record audio. Have you installed 'lame'?")
|
tr(TR.EDITING_COULDNT_RECORD_AUDIO_HAVE_YOU_INSTALLED)
|
||||||
+ "\n\n"
|
+ "\n\n"
|
||||||
+ repr(str(e))
|
+ repr(str(e))
|
||||||
)
|
)
|
||||||
|
|
@ -845,13 +866,14 @@ to a cloze type first, via 'Notes>Change Note Type'"""
|
||||||
with client.get(url) as response:
|
with client.get(url) as response:
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
error_msg = (
|
error_msg = (
|
||||||
_("Unexpected response code: %s") % response.status_code
|
tr(TR.QT_MISC_UNEXPECTED_RESPONSE_CODE, val="%s")
|
||||||
|
% response.status_code
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
filecontents = response.content
|
filecontents = response.content
|
||||||
content_type = response.headers.get("content-type")
|
content_type = response.headers.get("content-type")
|
||||||
except (urllib.error.URLError, requests.exceptions.RequestException) as e:
|
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
|
return None
|
||||||
finally:
|
finally:
|
||||||
self.mw.progress.finish()
|
self.mw.progress.finish()
|
||||||
|
|
@ -948,13 +970,17 @@ to a cloze type first, via 'Notes>Change Note Type'"""
|
||||||
m = QMenu(self.mw)
|
m = QMenu(self.mw)
|
||||||
|
|
||||||
for text, handler, shortcut in (
|
for text, handler, shortcut in (
|
||||||
(_("MathJax inline"), self.insertMathjaxInline, "Ctrl+M, M"),
|
(tr(TR.EDITING_MATHJAX_INLINE), self.insertMathjaxInline, "Ctrl+M, M"),
|
||||||
(_("MathJax block"), self.insertMathjaxBlock, "Ctrl+M, E"),
|
(tr(TR.EDITING_MATHJAX_BLOCK), self.insertMathjaxBlock, "Ctrl+M, E"),
|
||||||
(_("MathJax chemistry"), self.insertMathjaxChemistry, "Ctrl+M, C"),
|
(
|
||||||
(_("LaTeX"), self.insertLatex, "Ctrl+T, T"),
|
tr(TR.EDITING_MATHJAX_CHEMISTRY),
|
||||||
(_("LaTeX equation"), self.insertLatexEqn, "Ctrl+T, E"),
|
self.insertMathjaxChemistry,
|
||||||
(_("LaTeX math env."), self.insertLatexMathEnv, "Ctrl+T, M"),
|
"Ctrl+M, C",
|
||||||
(_("Edit HTML"), self.onHtmlEdit, "Ctrl+Shift+X"),
|
),
|
||||||
|
(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)
|
a = m.addAction(text)
|
||||||
qconnect(a.triggered, handler)
|
qconnect(a.triggered, handler)
|
||||||
|
|
@ -1204,11 +1230,11 @@ class EditorWebView(AnkiWebView):
|
||||||
|
|
||||||
def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
|
def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
|
||||||
m = QMenu(self)
|
m = QMenu(self)
|
||||||
a = m.addAction(_("Cut"))
|
a = m.addAction(tr(TR.EDITING_CUT))
|
||||||
qconnect(a.triggered, self.onCut)
|
qconnect(a.triggered, self.onCut)
|
||||||
a = m.addAction(_("Copy"))
|
a = m.addAction(tr(TR.ACTIONS_COPY))
|
||||||
qconnect(a.triggered, self.onCopy)
|
qconnect(a.triggered, self.onCopy)
|
||||||
a = m.addAction(_("Paste"))
|
a = m.addAction(tr(TR.EDITING_PASTE))
|
||||||
qconnect(a.triggered, self.onPaste)
|
qconnect(a.triggered, self.onPaste)
|
||||||
gui_hooks.editor_will_show_context_menu(self, m)
|
gui_hooks.editor_will_show_context_menu(self, m)
|
||||||
m.popup(QCursor.pos())
|
m.popup(QCursor.pos())
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,7 @@ your system's temporary folder may be incorrect."""
|
||||||
if "abortSchemaMod" in error:
|
if "abortSchemaMod" in error:
|
||||||
return
|
return
|
||||||
if "10013" in error:
|
if "10013" in error:
|
||||||
return showWarning(
|
return showWarning(tr(TR.QT_MISC_YOUR_FIREWALL_OR_ANTIVIRUS_PROGRAM_IS))
|
||||||
_(
|
|
||||||
"Your firewall or antivirus program is preventing Anki from creating a connection to itself. Please add an exception for Anki."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if "no default input" in error.lower():
|
if "no default input" in error.lower():
|
||||||
return showWarning(
|
return showWarning(
|
||||||
_(
|
_(
|
||||||
|
|
@ -94,11 +90,7 @@ your system's temporary folder may be incorrect."""
|
||||||
if "Beautiful Soup is not an HTTP client" in error:
|
if "Beautiful Soup is not an HTTP client" in error:
|
||||||
return
|
return
|
||||||
if "database or disk is full" in error or "Errno 28" in error:
|
if "database or disk is full" in error or "Errno 28" in error:
|
||||||
return showWarning(
|
return showWarning(tr(TR.QT_MISC_YOUR_COMPUTERS_STORAGE_MAY_BE_FULL))
|
||||||
_(
|
|
||||||
"Your computer's storage may be full. Please delete some unneeded files, then try again."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if "disk I/O error" in error:
|
if "disk I/O error" in error:
|
||||||
showWarning(markdown(tr(TR.ERRORS_ACCESSING_DB)))
|
showWarning(markdown(tr(TR.ERRORS_ACCESSING_DB)))
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ from anki import hooks
|
||||||
from anki.exporting import Exporter, exporters
|
from anki.exporting import Exporter, exporters
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from aqt.qt import *
|
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):
|
class ExportDialog(QDialog):
|
||||||
|
|
@ -49,13 +49,13 @@ class ExportDialog(QDialog):
|
||||||
self.exporterChanged(idx)
|
self.exporterChanged(idx)
|
||||||
# deck list
|
# deck list
|
||||||
if self.cids is None:
|
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())
|
self.decks.extend(d.name for d in self.col.decks.all_names_and_ids())
|
||||||
else:
|
else:
|
||||||
self.decks = [_("Selected Notes")]
|
self.decks = [tr(TR.EXPORTING_SELECTED_NOTES)]
|
||||||
self.frm.deck.addItems(self.decks)
|
self.frm.deck.addItems(self.decks)
|
||||||
# save button
|
# save button
|
||||||
b = QPushButton(_("Export..."))
|
b = QPushButton(tr(TR.EXPORTING_EXPORT))
|
||||||
self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
|
self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
|
||||||
# set default option if accessed through deck button
|
# set default option if accessed through deck button
|
||||||
if did:
|
if did:
|
||||||
|
|
@ -107,7 +107,7 @@ class ExportDialog(QDialog):
|
||||||
self.exporter.did = self.col.decks.id(name)
|
self.exporter.did = self.col.decks.id(name)
|
||||||
if self.isVerbatim:
|
if self.isVerbatim:
|
||||||
name = time.strftime("-%Y-%m-%d@%H-%M-%S", time.localtime(time.time()))
|
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:
|
else:
|
||||||
# Get deck name and remove invalid filename characters
|
# Get deck name and remove invalid filename characters
|
||||||
deck_name = self.decks[self.frm.deck.currentIndex()]
|
deck_name = self.decks[self.frm.deck.currentIndex()]
|
||||||
|
|
@ -121,7 +121,7 @@ class ExportDialog(QDialog):
|
||||||
while 1:
|
while 1:
|
||||||
file = getSaveFile(
|
file = getSaveFile(
|
||||||
self,
|
self,
|
||||||
_("Export"),
|
tr(TR.ACTIONS_EXPORT),
|
||||||
"export",
|
"export",
|
||||||
key_str,
|
key_str,
|
||||||
self.exporter.ext,
|
self.exporter.ext,
|
||||||
|
|
@ -142,7 +142,7 @@ class ExportDialog(QDialog):
|
||||||
f = open(file, "wb")
|
f = open(file, "wb")
|
||||||
f.close()
|
f.close()
|
||||||
except (OSError, IOError) as e:
|
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:
|
else:
|
||||||
os.unlink(file)
|
os.unlink(file)
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@ class ExportDialog(QDialog):
|
||||||
|
|
||||||
def on_export_finished(self):
|
def on_export_finished(self):
|
||||||
if self.isVerbatim:
|
if self.isVerbatim:
|
||||||
msg = _("Collection exported.")
|
msg = tr(TR.EXPORTING_COLLECTION_EXPORTED)
|
||||||
self.mw.reopen()
|
self.mw.reopen()
|
||||||
else:
|
else:
|
||||||
if self.isTextNote:
|
if self.isTextNote:
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from anki.rsbackend import TemplateError
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.schema_change_tracker import ChangeTracker
|
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):
|
class FieldDialog(QDialog):
|
||||||
|
|
@ -20,11 +20,11 @@ class FieldDialog(QDialog):
|
||||||
self.mm = self.mw.col.models
|
self.mm = self.mw.col.models
|
||||||
self.model = nt
|
self.model = nt
|
||||||
self.mm._remove_from_cache(self.model["id"])
|
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.change_tracker = ChangeTracker(self.mw)
|
||||||
self.form = aqt.forms.fields.Ui_Dialog()
|
self.form = aqt.forms.fields.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
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.Help).setAutoDefault(False)
|
||||||
self.form.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False)
|
self.form.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False)
|
||||||
self.form.buttonBox.button(QDialogButtonBox.Save).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:
|
if ignoreOrd is not None and f["ord"] == ignoreOrd:
|
||||||
continue
|
continue
|
||||||
if f["name"] == txt:
|
if f["name"] == txt:
|
||||||
showWarning(_("That field name is already used."))
|
showWarning(tr(TR.FIELDS_THAT_FIELD_NAME_IS_ALREADY_USED))
|
||||||
return
|
return
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
def onRename(self):
|
def onRename(self):
|
||||||
idx = self.currentIdx
|
idx = self.currentIdx
|
||||||
f = self.model["flds"][idx]
|
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:
|
if not name:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ class FieldDialog(QDialog):
|
||||||
self.form.fieldList.setCurrentRow(idx)
|
self.form.fieldList.setCurrentRow(idx)
|
||||||
|
|
||||||
def onAdd(self):
|
def onAdd(self):
|
||||||
name = self._uniqueName(_("Field name:"))
|
name = self._uniqueName(tr(TR.FIELDS_FIELD_NAME))
|
||||||
if not name:
|
if not name:
|
||||||
return
|
return
|
||||||
if not self.change_tracker.mark_schema():
|
if not self.change_tracker.mark_schema():
|
||||||
|
|
@ -120,10 +120,10 @@ class FieldDialog(QDialog):
|
||||||
|
|
||||||
def onDelete(self):
|
def onDelete(self):
|
||||||
if len(self.model["flds"]) < 2:
|
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)
|
count = self.mm.useCount(self.model)
|
||||||
c = ngettext("%d note", "%d notes", count) % count
|
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
|
return
|
||||||
if not self.change_tracker.mark_schema():
|
if not self.change_tracker.mark_schema():
|
||||||
return
|
return
|
||||||
|
|
@ -137,7 +137,9 @@ class FieldDialog(QDialog):
|
||||||
def onPosition(self, delta=-1):
|
def onPosition(self, delta=-1):
|
||||||
idx = self.currentIdx
|
idx = self.currentIdx
|
||||||
l = len(self.model["flds"])
|
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:
|
if not txt:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
@ -43,14 +42,14 @@ class ChangeMap(QDialog):
|
||||||
n = 0
|
n = 0
|
||||||
setCurrent = False
|
setCurrent = False
|
||||||
for field in self.model["flds"]:
|
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)
|
self.frm.fields.addItem(item)
|
||||||
if current == field["name"]:
|
if current == field["name"]:
|
||||||
setCurrent = True
|
setCurrent = True
|
||||||
self.frm.fields.setCurrentRow(n)
|
self.frm.fields.setCurrentRow(n)
|
||||||
n += 1
|
n += 1
|
||||||
self.frm.fields.addItem(QListWidgetItem(_("Map to Tags")))
|
self.frm.fields.addItem(QListWidgetItem(tr(TR.IMPORTING_MAP_TO_TAGS)))
|
||||||
self.frm.fields.addItem(QListWidgetItem(_("Ignore field")))
|
self.frm.fields.addItem(QListWidgetItem(tr(TR.IMPORTING_IGNORE_FIELD)))
|
||||||
if not setCurrent:
|
if not setCurrent:
|
||||||
if current == "_tags":
|
if current == "_tags":
|
||||||
self.frm.fields.setCurrentRow(n)
|
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.setText(self.mw.pm.profile.get("tagModified", ""))
|
||||||
self.frm.tagModified.setCol(self.mw.col)
|
self.frm.tagModified.setCol(self.mw.col)
|
||||||
# import button
|
# import button
|
||||||
b = QPushButton(_("Import"))
|
b = QPushButton(tr(TR.ACTIONS_IMPORT))
|
||||||
self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
|
self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
|
|
@ -156,24 +155,24 @@ you can enter it here. Use \\t to represent tab."""
|
||||||
else:
|
else:
|
||||||
d = self.importer.dialect.delimiter
|
d = self.importer.dialect.delimiter
|
||||||
if d == "\t":
|
if d == "\t":
|
||||||
d = _("Tab")
|
d = tr(TR.IMPORTING_TAB)
|
||||||
elif d == ",":
|
elif d == ",":
|
||||||
d = _("Comma")
|
d = tr(TR.IMPORTING_COMMA)
|
||||||
elif d == " ":
|
elif d == " ":
|
||||||
d = _("Space")
|
d = tr(TR.STUDYING_SPACE)
|
||||||
elif d == ";":
|
elif d == ";":
|
||||||
d = _("Semicolon")
|
d = tr(TR.IMPORTING_SEMICOLON)
|
||||||
elif d == ":":
|
elif d == ":":
|
||||||
d = _("Colon")
|
d = tr(TR.IMPORTING_COLON)
|
||||||
else:
|
else:
|
||||||
d = repr(d)
|
d = repr(d)
|
||||||
txt = _("Fields separated by: %s") % d
|
txt = tr(TR.IMPORTING_FIELDS_SEPARATED_BY, val="%s") % d
|
||||||
self.frm.autoDetect.setText(txt)
|
self.frm.autoDetect.setText(txt)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
self.importer.mapping = self.mapping
|
self.importer.mapping = self.mapping
|
||||||
if not self.importer.mappingOk():
|
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
|
return
|
||||||
self.importer.importMode = self.frm.importMode.currentIndex()
|
self.importer.importMode = self.frm.importMode.currentIndex()
|
||||||
self.mw.pm.profile["importMode"] = self.importer.importMode
|
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.models.save(self.importer.model, updateReqs=False)
|
||||||
self.mw.col.decks.select(did)
|
self.mw.col.decks.select(did)
|
||||||
self.mw.progress.start()
|
self.mw.progress.start()
|
||||||
self.mw.checkpoint(_("Import"))
|
self.mw.checkpoint(tr(TR.ACTIONS_IMPORT))
|
||||||
|
|
||||||
def on_done(future: Future):
|
def on_done(future: Future):
|
||||||
self.mw.progress.finish()
|
self.mw.progress.finish()
|
||||||
|
|
@ -208,7 +207,7 @@ you can enter it here. Use \\t to represent tab."""
|
||||||
showText(msg)
|
showText(msg)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
txt = _("Importing complete.") + "\n"
|
txt = tr(TR.IMPORTING_IMPORTING_COMPLETE) + "\n"
|
||||||
if self.importer.log:
|
if self.importer.log:
|
||||||
txt += "\n".join(self.importer.log)
|
txt += "\n".join(self.importer.log)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
@ -248,16 +247,16 @@ you can enter it here. Use \\t to represent tab."""
|
||||||
self.grid.setSpacing(6)
|
self.grid.setSpacing(6)
|
||||||
fields = self.importer.fields()
|
fields = self.importer.fields()
|
||||||
for num in range(len(self.mapping)):
|
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)
|
self.grid.addWidget(QLabel(text), num, 0)
|
||||||
if self.mapping[num] == "_tags":
|
if self.mapping[num] == "_tags":
|
||||||
text = _("mapped to <b>Tags</b>")
|
text = tr(TR.IMPORTING_MAPPED_TO_TAGS)
|
||||||
elif self.mapping[num]:
|
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:
|
else:
|
||||||
text = _("<ignored>")
|
text = tr(TR.IMPORTING_IGNORED)
|
||||||
self.grid.addWidget(QLabel(text), num, 1)
|
self.grid.addWidget(QLabel(text), num, 1)
|
||||||
button = QPushButton(_("Change"))
|
button = QPushButton(tr(TR.IMPORTING_CHANGE))
|
||||||
self.grid.addWidget(button, num, 2)
|
self.grid.addWidget(button, num, 2)
|
||||||
qconnect(button.clicked, lambda _, s=self, n=num: s.changeMappingNum(n))
|
qconnect(button.clicked, lambda _, s=self, n=num: s.changeMappingNum(n))
|
||||||
|
|
||||||
|
|
@ -308,7 +307,7 @@ def showUnicodeWarning():
|
||||||
|
|
||||||
def onImport(mw):
|
def onImport(mw):
|
||||||
filt = ";;".join([x[0] for x in importing.Importers])
|
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:
|
if not file:
|
||||||
return
|
return
|
||||||
file = str(file)
|
file = str(file)
|
||||||
|
|
@ -316,18 +315,10 @@ def onImport(mw):
|
||||||
head, ext = os.path.splitext(file)
|
head, ext = os.path.splitext(file)
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
if ext == ".anki":
|
if ext == ".anki":
|
||||||
showInfo(
|
showInfo(tr(TR.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."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
elif ext == ".anki2":
|
elif ext == ".anki2":
|
||||||
showInfo(
|
showInfo(tr(TR.IMPORTING_ANKI2_FILES_ARE_NOT_DIRECTLY_IMPORTABLE))
|
||||||
_(
|
|
||||||
".anki2 files are not directly importable - please import the .apkg or .zip file you have received instead."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
importFile(mw, file)
|
importFile(mw, file)
|
||||||
|
|
@ -364,7 +355,7 @@ def importFile(mw, file):
|
||||||
mw.progress.finish()
|
mw.progress.finish()
|
||||||
msg = repr(str(e))
|
msg = repr(str(e))
|
||||||
if msg == "'unknownFormat'":
|
if msg == "'unknownFormat'":
|
||||||
showWarning(_("Unknown file format."))
|
showWarning(tr(TR.IMPORTING_UNKNOWN_FILE_FORMAT))
|
||||||
else:
|
else:
|
||||||
msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n"
|
msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n"
|
||||||
msg += str(traceback.format_exc())
|
msg += str(traceback.format_exc())
|
||||||
|
|
@ -513,7 +504,7 @@ def _replaceWithApkg(mw, filename, backup):
|
||||||
future.result()
|
future.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
showWarning(_("The provided file is not a valid .apkg file."))
|
showWarning(tr(TR.IMPORTING_THE_PROVIDED_FILE_IS_NOT_A))
|
||||||
return
|
return
|
||||||
|
|
||||||
if not mw.loadCollection():
|
if not mw.loadCollection():
|
||||||
|
|
@ -521,6 +512,6 @@ def _replaceWithApkg(mw, filename, backup):
|
||||||
if backup:
|
if backup:
|
||||||
mw.col.modSchema(check=False)
|
mw.col.modSchema(check=False)
|
||||||
|
|
||||||
tooltip(_("Importing complete."))
|
tooltip(tr(TR.IMPORTING_IMPORTING_COMPLETE))
|
||||||
|
|
||||||
mw.taskman.run_in_background(do_import, on_done)
|
mw.taskman.run_in_background(do_import, on_done)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
|
|
@ -120,7 +119,9 @@ class AnkiQt(QMainWindow):
|
||||||
self.setupAddons(args)
|
self.setupAddons(args)
|
||||||
self.finish_ui_setup()
|
self.finish_ui_setup()
|
||||||
except:
|
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)
|
sys.exit(1)
|
||||||
# must call this after ui set up
|
# must call this after ui set up
|
||||||
if self.safeMode:
|
if self.safeMode:
|
||||||
|
|
@ -291,10 +292,10 @@ class AnkiQt(QMainWindow):
|
||||||
return not checkInvalidFilename(name) and name != "addons21"
|
return not checkInvalidFilename(name) and name != "addons21"
|
||||||
|
|
||||||
def onAddProfile(self):
|
def onAddProfile(self):
|
||||||
name = getOnlyText(_("Name:")).strip()
|
name = getOnlyText(tr(TR.ACTIONS_NAME)).strip()
|
||||||
if name:
|
if name:
|
||||||
if name in self.pm.profiles():
|
if name in self.pm.profiles():
|
||||||
return showWarning(_("Name exists."))
|
return showWarning(tr(TR.QT_MISC_NAME_EXISTS))
|
||||||
if not self.profileNameOk(name):
|
if not self.profileNameOk(name):
|
||||||
return
|
return
|
||||||
self.pm.create(name)
|
self.pm.create(name)
|
||||||
|
|
@ -302,13 +303,13 @@ class AnkiQt(QMainWindow):
|
||||||
self.refreshProfilesList()
|
self.refreshProfilesList()
|
||||||
|
|
||||||
def onRenameProfile(self):
|
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:
|
if not name:
|
||||||
return
|
return
|
||||||
if name == self.pm.name:
|
if name == self.pm.name:
|
||||||
return
|
return
|
||||||
if name in self.pm.profiles():
|
if name in self.pm.profiles():
|
||||||
return showWarning(_("Name exists."))
|
return showWarning(tr(TR.QT_MISC_NAME_EXISTS))
|
||||||
if not self.profileNameOk(name):
|
if not self.profileNameOk(name):
|
||||||
return
|
return
|
||||||
self.pm.rename(name)
|
self.pm.rename(name)
|
||||||
|
|
@ -317,7 +318,7 @@ class AnkiQt(QMainWindow):
|
||||||
def onRemProfile(self):
|
def onRemProfile(self):
|
||||||
profs = self.pm.profiles()
|
profs = self.pm.profiles()
|
||||||
if len(profs) < 2:
|
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?
|
# sure?
|
||||||
if not askUser(
|
if not askUser(
|
||||||
_(
|
_(
|
||||||
|
|
@ -348,7 +349,7 @@ Replace your collection with an earlier backup?"""
|
||||||
|
|
||||||
getFile(
|
getFile(
|
||||||
self.profileDiag,
|
self.profileDiag,
|
||||||
_("Revert to backup"),
|
tr(TR.QT_MISC_REVERT_TO_BACKUP),
|
||||||
cb=doOpen,
|
cb=doOpen,
|
||||||
filter="*.colpkg",
|
filter="*.colpkg",
|
||||||
dir=self.pm.backupFolder(),
|
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
|
# move the existing collection to the trash, as it may not open
|
||||||
self.pm.trashCollection()
|
self.pm.trashCollection()
|
||||||
except:
|
except:
|
||||||
showWarning(
|
showWarning(tr(TR.QT_MISC_UNABLE_TO_MOVE_EXISTING_FILE_TO))
|
||||||
_(
|
|
||||||
"Unable to move existing file to trash - please try restarting your computer."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.pendingImport = path
|
self.pendingImport = path
|
||||||
|
|
@ -553,9 +550,9 @@ close the profile or restart Anki."""
|
||||||
if not self.col:
|
if not self.col:
|
||||||
return
|
return
|
||||||
if self.restoringBackup:
|
if self.restoringBackup:
|
||||||
label = _("Closing...")
|
label = tr(TR.QT_MISC_CLOSING)
|
||||||
else:
|
else:
|
||||||
label = _("Backing Up...")
|
label = tr(TR.QT_MISC_BACKING_UP)
|
||||||
self.progress.start(label=label)
|
self.progress.start(label=label)
|
||||||
corrupt = False
|
corrupt = False
|
||||||
try:
|
try:
|
||||||
|
|
@ -641,7 +638,7 @@ from the profile screen."
|
||||||
# have two weeks passed?
|
# have two weeks passed?
|
||||||
if (intTime() - self.pm.profile["lastOptimize"]) < 86400 * 14:
|
if (intTime() - self.pm.profile["lastOptimize"]) < 86400 * 14:
|
||||||
return
|
return
|
||||||
self.progress.start(label=_("Optimizing..."))
|
self.progress.start(label=tr(TR.QT_MISC_OPTIMIZING))
|
||||||
self.col.optimize()
|
self.col.optimize()
|
||||||
self.pm.profile["lastOptimize"] = intTime()
|
self.pm.profile["lastOptimize"] = intTime()
|
||||||
self.pm.save()
|
self.pm.save()
|
||||||
|
|
@ -672,7 +669,7 @@ from the profile screen."
|
||||||
def _selectedDeck(self) -> Optional[Deck]:
|
def _selectedDeck(self) -> Optional[Deck]:
|
||||||
did = self.col.decks.selected()
|
did = self.col.decks.selected()
|
||||||
if not self.col.decks.nameOrNone(did):
|
if not self.col.decks.nameOrNone(did):
|
||||||
showInfo(_("Please select a deck."))
|
showInfo(tr(TR.QT_MISC_PLEASE_SELECT_A_DECK))
|
||||||
return None
|
return None
|
||||||
return self.col.decks.get(did)
|
return self.col.decks.get(did)
|
||||||
|
|
||||||
|
|
@ -732,8 +729,8 @@ from the profile screen."
|
||||||
return
|
return
|
||||||
web_context = ResetRequired(self)
|
web_context = ResetRequired(self)
|
||||||
self.web.set_bridge_command(lambda url: self.delayedMaybeReset(), web_context)
|
self.web.set_bridge_command(lambda url: self.delayedMaybeReset(), web_context)
|
||||||
i = _("Waiting for editing to finish.")
|
i = tr(TR.QT_MISC_WAITING_FOR_EDITING_TO_FINISH)
|
||||||
b = self.button("refresh", _("Resume Now"), id="resume")
|
b = self.button("refresh", tr(TR.QT_MISC_RESUME_NOW), id="resume")
|
||||||
self.web.stdHtml(
|
self.web.stdHtml(
|
||||||
"""
|
"""
|
||||||
<center><div style="height: 100%%">
|
<center><div style="height: 100%%">
|
||||||
|
|
@ -762,7 +759,7 @@ from the profile screen."
|
||||||
) -> str:
|
) -> str:
|
||||||
class_ = "but " + class_
|
class_ = "but " + class_
|
||||||
if key:
|
if key:
|
||||||
key = _("Shortcut key: %s") % key
|
key = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % key
|
||||||
else:
|
else:
|
||||||
key = ""
|
key = ""
|
||||||
return """
|
return """
|
||||||
|
|
@ -1033,17 +1030,19 @@ title="%s" %s>%s</button>""" % (
|
||||||
gui_hooks.review_did_undo(cid)
|
gui_hooks.review_did_undo(cid)
|
||||||
else:
|
else:
|
||||||
self.reset()
|
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)
|
gui_hooks.state_did_revert(n)
|
||||||
self.maybeEnableUndo()
|
self.maybeEnableUndo()
|
||||||
|
|
||||||
def maybeEnableUndo(self) -> None:
|
def maybeEnableUndo(self) -> None:
|
||||||
if self.col and self.col.undoName():
|
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)
|
self.form.actionUndo.setEnabled(True)
|
||||||
gui_hooks.undo_state_did_change(True)
|
gui_hooks.undo_state_did_change(True)
|
||||||
else:
|
else:
|
||||||
self.form.actionUndo.setText(_("Undo"))
|
self.form.actionUndo.setText(tr(TR.QT_MISC_UNDO))
|
||||||
self.form.actionUndo.setEnabled(False)
|
self.form.actionUndo.setEnabled(False)
|
||||||
gui_hooks.undo_state_did_change(False)
|
gui_hooks.undo_state_did_change(False)
|
||||||
|
|
||||||
|
|
@ -1119,7 +1118,7 @@ title="%s" %s>%s</button>""" % (
|
||||||
import aqt.importing
|
import aqt.importing
|
||||||
|
|
||||||
if not os.path.exists(path):
|
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
|
return None
|
||||||
|
|
||||||
aqt.importing.importFile(self, path)
|
aqt.importing.importFile(self, path)
|
||||||
|
|
@ -1161,9 +1160,9 @@ title="%s" %s>%s</button>""" % (
|
||||||
if not search:
|
if not search:
|
||||||
if not deck["dyn"]:
|
if not deck["dyn"]:
|
||||||
search = 'deck:"%s" ' % deck["name"]
|
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
|
n += 1
|
||||||
name = _("Filtered Deck %d") % n
|
name = tr(TR.QT_MISC_FILTERED_DECK, val="%s") % n
|
||||||
did = self.col.decks.new_filtered(name)
|
did = self.col.decks.new_filtered(name)
|
||||||
diag = aqt.dyndeckconf.DeckConf(self, first=True, search=search)
|
diag = aqt.dyndeckconf.DeckConf(self, first=True, search=search)
|
||||||
if not diag.ok:
|
if not diag.ok:
|
||||||
|
|
@ -1537,7 +1536,7 @@ will be lost. Continue?"""
|
||||||
)
|
)
|
||||||
frm.log.appendPlainText(to_append)
|
frm.log.appendPlainText(to_append)
|
||||||
except UnicodeDecodeError:
|
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 = gui_hooks.debug_console_did_evaluate_python(
|
||||||
to_append, text, frm
|
to_append, text, frm
|
||||||
)
|
)
|
||||||
|
|
@ -1600,9 +1599,9 @@ will be lost. Continue?"""
|
||||||
return None
|
return None
|
||||||
self.pendingImport = buf
|
self.pendingImport = buf
|
||||||
if is_addon:
|
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:
|
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)
|
return tooltip(msg)
|
||||||
if not self.interactiveState() or self.progress.busy():
|
if not self.interactiveState() or self.progress.busy():
|
||||||
# we can't raise the main window while in profile dialog, syncing, etc
|
# we can't raise the main window while in profile dialog, syncing, etc
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from typing import Optional
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import shortcut
|
from aqt.utils import TR, shortcut, tr
|
||||||
|
|
||||||
|
|
||||||
class ModelChooser(QHBoxLayout):
|
class ModelChooser(QHBoxLayout):
|
||||||
|
|
@ -37,11 +37,11 @@ class ModelChooser(QHBoxLayout):
|
||||||
|
|
||||||
def setupModels(self) -> None:
|
def setupModels(self) -> None:
|
||||||
if self.label:
|
if self.label:
|
||||||
self.modelLabel = QLabel(_("Type"))
|
self.modelLabel = QLabel(tr(TR.NOTETYPES_TYPE))
|
||||||
self.addWidget(self.modelLabel)
|
self.addWidget(self.modelLabel)
|
||||||
# models box
|
# models box
|
||||||
self.models = QPushButton()
|
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
|
QShortcut(QKeySequence("Ctrl+N"), self.widget, activated=self.on_activated) # type: ignore
|
||||||
self.models.setAutoDefault(False)
|
self.models.setAutoDefault(False)
|
||||||
self.addWidget(self.models)
|
self.addWidget(self.models)
|
||||||
|
|
@ -73,7 +73,7 @@ class ModelChooser(QHBoxLayout):
|
||||||
|
|
||||||
current = self.deck.models.current()["name"]
|
current = self.deck.models.current()["name"]
|
||||||
# edit button
|
# edit button
|
||||||
edit = QPushButton(_("Manage"), clicked=self.onEdit) # type: ignore
|
edit = QPushButton(tr(TR.QT_MISC_MANAGE), clicked=self.onEdit) # type: ignore
|
||||||
|
|
||||||
def nameFunc():
|
def nameFunc():
|
||||||
return sorted(self.deck.models.allNames())
|
return sorted(self.deck.models.allNames())
|
||||||
|
|
@ -81,8 +81,8 @@ class ModelChooser(QHBoxLayout):
|
||||||
ret = StudyDeck(
|
ret = StudyDeck(
|
||||||
self.mw,
|
self.mw,
|
||||||
names=nameFunc,
|
names=nameFunc,
|
||||||
accept=_("Choose"),
|
accept=tr(TR.ACTIONS_CHOOSE),
|
||||||
title=_("Choose Note Type"),
|
title=tr(TR.QT_MISC_CHOOSE_NOTE_TYPE),
|
||||||
help="_notes",
|
help="_notes",
|
||||||
current=current,
|
current=current,
|
||||||
parent=self.widget,
|
parent=self.widget,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from anki.rsbackend import pb
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
TR,
|
||||||
askUser,
|
askUser,
|
||||||
getText,
|
getText,
|
||||||
maybeHideClose,
|
maybeHideClose,
|
||||||
|
|
@ -21,6 +22,7 @@ from aqt.utils import (
|
||||||
restoreGeom,
|
restoreGeom,
|
||||||
saveGeom,
|
saveGeom,
|
||||||
showInfo,
|
showInfo,
|
||||||
|
tr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -33,7 +35,7 @@ class Models(QDialog):
|
||||||
self.col = mw.col.weakref()
|
self.col = mw.col.weakref()
|
||||||
assert self.col
|
assert self.col
|
||||||
self.mm = self.col.models
|
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 = aqt.forms.models.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
qconnect(
|
qconnect(
|
||||||
|
|
@ -54,20 +56,20 @@ class Models(QDialog):
|
||||||
box = f.buttonBox
|
box = f.buttonBox
|
||||||
|
|
||||||
default_buttons = [
|
default_buttons = [
|
||||||
(_("Add"), self.onAdd),
|
(tr(TR.ACTIONS_ADD), self.onAdd),
|
||||||
(_("Rename"), self.onRename),
|
(tr(TR.ACTIONS_RENAME), self.onRename),
|
||||||
(_("Delete"), self.onDelete),
|
(tr(TR.ACTIONS_DELETE), self.onDelete),
|
||||||
]
|
]
|
||||||
|
|
||||||
if self.fromMain:
|
if self.fromMain:
|
||||||
default_buttons.extend(
|
default_buttons.extend(
|
||||||
[
|
[
|
||||||
(_("Fields..."), self.onFields),
|
(tr(TR.NOTETYPES_FIELDS), self.onFields),
|
||||||
(_("Cards..."), self.onCards),
|
(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):
|
for label, func in gui_hooks.models_did_init_buttons(default_buttons, self):
|
||||||
button = box.addButton(label, QDialogButtonBox.ActionRole)
|
button = box.addButton(label, QDialogButtonBox.ActionRole)
|
||||||
|
|
@ -84,7 +86,7 @@ class Models(QDialog):
|
||||||
|
|
||||||
def onRename(self) -> None:
|
def onRename(self) -> None:
|
||||||
nt = self.current_notetype()
|
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('"', "")
|
name = txt[0].replace('"', "")
|
||||||
if txt[1] and name:
|
if txt[1] and name:
|
||||||
nt["name"] = name
|
nt["name"] = name
|
||||||
|
|
@ -120,20 +122,20 @@ class Models(QDialog):
|
||||||
def onAdd(self) -> None:
|
def onAdd(self) -> None:
|
||||||
m = AddModel(self.mw, self).get()
|
m = AddModel(self.mw, self).get()
|
||||||
if m:
|
if m:
|
||||||
txt = getText(_("Name:"), default=m["name"])[0].replace('"', "")
|
txt = getText(tr(TR.ACTIONS_NAME), default=m["name"])[0].replace('"', "")
|
||||||
if txt:
|
if txt:
|
||||||
m["name"] = txt
|
m["name"] = txt
|
||||||
self.saveAndRefresh(m)
|
self.saveAndRefresh(m)
|
||||||
|
|
||||||
def onDelete(self) -> None:
|
def onDelete(self) -> None:
|
||||||
if len(self.models) < 2:
|
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
|
return
|
||||||
idx = self.form.modelsList.currentRow()
|
idx = self.form.modelsList.currentRow()
|
||||||
if self.models[idx].use_count:
|
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:
|
else:
|
||||||
msg = _("Delete this unused note type?")
|
msg = tr(TR.NOTETYPES_DELETE_THIS_UNUSED_NOTE_TYPE)
|
||||||
if not askUser(msg, parent=self):
|
if not askUser(msg, parent=self):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -158,7 +160,7 @@ class Models(QDialog):
|
||||||
frm.latexsvg.setChecked(nt.get("latexsvg", False))
|
frm.latexsvg.setChecked(nt.get("latexsvg", False))
|
||||||
frm.latexHeader.setText(nt["latexPre"])
|
frm.latexHeader.setText(nt["latexPre"])
|
||||||
frm.latexFooter.setText(nt["latexPost"])
|
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"))
|
qconnect(frm.buttonBox.helpRequested, lambda: openHelp("math?id=latex"))
|
||||||
restoreGeom(d, "modelopts")
|
restoreGeom(d, "modelopts")
|
||||||
gui_hooks.models_advanced_will_show(d)
|
gui_hooks.models_advanced_will_show(d)
|
||||||
|
|
@ -209,12 +211,12 @@ class AddModel(QDialog):
|
||||||
# standard models
|
# standard models
|
||||||
self.models = []
|
self.models = []
|
||||||
for (name, func) in stdmodels.get_stock_notetypes(self.col):
|
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.dialog.models.addItem(item)
|
||||||
self.models.append((True, func))
|
self.models.append((True, func))
|
||||||
# add copies
|
# add copies
|
||||||
for m in sorted(self.col.models.all(), key=itemgetter("name")):
|
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.dialog.models.addItem(item)
|
||||||
self.models.append((False, m)) # type: ignore
|
self.models.append((False, m)) # type: ignore
|
||||||
self.dialog.models.setCurrentRow(0)
|
self.dialog.models.setCurrentRow(0)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
@ -12,7 +11,7 @@ from anki.lang import _
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
from aqt.toolbar import BottomBar
|
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:
|
class OverviewBottomBar:
|
||||||
|
|
@ -67,7 +66,7 @@ class Overview:
|
||||||
self.mw.col.startTimebox()
|
self.mw.col.startTimebox()
|
||||||
self.mw.moveToState("review")
|
self.mw.moveToState("review")
|
||||||
if self.mw.state == "overview":
|
if self.mw.state == "overview":
|
||||||
tooltip(_("No cards are due yet."))
|
tooltip(tr(TR.STUDYING_NO_CARDS_ARE_DUE_YET))
|
||||||
elif url == "anki":
|
elif url == "anki":
|
||||||
print("anki menu")
|
print("anki menu")
|
||||||
elif url == "opts":
|
elif url == "opts":
|
||||||
|
|
@ -128,13 +127,13 @@ class Overview:
|
||||||
info = self.mw.col.sched.congratulations_info()
|
info = self.mw.col.sched.congratulations_info()
|
||||||
if info.have_sched_buried and info.have_user_buried:
|
if info.have_sched_buried and info.have_user_buried:
|
||||||
opts = [
|
opts = [
|
||||||
_("Manually Buried Cards"),
|
tr(TR.STUDYING_MANUALLY_BURIED_CARDS),
|
||||||
_("Buried Siblings"),
|
tr(TR.STUDYING_BURIED_SIBLINGS),
|
||||||
_("All Buried Cards"),
|
tr(TR.STUDYING_ALL_BURIED_CARDS),
|
||||||
_("Cancel"),
|
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)
|
diag.setDefault(0)
|
||||||
ret = diag.run()
|
ret = diag.run()
|
||||||
if ret == opts[0]:
|
if ret == opts[0]:
|
||||||
|
|
@ -226,13 +225,13 @@ to their original deck."""
|
||||||
</table>
|
</table>
|
||||||
</td><td align=center>
|
</td><td align=center>
|
||||||
%s</td></tr></table>""" % (
|
%s</td></tr></table>""" % (
|
||||||
_("New"),
|
tr(TR.ACTIONS_NEW),
|
||||||
counts[0],
|
counts[0],
|
||||||
_("Learning"),
|
tr(TR.SCHEDULING_LEARNING),
|
||||||
counts[1],
|
counts[1],
|
||||||
_("To Review"),
|
tr(TR.STUDYING_TO_REVIEW),
|
||||||
counts[2],
|
counts[2],
|
||||||
but("study", _("Study Now"), id="study", extra=" autofocus"),
|
but("study", tr(TR.STUDYING_STUDY_NOW), id="study", extra=" autofocus"),
|
||||||
)
|
)
|
||||||
|
|
||||||
_body = """
|
_body = """
|
||||||
|
|
@ -249,20 +248,20 @@ to their original deck."""
|
||||||
|
|
||||||
def _renderBottom(self):
|
def _renderBottom(self):
|
||||||
links = [
|
links = [
|
||||||
["O", "opts", _("Options")],
|
["O", "opts", tr(TR.ACTIONS_OPTIONS)],
|
||||||
]
|
]
|
||||||
if self.mw.col.decks.current()["dyn"]:
|
if self.mw.col.decks.current()["dyn"]:
|
||||||
links.append(["R", "refresh", _("Rebuild")])
|
links.append(["R", "refresh", tr(TR.ACTIONS_REBUILD)])
|
||||||
links.append(["E", "empty", _("Empty")])
|
links.append(["E", "empty", tr(TR.STUDYING_EMPTY)])
|
||||||
else:
|
else:
|
||||||
links.append(["C", "studymore", _("Custom Study")])
|
links.append(["C", "studymore", tr(TR.ACTIONS_CUSTOM_STUDY)])
|
||||||
# links.append(["F", "cram", _("Filter/Cram")])
|
# links.append(["F", "cram", _("Filter/Cram")])
|
||||||
if self.mw.col.sched.haveBuried():
|
if self.mw.col.sched.haveBuried():
|
||||||
links.append(["U", "unbury", _("Unbury")])
|
links.append(["U", "unbury", tr(TR.STUDYING_UNBURY)])
|
||||||
buf = ""
|
buf = ""
|
||||||
for b in links:
|
for b in links:
|
||||||
if b[0]:
|
if b[0]:
|
||||||
b[0] = _("Shortcut key: %s") % shortcut(b[0])
|
b[0] = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % shortcut(b[0])
|
||||||
buf += """
|
buf += """
|
||||||
<button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple(
|
<button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple(
|
||||||
b
|
b
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import anki.lang
|
import anki.lang
|
||||||
import aqt
|
import aqt
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
|
@ -69,7 +68,9 @@ class Preferences(QDialog):
|
||||||
def onLangIdxChanged(self, idx):
|
def onLangIdxChanged(self, idx):
|
||||||
code = anki.lang.langs[idx][1]
|
code = anki.lang.langs[idx][1]
|
||||||
self.mw.pm.setLang(code)
|
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
|
# Collection options
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
@ -117,7 +118,7 @@ class Preferences(QDialog):
|
||||||
self.mw.pm.setGlMode("auto")
|
self.mw.pm.setGlMode("auto")
|
||||||
else:
|
else:
|
||||||
self.mw.pm.setGlMode("software")
|
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 = d.conf
|
||||||
qc["addToCur"] = not f.useCurrent.currentIndex()
|
qc["addToCur"] = not f.useCurrent.currentIndex()
|
||||||
|
|
@ -149,11 +150,7 @@ class Preferences(QDialog):
|
||||||
if haveNew == wantNew:
|
if haveNew == wantNew:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not askUser(
|
if not askUser(tr(TR.PREFERENCES_THIS_WILL_RESET_ANY_CARDS_IN)):
|
||||||
_(
|
|
||||||
"This will reset any cards in learning, clear filtered decks, and change the scheduler version. Proceed?"
|
|
||||||
)
|
|
||||||
):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if wantNew:
|
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())
|
self.mw.pm.set_interrupt_audio(self.form.interrupt_audio.isChecked())
|
||||||
|
|
||||||
if restart_required:
|
if restart_required:
|
||||||
showInfo(_("Changes will take effect when you restart Anki."))
|
showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
# mypy: check-untyped-defs
|
# mypy: check-untyped-defs
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
@ -26,7 +25,7 @@ from aqt.qt import (
|
||||||
from aqt.reviewer import replay_audio
|
from aqt.reviewer import replay_audio
|
||||||
from aqt.sound import av_player, play_clicked_audio
|
from aqt.sound import av_player, play_clicked_audio
|
||||||
from aqt.theme import theme_manager
|
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
|
from aqt.webview import AnkiWebView
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ class Previewer(QDialog):
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def _create_gui(self):
|
def _create_gui(self):
|
||||||
self.setWindowTitle(_("Preview"))
|
self.setWindowTitle(tr(TR.ACTIONS_PREVIEW))
|
||||||
|
|
||||||
qconnect(self.finished, self._on_finished)
|
qconnect(self.finished, self._on_finished)
|
||||||
self.silentlyClose = True
|
self.silentlyClose = True
|
||||||
|
|
@ -73,14 +72,14 @@ class Previewer(QDialog):
|
||||||
self.bbox = QDialogButtonBox()
|
self.bbox = QDialogButtonBox()
|
||||||
|
|
||||||
self._replay = self.bbox.addButton(
|
self._replay = self.bbox.addButton(
|
||||||
_("Replay Audio"), QDialogButtonBox.ActionRole
|
tr(TR.ACTIONS_REPLAY_AUDIO), QDialogButtonBox.ActionRole
|
||||||
)
|
)
|
||||||
self._replay.setAutoDefault(False)
|
self._replay.setAutoDefault(False)
|
||||||
self._replay.setShortcut(QKeySequence("R"))
|
self._replay.setShortcut(QKeySequence("R"))
|
||||||
self._replay.setToolTip(_("Shortcut key: %s" % "R"))
|
self._replay.setToolTip(_("Shortcut key: %s" % "R"))
|
||||||
qconnect(self._replay.clicked, self._on_replay_audio)
|
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.setShortcut(QKeySequence("B"))
|
||||||
both_sides_button.setToolTip(_("Shortcut key: %s" % "B"))
|
both_sides_button.setToolTip(_("Shortcut key: %s" % "B"))
|
||||||
self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole)
|
self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole)
|
||||||
|
|
@ -159,7 +158,7 @@ class Previewer(QDialog):
|
||||||
c = self.card()
|
c = self.card()
|
||||||
func = "_showQuestion"
|
func = "_showQuestion"
|
||||||
if not c:
|
if not c:
|
||||||
txt = _("(please select 1 card)")
|
txt = tr(TR.QT_MISC_PLEASE_SELECT_1_CARD)
|
||||||
bodyclass = ""
|
bodyclass = ""
|
||||||
self._last_state = None
|
self._last_state = None
|
||||||
else:
|
else:
|
||||||
|
|
@ -240,12 +239,12 @@ class MultiCardPreviewer(Previewer):
|
||||||
self._prev = self.bbox.addButton("<", QDialogButtonBox.ActionRole)
|
self._prev = self.bbox.addButton("<", QDialogButtonBox.ActionRole)
|
||||||
self._prev.setAutoDefault(False)
|
self._prev.setAutoDefault(False)
|
||||||
self._prev.setShortcut(QKeySequence("Left"))
|
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 = self.bbox.addButton(">", QDialogButtonBox.ActionRole)
|
||||||
self._next.setAutoDefault(True)
|
self._next.setAutoDefault(True)
|
||||||
self._next.setShortcut(QKeySequence("Right"))
|
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._prev.clicked, self._on_prev)
|
||||||
qconnect(self._next.clicked, self._on_next)
|
qconnect(self._next.clicked, self._on_next)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# 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 io
|
||||||
import locale
|
import locale
|
||||||
import pickle
|
import pickle
|
||||||
|
|
@ -28,6 +23,12 @@ from aqt import appHelpSite
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import TR, locale_dir, showWarning, tr
|
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(
|
metaConf = dict(
|
||||||
ver=0,
|
ver=0,
|
||||||
updates=True,
|
updates=True,
|
||||||
|
|
@ -251,7 +252,7 @@ class ProfileManager:
|
||||||
except:
|
except:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
None,
|
None,
|
||||||
_("Profile Corrupt"),
|
tr(TR.PROFILES_PROFILE_CORRUPT),
|
||||||
_(
|
_(
|
||||||
"""\
|
"""\
|
||||||
Anki could not read your profile data. Window sizes and your sync login \
|
Anki could not read your profile data. Window sizes and your sync login \
|
||||||
|
|
@ -304,12 +305,13 @@ details have been forgotten."""
|
||||||
oldFolder = midFolder
|
oldFolder = midFolder
|
||||||
else:
|
else:
|
||||||
showWarning(
|
showWarning(
|
||||||
_("Please remove the folder %s and try again.") % midFolder
|
tr(TR.PROFILES_PLEASE_REMOVE_THE_FOLDER_AND, val="%s")
|
||||||
|
% midFolder
|
||||||
)
|
)
|
||||||
self.name = oldName
|
self.name = oldName
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
showWarning(_("Folder already exists."))
|
showWarning(tr(TR.PROFILES_FOLDER_ALREADY_EXISTS))
|
||||||
self.name = oldName
|
self.name = oldName
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -476,7 +478,7 @@ create table if not exists profiles
|
||||||
|
|
||||||
def _ensureProfile(self) -> None:
|
def _ensureProfile(self) -> None:
|
||||||
"Create a new profile if none exists."
|
"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")
|
p = os.path.join(self.base, "README.txt")
|
||||||
with open(p, "w", encoding="utf8") as file:
|
with open(p, "w", encoding="utf8") as file:
|
||||||
file.write(
|
file.write(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
@ -10,6 +9,7 @@ from typing import Optional
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
from aqt.utils import TR, tr
|
||||||
|
|
||||||
# Progress info
|
# Progress info
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
@ -80,7 +80,7 @@ class ProgressManager:
|
||||||
if not parent and self.mw.isVisible():
|
if not parent and self.mw.isVisible():
|
||||||
parent = self.mw
|
parent = self.mw
|
||||||
|
|
||||||
label = label or _("Processing...")
|
label = label or tr(TR.QT_MISC_PROCESSING)
|
||||||
self._win = ProgressDialog(parent)
|
self._win = ProgressDialog(parent)
|
||||||
self._win.form.progressBar.setMinimum(min)
|
self._win.form.progressBar.setMinimum(min)
|
||||||
self._win.form.progressBar.setMaximum(max)
|
self._win.form.progressBar.setMaximum(max)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import difflib
|
import difflib
|
||||||
|
|
@ -22,7 +21,14 @@ from aqt.qt import *
|
||||||
from aqt.sound import av_player, getAudio, play_clicked_audio
|
from aqt.sound import av_player, getAudio, play_clicked_audio
|
||||||
from aqt.theme import theme_manager
|
from aqt.theme import theme_manager
|
||||||
from aqt.toolbar import BottomBar
|
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
|
from aqt.webview import AnkiWebView
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -92,8 +98,10 @@ class Reviewer:
|
||||||
)
|
)
|
||||||
mins = int(round(elapsed[0] / 60))
|
mins = int(round(elapsed[0] / 60))
|
||||||
part2 = ngettext("%s minute.", "%s minutes.", mins) % mins
|
part2 = ngettext("%s minute.", "%s minutes.", mins) % mins
|
||||||
fin = _("Finish")
|
fin = tr(TR.STUDYING_FINISH)
|
||||||
diag = askUserDialog("%s %s" % (part1, part2), [_("Continue"), fin])
|
diag = askUserDialog(
|
||||||
|
"%s %s" % (part1, part2), [tr(TR.STUDYING_CONTINUE), fin]
|
||||||
|
)
|
||||||
diag.setIcon(QMessageBox.Information)
|
diag.setIcon(QMessageBox.Information)
|
||||||
if diag.run() == fin:
|
if diag.run() == fin:
|
||||||
return self.mw.moveToState("deckBrowser")
|
return self.mw.moveToState("deckBrowser")
|
||||||
|
|
@ -388,7 +396,7 @@ class Reviewer:
|
||||||
Please run Tools>Empty Cards"""
|
Please run Tools>Empty Cards"""
|
||||||
)
|
)
|
||||||
else:
|
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)
|
return re.sub(self.typeAnsPat, warn, buf)
|
||||||
else:
|
else:
|
||||||
# empty field, remove type answer pattern
|
# empty field, remove type answer pattern
|
||||||
|
|
@ -569,9 +577,9 @@ time = %(time)d;
|
||||||
</script>
|
</script>
|
||||||
""" % dict(
|
""" % dict(
|
||||||
rem=self._remaining(),
|
rem=self._remaining(),
|
||||||
edit=_("Edit"),
|
edit=tr(TR.STUDYING_EDIT),
|
||||||
editkey=_("Shortcut key: %s") % "E",
|
editkey=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "E",
|
||||||
more=_("More"),
|
more=tr(TR.STUDYING_MORE),
|
||||||
downArrow=downArrow(),
|
downArrow=downArrow(),
|
||||||
time=self.card.timeTaken() // 1000,
|
time=self.card.timeTaken() // 1000,
|
||||||
)
|
)
|
||||||
|
|
@ -581,8 +589,8 @@ time = %(time)d;
|
||||||
<span class=stattxt>%s</span><br>
|
<span class=stattxt>%s</span><br>
|
||||||
<button title="%s" id="ansbut" class="focus" onclick='pycmd("ans");'>%s</button>""" % (
|
<button title="%s" id="ansbut" class="focus" onclick='pycmd("ans");'>%s</button>""" % (
|
||||||
self._remaining(),
|
self._remaining(),
|
||||||
_("Shortcut key: %s") % _("Space"),
|
tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % tr(TR.STUDYING_SPACE),
|
||||||
_("Show Answer"),
|
tr(TR.STUDYING_SHOW_ANSWER),
|
||||||
)
|
)
|
||||||
# wrap it in a table so it has the same top margin as the ease buttons
|
# wrap it in a table so it has the same top margin as the ease buttons
|
||||||
middle = (
|
middle = (
|
||||||
|
|
@ -626,17 +634,21 @@ time = %(time)d;
|
||||||
button_count = self.mw.col.sched.answerButtons(self.card)
|
button_count = self.mw.col.sched.answerButtons(self.card)
|
||||||
if button_count == 2:
|
if button_count == 2:
|
||||||
buttons_tuple: Tuple[Tuple[int, str], ...] = (
|
buttons_tuple: Tuple[Tuple[int, str], ...] = (
|
||||||
(1, _("Again")),
|
(1, tr(TR.STUDYING_AGAIN)),
|
||||||
(2, _("Good")),
|
(2, tr(TR.STUDYING_GOOD)),
|
||||||
)
|
)
|
||||||
elif button_count == 3:
|
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:
|
else:
|
||||||
buttons_tuple = (
|
buttons_tuple = (
|
||||||
(1, _("Again")),
|
(1, tr(TR.STUDYING_AGAIN)),
|
||||||
(2, _("Hard")),
|
(2, tr(TR.STUDYING_HARD)),
|
||||||
(3, _("Good")),
|
(3, tr(TR.STUDYING_GOOD)),
|
||||||
(4, _("Easy")),
|
(4, tr(TR.STUDYING_EASY)),
|
||||||
)
|
)
|
||||||
buttons_tuple = gui_hooks.reviewer_will_init_answer_buttons(
|
buttons_tuple = gui_hooks.reviewer_will_init_answer_buttons(
|
||||||
buttons_tuple, self, self.card
|
buttons_tuple, self, self.card
|
||||||
|
|
@ -657,7 +669,7 @@ time = %(time)d;
|
||||||
%s</button></td>""" % (
|
%s</button></td>""" % (
|
||||||
due,
|
due,
|
||||||
extra,
|
extra,
|
||||||
_("Shortcut key: %s") % i,
|
tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % i,
|
||||||
i,
|
i,
|
||||||
i,
|
i,
|
||||||
label,
|
label,
|
||||||
|
|
@ -682,9 +694,9 @@ time = %(time)d;
|
||||||
|
|
||||||
def onLeech(self, card: Card) -> None:
|
def onLeech(self, card: Card) -> None:
|
||||||
# for now
|
# for now
|
||||||
s = _("Card was a leech.")
|
s = tr(TR.STUDYING_CARD_WAS_A_LEECH)
|
||||||
if card.queue < 0:
|
if card.queue < 0:
|
||||||
s += " " + _("It has been suspended.")
|
s += " " + tr(TR.STUDYING_IT_HAS_BEEN_SUSPENDED)
|
||||||
tooltip(s)
|
tooltip(s)
|
||||||
|
|
||||||
# Context menu
|
# Context menu
|
||||||
|
|
@ -695,48 +707,48 @@ time = %(time)d;
|
||||||
currentFlag = self.card and self.card.userFlag()
|
currentFlag = self.card and self.card.userFlag()
|
||||||
opts = [
|
opts = [
|
||||||
[
|
[
|
||||||
_("Flag Card"),
|
tr(TR.STUDYING_FLAG_CARD),
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
_("Red Flag"),
|
tr(TR.ACTIONS_RED_FLAG),
|
||||||
"Ctrl+1",
|
"Ctrl+1",
|
||||||
lambda: self.setFlag(1),
|
lambda: self.setFlag(1),
|
||||||
dict(checked=currentFlag == 1),
|
dict(checked=currentFlag == 1),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
_("Orange Flag"),
|
tr(TR.ACTIONS_ORANGE_FLAG),
|
||||||
"Ctrl+2",
|
"Ctrl+2",
|
||||||
lambda: self.setFlag(2),
|
lambda: self.setFlag(2),
|
||||||
dict(checked=currentFlag == 2),
|
dict(checked=currentFlag == 2),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
_("Green Flag"),
|
tr(TR.ACTIONS_GREEN_FLAG),
|
||||||
"Ctrl+3",
|
"Ctrl+3",
|
||||||
lambda: self.setFlag(3),
|
lambda: self.setFlag(3),
|
||||||
dict(checked=currentFlag == 3),
|
dict(checked=currentFlag == 3),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
_("Blue Flag"),
|
tr(TR.ACTIONS_BLUE_FLAG),
|
||||||
"Ctrl+4",
|
"Ctrl+4",
|
||||||
lambda: self.setFlag(4),
|
lambda: self.setFlag(4),
|
||||||
dict(checked=currentFlag == 4),
|
dict(checked=currentFlag == 4),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[_("Mark Note"), "*", self.onMark],
|
[tr(TR.STUDYING_MARK_NOTE), "*", self.onMark],
|
||||||
[_("Bury Card"), "-", self.onBuryCard],
|
[tr(TR.STUDYING_BURY_CARD), "-", self.onBuryCard],
|
||||||
[_("Bury Note"), "=", self.onBuryNote],
|
[tr(TR.STUDYING_BURY_NOTE), "=", self.onBuryNote],
|
||||||
[_("Suspend Card"), "@", self.onSuspendCard],
|
[tr(TR.ACTIONS_SUSPEND_CARD), "@", self.onSuspendCard],
|
||||||
[_("Suspend Note"), "!", self.onSuspend],
|
[tr(TR.STUDYING_SUSPEND_NOTE), "!", self.onSuspend],
|
||||||
[_("Delete Note"), "Ctrl+Delete", self.onDelete],
|
[tr(TR.STUDYING_DELETE_NOTE), "Ctrl+Delete", self.onDelete],
|
||||||
[_("Options"), "O", self.onOptions],
|
[tr(TR.ACTIONS_OPTIONS), "O", self.onOptions],
|
||||||
None,
|
None,
|
||||||
[_("Replay Audio"), "R", self.replayAudio],
|
[tr(TR.ACTIONS_REPLAY_AUDIO), "R", self.replayAudio],
|
||||||
[_("Pause Audio"), "5", self.on_pause_audio],
|
[tr(TR.STUDYING_PAUSE_AUDIO), "5", self.on_pause_audio],
|
||||||
[_("Audio -5s"), "6", self.on_seek_backward],
|
[tr(TR.STUDYING_AUDIO_5S), "6", self.on_seek_backward],
|
||||||
[_("Audio +5s"), "7", self.on_seek_forward],
|
[tr(TR.STUDYING_AUDIO_AND5S), "7", self.on_seek_forward],
|
||||||
[_("Record Own Voice"), "Shift+V", self.onRecordVoice],
|
[tr(TR.STUDYING_RECORD_OWN_VOICE), "Shift+V", self.onRecordVoice],
|
||||||
[_("Replay Own Voice"), "V", self.onReplayRecorded],
|
[tr(TR.STUDYING_REPLAY_OWN_VOICE), "V", self.onReplayRecorded],
|
||||||
]
|
]
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
|
|
@ -793,15 +805,15 @@ time = %(time)d;
|
||||||
self._drawMark()
|
self._drawMark()
|
||||||
|
|
||||||
def onSuspend(self) -> None:
|
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()])
|
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()
|
self.mw.reset()
|
||||||
|
|
||||||
def onSuspendCard(self) -> None:
|
def onSuspendCard(self) -> None:
|
||||||
self.mw.checkpoint(_("Suspend"))
|
self.mw.checkpoint(tr(TR.STUDYING_SUSPEND))
|
||||||
self.mw.col.sched.suspend_cards([self.card.id])
|
self.mw.col.sched.suspend_cards([self.card.id])
|
||||||
tooltip(_("Card suspended."))
|
tooltip(tr(TR.STUDYING_CARD_SUSPENDED))
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
|
|
||||||
def onDelete(self) -> None:
|
def onDelete(self) -> None:
|
||||||
|
|
@ -809,7 +821,7 @@ time = %(time)d;
|
||||||
# window
|
# window
|
||||||
if self.mw.state != "review" or not self.card:
|
if self.mw.state != "review" or not self.card:
|
||||||
return
|
return
|
||||||
self.mw.checkpoint(_("Delete"))
|
self.mw.checkpoint(tr(TR.ACTIONS_DELETE))
|
||||||
cnt = len(self.card.note().cards())
|
cnt = len(self.card.note().cards())
|
||||||
self.mw.col.remove_notes([self.card.note().id])
|
self.mw.col.remove_notes([self.card.note().id])
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
|
|
@ -821,16 +833,16 @@ time = %(time)d;
|
||||||
)
|
)
|
||||||
|
|
||||||
def onBuryCard(self) -> None:
|
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.col.sched.bury_cards([self.card.id])
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
tooltip(_("Card buried."))
|
tooltip(tr(TR.STUDYING_CARD_BURIED))
|
||||||
|
|
||||||
def onBuryNote(self) -> None:
|
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.col.sched.bury_note(self.card.note())
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
tooltip(_("Note buried."))
|
tooltip(tr(TR.STUDYING_NOTE_BURIED))
|
||||||
|
|
||||||
def onRecordVoice(self) -> None:
|
def onRecordVoice(self) -> None:
|
||||||
self._recordedAudio = getAudio(self.mw, encode=False)
|
self._recordedAudio = getAudio(self.mw, encode=False)
|
||||||
|
|
@ -838,6 +850,6 @@ time = %(time)d;
|
||||||
|
|
||||||
def onReplayRecorded(self) -> None:
|
def onReplayRecorded(self) -> None:
|
||||||
if not self._recordedAudio:
|
if not self._recordedAudio:
|
||||||
tooltip(_("You haven't recorded your voice yet."))
|
tooltip(tr(TR.STUDYING_YOU_HAVENT_RECORDED_YOUR_VOICE_YET))
|
||||||
return
|
return
|
||||||
av_player.play_file(self._recordedAudio)
|
av_player.play_file(self._recordedAudio)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
@ -10,7 +9,7 @@ import aqt
|
||||||
from anki.errors import DeckRenameError
|
from anki.errors import DeckRenameError
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import getOnlyText, showWarning
|
from aqt.utils import TR, getOnlyText, showWarning, tr
|
||||||
|
|
||||||
|
|
||||||
class SidebarItemType(Enum):
|
class SidebarItemType(Enum):
|
||||||
|
|
@ -71,7 +70,7 @@ class NewSidebarTreeView(SidebarTreeViewBase):
|
||||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
self.customContextMenuRequested.connect(self.onContextMenu) # type: ignore
|
self.customContextMenuRequested.connect(self.onContextMenu) # type: ignore
|
||||||
self.context_menus = {
|
self.context_menus = {
|
||||||
SidebarItemType.DECK: ((_("Rename"), self.rename_deck),),
|
SidebarItemType.DECK: ((tr(TR.ACTIONS_RENAME), self.rename_deck),),
|
||||||
}
|
}
|
||||||
|
|
||||||
def onContextMenu(self, point: QPoint) -> None:
|
def onContextMenu(self, point: QPoint) -> None:
|
||||||
|
|
@ -92,10 +91,10 @@ class NewSidebarTreeView(SidebarTreeViewBase):
|
||||||
m.exec_(QCursor.pos())
|
m.exec_(QCursor.pos())
|
||||||
|
|
||||||
def rename_deck(self, item: "aqt.browser.SidebarItem") -> None:
|
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)
|
deck = self.mw.col.decks.get(item.id)
|
||||||
old_name = deck["name"]
|
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('"', "")
|
new_name = new_name.replace('"', "")
|
||||||
if not new_name or new_name == old_name:
|
if not new_name or new_name == old_name:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from aqt import gui_hooks
|
||||||
from aqt.mpv import MPV, MPVBase, MPVCommandError
|
from aqt.mpv import MPV, MPVBase, MPVCommandError
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.taskman import TaskManager
|
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:
|
try:
|
||||||
import pyaudio
|
import pyaudio
|
||||||
|
|
@ -318,11 +318,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
|
||||||
try:
|
try:
|
||||||
ret.result()
|
ret.result()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
showWarning(
|
showWarning(tr(TR.MEDIA_SOUND_AND_VIDEO_ON_CARDS_WILL))
|
||||||
_(
|
|
||||||
"Sound and video on cards will not function until mpv or mplayer is installed."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# must call cb() here, as we don't currently have another way
|
# must call cb() here, as we don't currently have another way
|
||||||
# to flag to av_player that we've stopped
|
# to flag to av_player that we've stopped
|
||||||
cb()
|
cb()
|
||||||
|
|
@ -499,7 +495,7 @@ class _Recorder:
|
||||||
finally:
|
finally:
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
if ret:
|
if ret:
|
||||||
raise Exception(_("Error running %s") % " ".join(cmd))
|
raise Exception(tr(TR.MEDIA_ERROR_RUNNING, val="%s") % " ".join(cmd))
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
if os.path.exists(processingSrc):
|
if os.path.exists(processingSrc):
|
||||||
|
|
@ -596,10 +592,10 @@ def getAudio(parent: QWidget, encode: bool = True) -> Optional[str]:
|
||||||
restoreGeom(mb, "audioRecorder")
|
restoreGeom(mb, "audioRecorder")
|
||||||
mb.setWindowTitle("Anki")
|
mb.setWindowTitle("Anki")
|
||||||
mb.setIconPixmap(QPixmap(":/icons/media-record.png"))
|
mb.setIconPixmap(QPixmap(":/icons/media-record.png"))
|
||||||
but = QPushButton(_("Save"))
|
but = QPushButton(tr(TR.ACTIONS_SAVE))
|
||||||
mb.addButton(but, QMessageBox.AcceptRole)
|
mb.addButton(but, QMessageBox.AcceptRole)
|
||||||
but.setDefault(True)
|
but.setDefault(True)
|
||||||
but = QPushButton(_("Cancel"))
|
but = QPushButton(tr(TR.ACTIONS_CANCEL))
|
||||||
mb.addButton(but, QMessageBox.RejectRole)
|
mb.addButton(but, QMessageBox.RejectRole)
|
||||||
mb.setEscapeButton(but)
|
mb.setEscapeButton(but)
|
||||||
t = time.time()
|
t = time.time()
|
||||||
|
|
@ -607,7 +603,7 @@ def getAudio(parent: QWidget, encode: bool = True) -> Optional[str]:
|
||||||
time.sleep(r.startupDelay)
|
time.sleep(r.startupDelay)
|
||||||
QApplication.instance().processEvents() # type: ignore
|
QApplication.instance().processEvents() # type: ignore
|
||||||
while not mb.clickedButton():
|
while not mb.clickedButton():
|
||||||
txt = _("Recording...<br>Time: %0.1f")
|
txt = tr(TR.MEDIA_RECORDINGTIME)
|
||||||
mb.setText(txt % (time.time() - t))
|
mb.setText(txt % (time.time() - t))
|
||||||
mb.show()
|
mb.show()
|
||||||
QApplication.instance().processEvents() # type: ignore
|
QApplication.instance().processEvents() # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
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):
|
class StudyDeck(QDialog):
|
||||||
|
|
@ -43,9 +51,9 @@ class StudyDeck(QDialog):
|
||||||
for b in buttons:
|
for b in buttons:
|
||||||
self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole)
|
self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole)
|
||||||
else:
|
else:
|
||||||
b = QPushButton(_("Add"))
|
b = QPushButton(tr(TR.ACTIONS_ADD))
|
||||||
b.setShortcut(QKeySequence("Ctrl+N"))
|
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)
|
self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole)
|
||||||
qconnect(b.clicked, self.onAddDeck)
|
qconnect(b.clicked, self.onAddDeck)
|
||||||
if title:
|
if title:
|
||||||
|
|
@ -64,7 +72,7 @@ class StudyDeck(QDialog):
|
||||||
self.origNames = names()
|
self.origNames = names()
|
||||||
self.name = None
|
self.name = None
|
||||||
self.ok = self.form.buttonBox.addButton(
|
self.ok = self.form.buttonBox.addButton(
|
||||||
accept or _("Study"), QDialogButtonBox.AcceptRole
|
accept or tr(TR.DECKS_STUDY), QDialogButtonBox.AcceptRole
|
||||||
)
|
)
|
||||||
self.setWindowModality(Qt.WindowModal)
|
self.setWindowModality(Qt.WindowModal)
|
||||||
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp(help))
|
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp(help))
|
||||||
|
|
@ -132,7 +140,7 @@ class StudyDeck(QDialog):
|
||||||
gui_hooks.state_did_reset.remove(self.onReset)
|
gui_hooks.state_did_reset.remove(self.onReset)
|
||||||
row = self.form.list.currentRow()
|
row = self.form.list.currentRow()
|
||||||
if row < 0:
|
if row < 0:
|
||||||
showInfo(_("Please select something."))
|
showInfo(tr(TR.DECKS_PLEASE_SELECT_SOMETHING))
|
||||||
return
|
return
|
||||||
self.name = self.names[self.form.list.currentRow()]
|
self.name = self.names[self.form.list.currentRow()]
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
@ -148,7 +156,7 @@ class StudyDeck(QDialog):
|
||||||
default = self.form.filter.text()
|
default = self.form.filter.text()
|
||||||
else:
|
else:
|
||||||
default = self.names[self.form.list.currentRow()]
|
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()
|
n = n.strip()
|
||||||
if n:
|
if n:
|
||||||
did = self.mw.col.decks.id(n)
|
did = self.mw.col.decks.id(n)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
@ -12,6 +11,7 @@ from anki.rsbackend import SyncStatus
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sync import get_sync_status
|
from aqt.sync import get_sync_status
|
||||||
|
from aqt.utils import TR, tr
|
||||||
from aqt.webview import AnkiWebView
|
from aqt.webview import AnkiWebView
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -102,30 +102,30 @@ class Toolbar:
|
||||||
links = [
|
links = [
|
||||||
self.create_link(
|
self.create_link(
|
||||||
"decks",
|
"decks",
|
||||||
_("Decks"),
|
tr(TR.ACTIONS_DECKS),
|
||||||
self._deckLinkHandler,
|
self._deckLinkHandler,
|
||||||
tip=_("Shortcut key: %s") % "D",
|
tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "D",
|
||||||
id="decks",
|
id="decks",
|
||||||
),
|
),
|
||||||
self.create_link(
|
self.create_link(
|
||||||
"add",
|
"add",
|
||||||
_("Add"),
|
tr(TR.ACTIONS_ADD),
|
||||||
self._addLinkHandler,
|
self._addLinkHandler,
|
||||||
tip=_("Shortcut key: %s") % "A",
|
tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "A",
|
||||||
id="add",
|
id="add",
|
||||||
),
|
),
|
||||||
self.create_link(
|
self.create_link(
|
||||||
"browse",
|
"browse",
|
||||||
_("Browse"),
|
tr(TR.QT_MISC_BROWSE),
|
||||||
self._browseLinkHandler,
|
self._browseLinkHandler,
|
||||||
tip=_("Shortcut key: %s") % "B",
|
tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "B",
|
||||||
id="browse",
|
id="browse",
|
||||||
),
|
),
|
||||||
self.create_link(
|
self.create_link(
|
||||||
"stats",
|
"stats",
|
||||||
_("Stats"),
|
tr(TR.QT_MISC_STATS),
|
||||||
self._statsLinkHandler,
|
self._statsLinkHandler,
|
||||||
tip=_("Shortcut key: %s") % "T",
|
tip=tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "T",
|
||||||
id="stats",
|
id="stats",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
@ -140,8 +140,8 @@ class Toolbar:
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def _create_sync_link(self) -> str:
|
def _create_sync_link(self) -> str:
|
||||||
name = _("Sync")
|
name = tr(TR.QT_MISC_SYNC)
|
||||||
title = _("Shortcut key: %s") % "Y"
|
title = tr(TR.ACTIONS_SHORTCUT_KEY, val="%s") % "Y"
|
||||||
label = "sync"
|
label = "sync"
|
||||||
self.link_handlers[label] = self._syncLinkHandler
|
self.link_handlers[label] = self._syncLinkHandler
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import aqt
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.utils import platDesc, versionWithBuild
|
from anki.utils import platDesc, versionWithBuild
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import openLink, showText
|
from aqt.utils import TR, openLink, showText, tr
|
||||||
|
|
||||||
|
|
||||||
class LatestVersionFinder(QThread):
|
class LatestVersionFinder(QThread):
|
||||||
|
|
@ -56,12 +56,12 @@ class LatestVersionFinder(QThread):
|
||||||
|
|
||||||
|
|
||||||
def askAndUpdate(mw, ver):
|
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 = QMessageBox(mw)
|
||||||
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) # type: ignore
|
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) # type: ignore
|
||||||
msg.setIcon(QMessageBox.Information)
|
msg.setIcon(QMessageBox.Information)
|
||||||
msg.setText(baseStr + _("Would you like to download it now?"))
|
msg.setText(baseStr + tr(TR.QT_MISC_WOULD_YOU_LIKE_TO_DOWNLOAD_IT))
|
||||||
button = QPushButton(_("Ignore this update"))
|
button = QPushButton(tr(TR.QT_MISC_IGNORE_THIS_UPDATE))
|
||||||
msg.addButton(button, QMessageBox.RejectRole)
|
msg.addButton(button, QMessageBox.RejectRole)
|
||||||
msg.setDefaultButton(QMessageBox.Yes)
|
msg.setDefaultButton(QMessageBox.Yes)
|
||||||
ret = msg.exec_()
|
ret = msg.exec_()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
@ -54,7 +53,7 @@ def openHelp(section):
|
||||||
|
|
||||||
|
|
||||||
def openLink(link):
|
def openLink(link):
|
||||||
tooltip(_("Loading..."), period=1000)
|
tooltip(tr(TR.QT_MISC_LOADING), period=1000)
|
||||||
with noBundledLibs():
|
with noBundledLibs():
|
||||||
QDesktopServices.openUrl(QUrl(link))
|
QDesktopServices.openUrl(QUrl(link))
|
||||||
|
|
||||||
|
|
@ -145,7 +144,7 @@ def showText(
|
||||||
def onCopy():
|
def onCopy():
|
||||||
QApplication.clipboard().setText(text.toPlainText())
|
QApplication.clipboard().setText(text.toPlainText())
|
||||||
|
|
||||||
btn = QPushButton(_("Copy to Clipboard"))
|
btn = QPushButton(tr(TR.QT_MISC_COPY_TO_CLIPBOARD))
|
||||||
qconnect(btn.clicked, onCopy)
|
qconnect(btn.clicked, onCopy)
|
||||||
box.addButton(btn, QDialogButtonBox.ActionRole)
|
box.addButton(btn, QDialogButtonBox.ActionRole)
|
||||||
|
|
||||||
|
|
@ -209,8 +208,8 @@ class ButtonedDialog(QMessageBox):
|
||||||
for b in buttons:
|
for b in buttons:
|
||||||
self._buttons.append(self.addButton(b, QMessageBox.AcceptRole))
|
self._buttons.append(self.addButton(b, QMessageBox.AcceptRole))
|
||||||
if help:
|
if help:
|
||||||
self.addButton(_("Help"), QMessageBox.HelpRole)
|
self.addButton(tr(TR.ACTIONS_HELP), QMessageBox.HelpRole)
|
||||||
buttons.append(_("Help"))
|
buttons.append(tr(TR.ACTIONS_HELP))
|
||||||
# self.setLayout(v)
|
# self.setLayout(v)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
@ -408,9 +407,7 @@ def getSaveFile(parent, title, dir_description, key, ext, fname=None):
|
||||||
aqt.mw.pm.profile[config_key] = dir
|
aqt.mw.pm.profile[config_key] = dir
|
||||||
# check if it exists
|
# check if it exists
|
||||||
if os.path.exists(file):
|
if os.path.exists(file):
|
||||||
if not askUser(
|
if not askUser(tr(TR.QT_MISC_THIS_FILE_EXISTS_ARE_YOU_SURE), parent):
|
||||||
_("This file exists. Are you sure you want to overwrite it?"), parent
|
|
||||||
):
|
|
||||||
return None
|
return None
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
|
@ -656,7 +653,7 @@ def closeTooltip():
|
||||||
def checkInvalidFilename(str, dirsep=True):
|
def checkInvalidFilename(str, dirsep=True):
|
||||||
bad = invalidFilename(str, dirsep)
|
bad = invalidFilename(str, dirsep)
|
||||||
if bad:
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
@ -14,7 +13,7 @@ from anki.utils import isLin, isMac, isWin
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.theme import theme_manager
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import openLink, showInfo
|
from aqt.utils import TR, openLink, showInfo, tr
|
||||||
|
|
||||||
serverbaseurl = re.compile(r"^.+:\/\/[^\/]+")
|
serverbaseurl = re.compile(r"^.+:\/\/[^\/]+")
|
||||||
|
|
||||||
|
|
@ -286,7 +285,7 @@ class AnkiWebView(QWebEngineView):
|
||||||
|
|
||||||
def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
|
def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
|
||||||
m = QMenu(self)
|
m = QMenu(self)
|
||||||
a = m.addAction(_("Copy"))
|
a = m.addAction(tr(TR.ACTIONS_COPY))
|
||||||
qconnect(a.triggered, self.onCopy)
|
qconnect(a.triggered, self.onCopy)
|
||||||
gui_hooks.webview_will_show_context_menu(self, m)
|
gui_hooks.webview_will_show_context_menu(self, m)
|
||||||
m.popup(QCursor.pos())
|
m.popup(QCursor.pos())
|
||||||
|
|
|
||||||
45
repos.bzl
45
repos.bzl
|
|
@ -129,38 +129,43 @@ def register_repos():
|
||||||
qtpo_i18n_commit = "872d7f0f6bde52577e8fc795dd85699b0eeb97d5"
|
qtpo_i18n_commit = "872d7f0f6bde52577e8fc795dd85699b0eeb97d5"
|
||||||
qtpo_i18n_shallow_since = "1605564627 +0000"
|
qtpo_i18n_shallow_since = "1605564627 +0000"
|
||||||
|
|
||||||
new_git_repository(
|
i18n_build_content = """
|
||||||
name = "rslib_ftl",
|
|
||||||
build_file_content = """
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "files",
|
name = "files",
|
||||||
srcs = glob(["**/*.ftl"]),
|
srcs = glob(["**/*.ftl"]),
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
exports_files(["l10n.toml"])
|
exports_files(["l10n.toml"])
|
||||||
""",
|
"""
|
||||||
commit = core_i18n_commit,
|
|
||||||
shallow_since = core_i18n_shallow_since,
|
native.new_local_repository(
|
||||||
remote = "https://github.com/ankitects/anki-core-i18n",
|
name = "rslib_ftl",
|
||||||
|
path = "../anki-i18n/core",
|
||||||
|
build_file_content = i18n_build_content,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 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",
|
||||||
|
# )
|
||||||
|
|
||||||
if not native.existing_rule("extra_ftl"):
|
if not native.existing_rule("extra_ftl"):
|
||||||
new_git_repository(
|
native.new_local_repository(
|
||||||
name = "extra_ftl",
|
name = "extra_ftl",
|
||||||
build_file_content = """
|
path = "../anki-i18n/qtftl",
|
||||||
filegroup(
|
build_file_content = i18n_build_content,
|
||||||
name = "files",
|
|
||||||
srcs = glob(["**/*.ftl"]),
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
exports_files(["l10n.toml"])
|
# new_git_repository(
|
||||||
""",
|
# name = "extra_ftl",
|
||||||
commit = qtftl_i18n_commit,
|
# build_file_content = i18n_build_content,
|
||||||
shallow_since = qtftl_i18n_shallow_since,
|
# commit = qtftl_i18n_commit,
|
||||||
remote = "https://github.com/ankitects/anki-desktop-ftl",
|
# shallow_since = qtftl_i18n_shallow_since,
|
||||||
)
|
# remote = "https://github.com/ankitects/anki-desktop-ftl",
|
||||||
|
# )
|
||||||
|
|
||||||
new_git_repository(
|
new_git_repository(
|
||||||
name = "aqt_po",
|
name = "aqt_po",
|
||||||
|
|
|
||||||
32
rslib/ftl/actions.ftl
Normal file
32
rslib/ftl/actions.ftl
Normal 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
9
rslib/ftl/adding.ftl
Normal 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
104
rslib/ftl/browsing.ftl
Normal 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.
|
||||||
|
|
@ -21,3 +21,22 @@ card-templates-preview-settings = Options
|
||||||
card-templates-invalid-template-number = Card template { $number } has a problem.
|
card-templates-invalid-template-number = Card template { $number } has a problem.
|
||||||
card-templates-changes-saved = Changes saved.
|
card-templates-changes-saved = Changes saved.
|
||||||
card-templates-discard-changes = Discard changes?
|
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...
|
||||||
|
|
|
||||||
|
|
@ -1 +1,28 @@
|
||||||
custom-study-must-rename-deck = Please rename the existing Custom Study deck first.
|
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
33
rslib/ftl/decks.ftl
Normal 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
35
rslib/ftl/editing.ftl
Normal 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
17
rslib/ftl/exporting.ftl
Normal 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
14
rslib/ftl/fields.ftl
Normal 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.
|
||||||
|
|
@ -1 +1,49 @@
|
||||||
importing-failed-debug-info = Import failed. Debugging info:
|
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
7
rslib/ftl/media.ftl
Normal 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.
|
||||||
|
|
@ -19,3 +19,18 @@ notetypes-cloze-name = Cloze
|
||||||
notetypes-card-1-name = Card 1
|
notetypes-card-1-name = Card 1
|
||||||
notetypes-card-2-name = Card 2
|
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
39
rslib/ftl/preferences.ftl
Normal 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
10
rslib/ftl/profiles.ftl
Normal 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
|
||||||
|
|
@ -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
|
# used in scheduling-how-to-custom-study
|
||||||
# "... you can use the custom study feature."
|
# "... you can use the custom study feature."
|
||||||
scheduling-custom-study = custom study
|
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
44
rslib/ftl/studying.ftl
Normal 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.
|
||||||
Loading…
Reference in a new issue