From b57e9be46f128e4c9ca12673393805e4a8438df2 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 26 Mar 2021 21:22:37 +1000 Subject: [PATCH] allow js to request specific i18n modules Brings the payload on the congrats page with a non-English language down from about 150k to 15k --- pylib/anki/collection.py | 4 ++-- qt/aqt/mediasrv.py | 7 ++++++- rslib/backend.proto | 6 +++++- rslib/i18n/src/lib.rs | 31 ++++++++++++++++++++++--------- rslib/src/backend/i18n.rs | 4 ++-- ts/congrats/index.ts | 4 ++-- ts/graphs/index.ts | 4 ++-- ts/lib/genfluent.py | 11 ++++++++++- ts/lib/i18n_helpers.ts | 9 +++++++-- 9 files changed, 58 insertions(+), 22 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index b272a4e73..6f8a28d94 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -1041,8 +1041,8 @@ table.review-log {{ {revlog_style} }} def set_wants_abort(self) -> None: self._backend.set_wants_abort() - def i18n_resources(self) -> bytes: - return self._backend.i18n_resources() + def i18n_resources(self, modules: Sequence[str]) -> bytes: + return self._backend.i18n_resources(modules=modules) def abort_media_sync(self) -> None: self._backend.abort_media_sync() diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 9f3177a6e..d39101cda 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -269,12 +269,17 @@ def congrats_info() -> bytes: return aqt.mw.col.congrats_info() +def i18n_resources() -> bytes: + args = from_json_bytes(request.data) + return aqt.mw.col.i18n_resources(modules=args["modules"]) + + post_handlers = { "graphData": graph_data, "graphPreferences": graph_preferences, "setGraphPreferences": set_graph_preferences, # pylint: disable=unnecessary-lambda - "i18nResources": lambda: aqt.mw.col.i18n_resources(), + "i18nResources": i18n_resources, "congratsInfo": congrats_info, } diff --git a/rslib/backend.proto b/rslib/backend.proto index 7e21d8321..ae2f9e30f 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -263,7 +263,7 @@ service MediaService { service I18nService { rpc TranslateString(TranslateStringIn) returns (String); rpc FormatTimespan(FormatTimespanIn) returns (String); - rpc I18nResources(Empty) returns (Json); + rpc I18nResources(I18nResourcesIn) returns (Json); } service CollectionService { @@ -773,6 +773,10 @@ message FormatTimespanIn { Context context = 2; } +message I18nResourcesIn { + repeated string modules = 1; +} + message StudiedTodayMessageIn { uint32 cards = 1; double seconds = 2; diff --git a/rslib/i18n/src/lib.rs b/rslib/i18n/src/lib.rs index 07600247c..4fc132f0c 100644 --- a/rslib/i18n/src/lib.rs +++ b/rslib/i18n/src/lib.rs @@ -183,7 +183,6 @@ impl I18n { pub fn new>(locale_codes: &[S]) -> Self { let mut input_langs = vec![]; let mut bundles = Vec::with_capacity(locale_codes.len() + 1); - let mut resource_text = vec![]; for code in locale_codes { let code = code.as_ref(); @@ -210,7 +209,6 @@ impl I18n { } }) { if let Some(bundle) = get_bundle_with_extra(&text, Some(lang.clone())) { - resource_text.push(text); bundles.push(bundle); output_langs.push(lang); } else { @@ -223,7 +221,6 @@ impl I18n { let template_lang = "en-US".parse().unwrap(); let template_text = ftl_localized_text(&template_lang).unwrap(); let template_bundle = get_bundle_with_extra(&template_text, None).unwrap(); - resource_text.push(template_text); bundles.push(template_bundle); output_langs.push(template_lang); @@ -238,7 +235,6 @@ impl I18n { inner: Arc::new(Mutex::new(I18nInner { bundles, langs: output_langs, - resource_text, })), } } @@ -288,15 +284,35 @@ impl I18n { } /// Return text from configured locales for use with the JS Fluent implementation. - pub fn resources_for_js(&self) -> ResourcesForJavascript { + pub fn resources_for_js(&self, desired_modules: &[String]) -> ResourcesForJavascript { let inner = self.inner.lock().unwrap(); + let resources = get_modules(&inner.langs, desired_modules); ResourcesForJavascript { langs: inner.langs.iter().map(ToString::to_string).collect(), - resources: inner.resource_text.clone(), + resources, } } } +fn get_modules(langs: &[LanguageIdentifier], desired_modules: &[String]) -> Vec { + langs + .iter() + .cloned() + .map(|lang| { + let mut buf = String::new(); + let lang_name = remapped_lang_name(&lang); + if let Some(strings) = STRINGS.get(lang_name) { + for module_name in desired_modules { + if let Some(text) = strings.get(module_name.as_str()) { + buf.push_str(text); + } + } + } + buf + }) + .collect() +} + /// This temporarily behaves like the older code; in the future we could either /// access each &str separately, or load them on demand. fn ftl_localized_text(lang: &LanguageIdentifier) -> Option { @@ -317,9 +333,6 @@ struct I18nInner { // last element bundles: Vec>, langs: Vec, - // fixme: this is a relic from the old implementation, and we could gather - // it only when needed in the future - resource_text: Vec, } // Simple number formatting implementation diff --git a/rslib/src/backend/i18n.rs b/rslib/src/backend/i18n.rs index 96dd4e843..c2a2e6eab 100644 --- a/rslib/src/backend/i18n.rs +++ b/rslib/src/backend/i18n.rs @@ -32,8 +32,8 @@ impl I18nService for Backend { .into()) } - fn i18n_resources(&self, _input: pb::Empty) -> Result { - serde_json::to_vec(&self.i18n.resources_for_js()) + fn i18n_resources(&self, input: pb::I18nResourcesIn) -> Result { + serde_json::to_vec(&self.i18n.resources_for_js(&input.modules)) .map(Into::into) .map_err(Into::into) } diff --git a/ts/congrats/index.ts b/ts/congrats/index.ts index d134011a3..547b04c37 100644 --- a/ts/congrats/index.ts +++ b/ts/congrats/index.ts @@ -2,14 +2,14 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { getCongratsInfo } from "./lib"; -import { setupI18n } from "anki/i18n"; +import { setupI18n, ModuleName } from "anki/i18n"; import { checkNightMode } from "anki/nightmode"; import CongratsPage from "./CongratsPage.svelte"; export async function congrats(target: HTMLDivElement): Promise { checkNightMode(); - await setupI18n(); + await setupI18n({ modules: [ModuleName.SCHEDULING] }); const info = await getCongratsInfo(); new CongratsPage({ target, diff --git a/ts/graphs/index.ts b/ts/graphs/index.ts index b258f1509..c0c0594e9 100644 --- a/ts/graphs/index.ts +++ b/ts/graphs/index.ts @@ -3,7 +3,7 @@ import type { SvelteComponent } from "svelte/internal"; -import { setupI18n } from "anki/i18n"; +import { setupI18n, ModuleName } from "anki/i18n"; import { checkNightMode } from "anki/nightmode"; import GraphsPage from "./GraphsPage.svelte"; @@ -33,7 +33,7 @@ export function graphs( ): void { const nightMode = checkNightMode(); - setupI18n().then(() => { + setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(() => { new GraphsPage({ target, props: { diff --git a/ts/lib/genfluent.py b/ts/lib/genfluent.py index 732dc6ac7..b30d1f606 100644 --- a/ts/lib/genfluent.py +++ b/ts/lib/genfluent.py @@ -91,11 +91,20 @@ def typescript_arg_name(arg: Variable) -> str: else: return name +def module_names() -> str: + buf = "export enum ModuleName {\n" + for module in modules: + name = module["name"] + upper = name.upper() + buf += f' {upper} = "{name}",\n' + buf += "}\n" + return buf + out = "" out += methods() - +out += module_names() open(outfile, "wb").write( ( diff --git a/ts/lib/i18n_helpers.ts b/ts/lib/i18n_helpers.ts index 9c66c8006..1852dffa5 100644 --- a/ts/lib/i18n_helpers.ts +++ b/ts/lib/i18n_helpers.ts @@ -70,8 +70,13 @@ export class I18n { // global singleton export const i18n = new I18n(); -export async function setupI18n(): Promise { - const resp = await fetch("/_anki/i18nResources", { method: "POST" }); +import type { ModuleName } from "./i18n"; + +export async function setupI18n(args: { modules: ModuleName[] }): Promise { + const resp = await fetch("/_anki/i18nResources", { + method: "POST", + body: JSON.stringify(args), + }); if (!resp.ok) { throw Error(`unexpected reply: ${resp.statusText}`); }