diff --git a/pylib/anki/lang.py b/pylib/anki/lang.py index 629029017..e679b5d85 100644 --- a/pylib/anki/lang.py +++ b/pylib/anki/lang.py @@ -145,7 +145,7 @@ current_catalog: Optional[ ] = None # the current Fluent translation instance -current_i18n: Optional[anki.rsbackend.I18nBackend] +current_i18n: Optional[anki.rsbackend.RustBackend] # path to locale folder locale_folder = "" @@ -175,9 +175,9 @@ def set_lang(lang: str, locale_dir: str) -> None: current_catalog = gettext.translation( "anki", gettext_dir, languages=[lang], fallback=True ) - current_i18n = anki.rsbackend.I18nBackend( - preferred_langs=[lang], ftl_folder=ftl_dir - ) + + current_i18n = anki.rsbackend.RustBackend(ftl_folder=ftl_dir, langs=[lang]) + locale_folder = locale_dir diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 5b57224cc..a77e0f367 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -200,12 +200,20 @@ def _on_progress(progress_bytes: bytes) -> bool: class RustBackend: - def __init__(self, server: bool = False) -> None: - ftl_folder = os.path.join(anki.lang.locale_folder, "fluent") + def __init__( + self, + ftl_folder: Optional[str] = None, + langs: Optional[List[str]] = None, + server: bool = False, + ) -> None: + # pick up global defaults if not provided + if ftl_folder is None: + ftl_folder = os.path.join(anki.lang.locale_folder, "fluent") + if langs is None: + langs = [anki.lang.currentLang] + init_msg = pb.BackendInit( - locale_folder_path=ftl_folder, - preferred_langs=[anki.lang.currentLang], - server=server, + locale_folder_path=ftl_folder, preferred_langs=langs, server=server, ) self._backend = ankirspy.open_backend(init_msg.SerializeToString()) self._backend.set_progress_callback(_on_progress) @@ -428,19 +436,6 @@ def translate_string_in( return pb.TranslateStringIn(key=key, args=args) -class I18nBackend: - def __init__(self, preferred_langs: List[str], ftl_folder: str) -> None: - init_msg = pb.I18nBackendInit( - locale_folder_path=ftl_folder, preferred_langs=preferred_langs - ) - self._backend = ankirspy.open_i18n(init_msg.SerializeToString()) - - def translate(self, key: TR, **kwargs: Union[str, int, float]) -> str: - return self._backend.translate( - translate_string_in(key, **kwargs).SerializeToString() - ) - - # temporarily force logging of media handling if "RUST_LOG" not in os.environ: os.environ["RUST_LOG"] = "warn,anki::media=debug" diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 925250405..b7183af15 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -17,6 +17,7 @@ import anki.lang import aqt.buildinfo from anki import version as _version from anki.consts import HELP_SITE +from anki.rsbackend import RustBackend from anki.utils import checksum, isLin, isMac from aqt.qt import * from aqt.utils import locale_dir @@ -162,15 +163,15 @@ dialogs = DialogManager() # Qt requires its translator to be installed before any GUI widgets are # loaded, and we need the Qt language to match the gettext language or # translated shortcuts will not work. -# -# The Qt translator needs to be retained to work. +# A reference to the Qt translator needs to be held to prevent it from +# being immediately deallocated. _qtrans: Optional[QTranslator] = None -def setupLang( +def setupLangAndBackend( pm: ProfileManager, app: QApplication, force: Optional[str] = None -) -> None: +) -> RustBackend: global _qtrans try: locale.setlocale(locale.LC_ALL, "") @@ -218,6 +219,8 @@ def setupLang( if _qtrans.load("qtbase_" + qt_lang, qt_dir): app.installTranslator(_qtrans) + return anki.lang.current_i18n + # App initialisation ########################################################################## @@ -465,8 +468,8 @@ environment points to a valid, writable folder.""", if opts.profile: pm.openProfile(opts.profile) - # i18n - setupLang(pm, app, opts.lang) + # i18n & backend + backend = setupLangAndBackend(pm, app, opts.lang) if isLin and pm.glMode() == "auto": from aqt.utils import gfxDriverIsBroken @@ -483,7 +486,7 @@ environment points to a valid, writable folder.""", # load the main window import aqt.main - mw = aqt.main.AnkiQt(app, pm, opts, args) + mw = aqt.main.AnkiQt(app, pm, backend, opts, args) if exec: app.exec() else: diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 661608dcc..ed56974c3 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -29,6 +29,7 @@ from anki import hooks from anki.collection import _Collection from anki.hooks import runHook from anki.lang import _, ngettext +from anki.rsbackend import RustBackend from anki.sound import AVTag, SoundOrVideoTag from anki.storage import Collection from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields @@ -78,10 +79,12 @@ class AnkiQt(QMainWindow): self, app: QApplication, profileManager: ProfileManagerType, + backend: RustBackend, opts: Namespace, args: List[Any], ) -> None: QMainWindow.__init__(self) + self.backend = backend self.state = "startup" self.opts = opts self.col: Optional[_Collection] = None diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 0025e6f97..59de1e1fa 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -621,52 +621,3 @@ fn media_sync_progress(p: &MediaSyncProgress, i18n: &I18n) -> pb::MediaSyncProgr ), } } - -/// Standalone I18n backend -/// This is a hack to allow translating strings in the GUI -/// when a collection is not open, and in the future it should -/// either be shared with or merged into the backend object. -/////////////////////////////////////////////////////// - -pub struct I18nBackend { - i18n: I18n, -} - -pub fn init_i18n_backend(init_msg: &[u8]) -> Result { - let input: pb::I18nBackendInit = match pb::I18nBackendInit::decode(init_msg) { - Ok(req) => req, - Err(_) => return Err(AnkiError::invalid_input("couldn't decode init msg")), - }; - - let log = log::terminal(); - - let i18n = I18n::new(&input.preferred_langs, input.locale_folder_path, log); - - Ok(I18nBackend { i18n }) -} - -impl I18nBackend { - pub fn translate(&self, req: &[u8]) -> String { - let req = match pb::TranslateStringIn::decode(req) { - Ok(req) => req, - Err(_e) => return "decoding error".into(), - }; - - self.translate_string(req) - } - - fn translate_string(&self, input: pb::TranslateStringIn) -> String { - let key = match pb::FluentString::from_i32(input.key) { - Some(key) => key, - None => return "invalid key".to_string(), - }; - - let map = input - .args - .iter() - .map(|(k, v)| (k.as_str(), translate_arg_to_fluent_val(&v))) - .collect(); - - self.i18n.trn(key, map) - } -} diff --git a/rspy/src/lib.rs b/rspy/src/lib.rs index 8d74ed3c9..5ec2375f4 100644 --- a/rspy/src/lib.rs +++ b/rspy/src/lib.rs @@ -1,9 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use anki::backend::{ - init_backend, init_i18n_backend, Backend as RustBackend, I18nBackend as RustI18nBackend, -}; +use anki::backend::{init_backend, Backend as RustBackend}; use pyo3::exceptions::Exception; use pyo3::prelude::*; use pyo3::types::PyBytes; @@ -87,30 +85,6 @@ impl Backend { } } -// I18n backend -////////////////////////////////// - -#[pyclass] -struct I18nBackend { - backend: RustI18nBackend, -} - -#[pyfunction] -fn open_i18n(init_msg: &PyBytes) -> PyResult { - match init_i18n_backend(init_msg.as_bytes()) { - Ok(backend) => Ok(I18nBackend { backend }), - Err(e) => Err(exceptions::Exception::py_err(format!("{:?}", e))), - } -} - -#[pymethods] -impl I18nBackend { - fn translate(&self, input: &PyBytes) -> String { - let in_bytes = input.as_bytes(); - self.backend.translate(in_bytes) - } -} - // Module definition ////////////////////////////////// @@ -119,7 +93,6 @@ fn ankirspy(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_wrapped(wrap_pyfunction!(buildhash)).unwrap(); m.add_wrapped(wrap_pyfunction!(open_backend)).unwrap(); - m.add_wrapped(wrap_pyfunction!(open_i18n)).unwrap(); Ok(()) }