From c395003defb1e84c7a723665b3644896f8027e88 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 16 Feb 2020 21:07:40 +1000 Subject: [PATCH] expose translations to Python --- proto/backend.proto | 17 ++++++++++++++++- pylib/anki/rsbackend.py | 18 ++++++++++++++++++ pylib/tests/test_collection.py | 15 +++++++++++++++ rslib/src/backend.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/proto/backend.proto b/proto/backend.proto index ef89bcfd4..41224421b 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -39,6 +39,7 @@ message BackendInput { SyncMediaIn sync_media = 27; Empty check_media = 28; TrashMediaFilesIn trash_media_files = 29; + TranslateStringIn translate_string = 30; } } @@ -58,6 +59,7 @@ message BackendOutput { Empty sync_media = 27; MediaCheckOut check_media = 28; Empty trash_media_files = 29; + string translate_string = 30; BackendError error = 2047; } @@ -281,4 +283,17 @@ message MediaCheckOut { message TrashMediaFilesIn { repeated string fnames = 1; -} \ No newline at end of file +} + +message TranslateStringIn { + StringsGroup group = 1; + string key = 2; + map args = 3; +} + +message TranslateArgValue { + oneof value { + string str = 1; + string number = 2; + } +} diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index d714611dd..3c1a074ea 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -126,6 +126,8 @@ MediaSyncProgress = pb.MediaSyncProgress MediaCheckOutput = pb.MediaCheckOut +StringsGroup = pb.StringsGroup + @dataclass class ExtractedLatex: @@ -318,3 +320,19 @@ class RustBackend: self._run_command( pb.BackendInput(trash_media_files=pb.TrashMediaFilesIn(fnames=fnames)) ) + + def translate( + self, group: pb.StringsGroup, key: str, **kwargs: Union[str, int, float] + ): + args = {} + for (k, v) in kwargs.items(): + if isinstance(v, str): + args[k] = pb.TranslateArgValue(str=v) + else: + args[k] = pb.TranslateArgValue(number=str(v)) + + return self._run_command( + pb.BackendInput( + translate_string=pb.TranslateStringIn(group=group, key=key, args=args) + ) + ).translate_string diff --git a/pylib/tests/test_collection.py b/pylib/tests/test_collection.py index 829332a3f..623e16181 100644 --- a/pylib/tests/test_collection.py +++ b/pylib/tests/test_collection.py @@ -4,6 +4,7 @@ import os import tempfile from anki import Collection as aopen +from anki.rsbackend import StringsGroup from anki.stdmodels import addBasicModel, models from anki.utils import isWin from tests.shared import assertException, getEmptyCol @@ -147,3 +148,17 @@ def test_furigana(): m["tmpls"][0]["qfmt"] = "{{kana:}}" mm.save(m) c.q(reload=True) + + +def test_translate(): + d = getEmptyCol() + tr = d.backend.translate + + # strip off unicode separators + def no_uni(s: str) -> str: + return s.replace("\u2068", "").replace("\u2069", "") + + assert tr(StringsGroup.TEST, "valid-key") == "a valid key" + assert "invalid-key" in tr(StringsGroup.TEST, "invalid-key") + assert no_uni(tr(StringsGroup.TEST, "plural", hats=1)) == "You have 1 hat." + assert no_uni(tr(StringsGroup.TEST, "plural", hats=2)) == "You have 2 hats." diff --git a/rslib/src/backend.rs b/rslib/src/backend.rs index bb40e95c2..2d327739e 100644 --- a/rslib/src/backend.rs +++ b/rslib/src/backend.rs @@ -16,6 +16,7 @@ use crate::template::{ RenderedNode, }; use crate::text::{extract_av_tags, strip_av_tags, AVTag}; +use fluent::FluentValue; use prost::Message; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; @@ -193,6 +194,7 @@ impl Backend { self.remove_media_files(&input.fnames)?; OValue::TrashMediaFiles(Empty {}) } + Value::TranslateString(input) => OValue::TranslateString(self.translate_string(input)), }) } @@ -376,6 +378,31 @@ impl Backend { let mut ctx = mgr.dbctx(); mgr.remove_files(&mut ctx, fnames) } + + fn translate_string(&self, input: pb::TranslateStringIn) -> String { + let group = match pb::StringsGroup::from_i32(input.group) { + Some(group) => group, + None => return "".to_string(), + }; + let map = input + .args + .iter() + .map(|(k, v)| (k.as_str(), translate_arg_to_fluent_val(&v))) + .collect(); + + self.i18n.get(group).trn(&input.key, map) + } +} + +fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue { + use pb::translate_arg_value::Value as V; + match &arg.value { + Some(val) => match val { + V::Str(s) => FluentValue::String(s.into()), + V::Number(s) => FluentValue::Number(s.into()), + }, + None => FluentValue::String("".into()), + } } fn ords_hash_to_set(ords: HashSet) -> Vec {