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
This commit is contained in:
Damien Elmes 2021-03-26 21:22:37 +10:00
parent 8920a6f9ea
commit b57e9be46f
9 changed files with 58 additions and 22 deletions

View File

@ -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()

View File

@ -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,
}

View File

@ -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;

View File

@ -183,7 +183,6 @@ impl I18n {
pub fn new<S: AsRef<str>>(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<String> {
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<String> {
@ -317,9 +333,6 @@ struct I18nInner {
// last element
bundles: Vec<FluentBundle<FluentResource>>,
langs: Vec<LanguageIdentifier>,
// fixme: this is a relic from the old implementation, and we could gather
// it only when needed in the future
resource_text: Vec<String>,
}
// Simple number formatting implementation

View File

@ -32,8 +32,8 @@ impl I18nService for Backend {
.into())
}
fn i18n_resources(&self, _input: pb::Empty) -> Result<pb::Json> {
serde_json::to_vec(&self.i18n.resources_for_js())
fn i18n_resources(&self, input: pb::I18nResourcesIn) -> Result<pb::Json> {
serde_json::to_vec(&self.i18n.resources_for_js(&input.modules))
.map(Into::into)
.map_err(Into::into)
}

View File

@ -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<void> {
checkNightMode();
await setupI18n();
await setupI18n({ modules: [ModuleName.SCHEDULING] });
const info = await getCongratsInfo();
new CongratsPage({
target,

View File

@ -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: {

View File

@ -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(
(

View File

@ -70,8 +70,13 @@ export class I18n {
// global singleton
export const i18n = new I18n();
export async function setupI18n(): Promise<void> {
const resp = await fetch("/_anki/i18nResources", { method: "POST" });
import type { ModuleName } from "./i18n";
export async function setupI18n(args: { modules: ModuleName[] }): Promise<void> {
const resp = await fetch("/_anki/i18nResources", {
method: "POST",
body: JSON.stringify(args),
});
if (!resp.ok) {
throw Error(`unexpected reply: ${resp.statusText}`);
}