diff --git a/qt/aqt/mediacheck.py b/qt/aqt/mediacheck.py index ac5581ddf..8fb68118d 100644 --- a/qt/aqt/mediacheck.py +++ b/qt/aqt/mediacheck.py @@ -10,10 +10,15 @@ from typing import Iterable, List, Optional, TypeVar import aqt from anki import hooks -from anki.lang import _, ngettext -from anki.rsbackend import Interrupted, MediaCheckOutput, Progress, ProgressKind +from anki.rsbackend import ( + Interrupted, + MediaCheckOutput, + Progress, + ProgressKind, + StringsGroup, +) from aqt.qt import * -from aqt.utils import askUser, restoreGeom, saveGeom, showText, tooltip +from aqt.utils import askUser, restoreGeom, saveGeom, showText, tooltip, tr T = TypeVar("T") @@ -84,14 +89,14 @@ class MediaChecker: layout.addWidget(box) if output.unused: - b = QPushButton(_("Delete Unused Files")) + b = QPushButton(tr(StringsGroup.MEDIA_CHECK, "delete-unused")) b.setAutoDefault(False) box.addButton(b, QDialogButtonBox.RejectRole) b.clicked.connect(lambda c: self._on_trash_files(output.unused)) # type: ignore if output.missing: if any(map(lambda x: x.startswith("latex-"), output.missing)): - b = QPushButton(_("Render LaTeX")) + b = QPushButton(tr(StringsGroup.MEDIA_CHECK, "render-latex")) b.setAutoDefault(False) box.addButton(b, QDialogButtonBox.RejectRole) b.clicked.connect(self._on_render_latex) # type: ignore @@ -120,37 +125,34 @@ class MediaChecker: browser.onSearchActivated() showText(err, type="html") else: - tooltip(_("All LaTeX rendered.")) + tooltip(tr(StringsGroup.MEDIA_CHECK, "all-latex-rendered")) def _on_render_latex_progress(self, count: int) -> bool: if self.progress_dialog.wantCancel: return False - self.mw.progress.update(_("Checked {}...").format(count)) + self.mw.progress.update(tr(StringsGroup.MEDIA_CHECK, "checked", count=count)) return True def _on_trash_files(self, fnames: List[str]): - if not askUser(_("Delete unused media?")): + if not askUser(tr(StringsGroup.MEDIA_CHECK, "delete-unused-confirm")): return self.progress_dialog = self.mw.progress.start() last_progress = time.time() remaining = len(fnames) + total = len(fnames) try: for chunk in chunked_list(fnames, 25): self.mw.col.media.trash_files(chunk) remaining -= len(chunk) if time.time() - last_progress >= 0.3: - label = ( - ngettext( - "%d file remaining...", "%d files remaining...", remaining, - ) - % remaining + self.mw.progress.update( + tr(StringsGroup.MEDIA_CHECK, "files-remaining", count=remaining) ) - self.mw.progress.update(label) finally: self.mw.progress.finish() self.progress_dialog = None - tooltip(_("Files moved to trash.")) + tooltip(tr(StringsGroup.MEDIA_CHECK, "delete-unused-complete", count=total)) diff --git a/qt/aqt/mediasync.py b/qt/aqt/mediasync.py index 3aba35380..47821f698 100644 --- a/qt/aqt/mediasync.py +++ b/qt/aqt/mediasync.py @@ -19,6 +19,7 @@ from anki.rsbackend import ( NetworkErrorKind, Progress, ProgressKind, + StringsGroup, SyncError, SyncErrorKind, ) @@ -26,7 +27,7 @@ from anki.types import assert_impossible from anki.utils import intTime from aqt import gui_hooks from aqt.qt import QDialog, QDialogButtonBox, QPushButton -from aqt.utils import showWarning +from aqt.utils import showWarning, tr LogEntry = Union[MediaSyncProgress, str] @@ -68,10 +69,10 @@ class MediaSyncer: return if not self.mw.pm.media_syncing_enabled(): - self._log_and_notify(_("Media syncing disabled.")) + self._log_and_notify(tr(StringsGroup.SYNC, "media-disabled")) return - self._log_and_notify(_("Media sync starting...")) + self._log_and_notify(tr(StringsGroup.SYNC, "media-starting")) self._syncing = True self._want_stop = False gui_hooks.media_sync_did_start_or_stop(True) @@ -104,14 +105,14 @@ class MediaSyncer: if exc is not None: self._handle_sync_error(exc) else: - self._log_and_notify(_("Media sync complete.")) + self._log_and_notify(tr(StringsGroup.SYNC, "media-complete")) def _handle_sync_error(self, exc: BaseException): if isinstance(exc, Interrupted): - self._log_and_notify(_("Media sync aborted.")) + self._log_and_notify(tr(StringsGroup.SYNC, "media-aborted")) return - self._log_and_notify(_("Media sync failed.")) + self._log_and_notify(tr(StringsGroup.SYNC, "media-failed")) if isinstance(exc, SyncError): kind = exc.kind() if kind == SyncErrorKind.AUTH_FAILED: @@ -156,7 +157,7 @@ class MediaSyncer: def abort(self) -> None: if not self.is_syncing(): return - self._log_and_notify(_("Media sync aborting...")) + self._log_and_notify(tr(StringsGroup.SYNC, "media-aborting")) self._want_stop = True def is_syncing(self) -> bool: @@ -199,7 +200,7 @@ class MediaSyncDialog(QDialog): self._close_when_done = close_when_done self.form = aqt.forms.synclog.Ui_Dialog() self.form.setupUi(self) - self.abort_button = QPushButton(_("Abort")) + self.abort_button = QPushButton(tr(StringsGroup.SYNC, "abort")) self.abort_button.clicked.connect(self._on_abort) # type: ignore self.abort_button.setAutoDefault(False) self.form.buttonBox.addButton(self.abort_button, QDialogButtonBox.ActionRole) diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 318f48b07..b01cc7442 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -1,14 +1,18 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +from __future__ import annotations + import os import re import subprocess import sys -from typing import Any, Optional +from typing import Any, Optional, Union import aqt from anki.lang import _ +from anki.rsbackend import StringsGroup from anki.utils import invalidFilename, isMac, isWin, noBundledLibs, versionWithBuild from aqt.qt import * from aqt.theme import theme_manager @@ -27,6 +31,12 @@ def locale_dir() -> str: return os.path.join(aqt_data_folder(), "locale") +def tr(group: StringsGroup, key: str, **kwargs: Union[str, int, float]) -> str: + """Shortcut to access translations from the backend. + (Currently) requires an open collection.""" + return aqt.mw.col.backend.translate(group, key, **kwargs) + + def openHelp(section): link = aqt.appHelpSite if section: diff --git a/rslib/src/i18n/media-check.ftl b/rslib/src/i18n/media-check.ftl index 96326554a..5195ee0c0 100644 --- a/rslib/src/i18n/media-check.ftl +++ b/rslib/src/i18n/media-check.ftl @@ -19,3 +19,17 @@ missing-file = Missing: {$filename} unused-file = Unused: {$filename} checked = Checked {$count}... + +delete-unused = Delete Unused +delete-unused-confirm = Delete unused media? +files-remaining = {$count -> + [one] 1 file + *[other] {$count} files + } remaining. +delete-unused-complete = {$count -> + [one] 1 file + *[other] {$count} files + } moved to the trash. + +render-latex = Render LaTeX +all-latex-rendered = All LaTeX rendered. diff --git a/rslib/src/i18n/sync.ftl b/rslib/src/i18n/sync.ftl index df3de8703..71d0f1b28 100644 --- a/rslib/src/i18n/sync.ftl +++ b/rslib/src/i18n/sync.ftl @@ -1,3 +1,16 @@ +### Messages shown when synchronizing with AnkiWeb. + +## Media synchronization + media-added-count = Added: {$up}↑ {$down}↓ media-removed-count = Removed: {$up}↑ {$down}↓ media-checked-count = Checked: {$count} + +media-starting = Media sync starting... +media-complete = Media sync complete. +media-failed = Media sync failed. +media-aborting = Media sync aborting... +media-aborted = Media sync aborted. +media-disabled = Media sync disabled. + +abort-button = Abort