mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Move .py i18n method generation to Rust
This commit is contained in:
parent
4c76e3150b
commit
cb8007ce30
16 changed files with 126 additions and 130 deletions
|
@ -1,5 +1,6 @@
|
||||||
[env]
|
[env]
|
||||||
STRINGS_JSON = { value = "out/rslib/i18n/strings.json", relative = true }
|
# STRINGS_JSON = { value = "out/rslib/i18n/strings.json", relative = true }
|
||||||
|
STRINGS_PY = { value = "out/pylib/anki/_fluent.py", relative = true }
|
||||||
STRINGS_JS = { value = "out/ts/lib/ftl.js", relative = true }
|
STRINGS_JS = { value = "out/ts/lib/ftl.js", relative = true }
|
||||||
STRINGS_DTS = { value = "out/ts/lib/ftl.d.ts", relative = true }
|
STRINGS_DTS = { value = "out/ts/lib/ftl.d.ts", relative = true }
|
||||||
DESCRIPTORS_BIN = { value = "out/rslib/proto/descriptors.bin", relative = true }
|
DESCRIPTORS_BIN = { value = "out/rslib/proto/descriptors.bin", relative = true }
|
||||||
|
|
|
@ -147,6 +147,10 @@ debug = 0
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
debug = 0
|
debug = 0
|
||||||
|
|
||||||
|
[profile.dev.package.anki_proto]
|
||||||
|
opt-level = 1
|
||||||
|
debug = 0
|
||||||
|
|
||||||
# Debug info off by default, which speeds up incremental builds and produces a considerably
|
# Debug info off by default, which speeds up incremental builds and produces a considerably
|
||||||
# smaller library.
|
# smaller library.
|
||||||
[profile.dev.package.anki]
|
[profile.dev.package.anki]
|
||||||
|
|
|
@ -27,22 +27,8 @@ pub fn build_pylib(build: &mut Build) -> Result<()> {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
build.add_dependency("pylib:anki:proto", ":rslib:proto:py".into());
|
build.add_dependency("pylib:anki:proto", ":rslib:proto:py".into());
|
||||||
|
build.add_dependency("pylib:anki:i18n", ":rslib:i18n:py".into());
|
||||||
|
|
||||||
build.add_action(
|
|
||||||
"pylib:anki:_fluent.py",
|
|
||||||
RunCommand {
|
|
||||||
command: ":pyenv:bin",
|
|
||||||
args: "$script $strings $out",
|
|
||||||
inputs: hashmap! {
|
|
||||||
"script" => inputs!["pylib/tools/genfluent.py"],
|
|
||||||
"strings" => inputs![":rslib:i18n:strings.json"],
|
|
||||||
"" => inputs!["pylib/anki/_vendor/stringcase.py"]
|
|
||||||
},
|
|
||||||
outputs: hashmap! {
|
|
||||||
"out" => vec!["pylib/anki/_fluent.py"]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
build.add_action(
|
build.add_action(
|
||||||
"pylib:anki:hooks_gen.py",
|
"pylib:anki:hooks_gen.py",
|
||||||
RunCommand {
|
RunCommand {
|
||||||
|
|
|
@ -48,10 +48,11 @@ fn prepare_translations(build: &mut Build) -> Result<()> {
|
||||||
glob!["ftl/{core,core-repo,qt,qt-repo}/**"],
|
glob!["ftl/{core,core-repo,qt,qt-repo}/**"],
|
||||||
":ftl:repo",
|
":ftl:repo",
|
||||||
],
|
],
|
||||||
outputs: &[RustOutput::Data(
|
outputs: &[
|
||||||
"strings.json",
|
RustOutput::Data("py", "pylib/anki/_fluent.py"),
|
||||||
"$builddir/rslib/i18n/strings.json",
|
RustOutput::Data("ts", "ts/lib/ftl.d.ts"),
|
||||||
)],
|
RustOutput::Data("ts", "ts/lib/ftl.js"),
|
||||||
|
],
|
||||||
target: None,
|
target: None,
|
||||||
extra_args: "-p anki_i18n",
|
extra_args: "-p anki_i18n",
|
||||||
release_override: None,
|
release_override: None,
|
||||||
|
|
|
@ -114,7 +114,7 @@ fn setup_node(build: &mut Build) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_and_check_tslib(build: &mut Build) -> Result<()> {
|
fn build_and_check_tslib(build: &mut Build) -> Result<()> {
|
||||||
build.add_dependency("ts:lib:i18n", ":rslib:i18n".into());
|
build.add_dependency("ts:lib:i18n", ":rslib:i18n:ts".into());
|
||||||
build.add_action(
|
build.add_action(
|
||||||
"ts:lib:proto",
|
"ts:lib:proto",
|
||||||
GenTypescriptProto {
|
GenTypescriptProto {
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
|
|
||||||
# pylint: disable=unbalanced-tuple-unpacking
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from typing import Literal, TypedDict
|
|
||||||
|
|
||||||
sys.path.append("pylib/anki/_vendor")
|
|
||||||
|
|
||||||
import stringcase
|
|
||||||
|
|
||||||
strings_json, outfile = sys.argv[1:]
|
|
||||||
with open(strings_json, encoding="utf8") as f:
|
|
||||||
modules = json.load(f)
|
|
||||||
|
|
||||||
|
|
||||||
class Variable(TypedDict):
|
|
||||||
name: str
|
|
||||||
kind: Literal["Any", "Int", "String", "Float"]
|
|
||||||
|
|
||||||
|
|
||||||
def legacy_enum() -> str:
|
|
||||||
out = ["class LegacyTranslationEnum:"]
|
|
||||||
for module in modules:
|
|
||||||
for translation in module["translations"]:
|
|
||||||
key = stringcase.constcase(translation["key"])
|
|
||||||
value = f'({module["index"]}, {translation["index"]})'
|
|
||||||
out.append(f" {key} = {value}")
|
|
||||||
|
|
||||||
return "\n".join(out) + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
def methods() -> str:
|
|
||||||
out = [
|
|
||||||
"class GeneratedTranslations:",
|
|
||||||
" def _translate(self, module: int, translation: int, args: Dict) -> str:",
|
|
||||||
" raise Exception('not implemented')",
|
|
||||||
]
|
|
||||||
for module in modules:
|
|
||||||
for translation in module["translations"]:
|
|
||||||
key = translation["key"].replace("-", "_")
|
|
||||||
arg_types = get_arg_types(translation["variables"])
|
|
||||||
args = get_args(translation["variables"])
|
|
||||||
doc = translation["text"]
|
|
||||||
out.append(
|
|
||||||
f"""
|
|
||||||
def {key}(self, {arg_types}) -> str:
|
|
||||||
r''' {doc} '''
|
|
||||||
return self._translate({module["index"]}, {translation["index"]}, {{{args}}})
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
return "\n".join(out) + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
def get_arg_types(args: list[Variable]) -> str:
|
|
||||||
return ", ".join(
|
|
||||||
[f"{stringcase.snakecase(arg['name'])}: {arg_kind(arg)}" for arg in args]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def arg_kind(arg: Variable) -> str:
|
|
||||||
if arg["kind"] == "Int":
|
|
||||||
return "int"
|
|
||||||
elif arg["kind"] == "Any":
|
|
||||||
return "FluentVariable"
|
|
||||||
elif arg["kind"] == "Float":
|
|
||||||
return "float"
|
|
||||||
else:
|
|
||||||
return "str"
|
|
||||||
|
|
||||||
|
|
||||||
def get_args(args: list[Variable]) -> str:
|
|
||||||
return ", ".join(
|
|
||||||
[f'"{arg["name"]}": {stringcase.snakecase(arg["name"])}' for arg in args]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
out = ""
|
|
||||||
|
|
||||||
out += legacy_enum()
|
|
||||||
out += methods()
|
|
||||||
|
|
||||||
|
|
||||||
with open(outfile, "w", encoding="utf8") as f:
|
|
||||||
f.write(
|
|
||||||
'''# Copyright: Ankitects Pty Ltd and contributors
|
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
# pylint: skip-file
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
"""
|
|
||||||
This file is automatically generated from the *.ftl files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
from typing import Dict, Union
|
|
||||||
|
|
||||||
FluentVariable = Union[str, int, float]
|
|
||||||
|
|
||||||
'''
|
|
||||||
+ out
|
|
||||||
)
|
|
|
@ -2,7 +2,6 @@
|
||||||
name = "anki_i18n"
|
name = "anki_i18n"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
build = "build/main.rs"
|
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
publish = false
|
publish = false
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
mod check;
|
mod check;
|
||||||
mod extract;
|
mod extract;
|
||||||
mod gather;
|
mod gather;
|
||||||
|
mod python;
|
||||||
mod typescript;
|
mod typescript;
|
||||||
mod write_strings;
|
mod write_strings;
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ fn main() -> Result<()> {
|
||||||
write_strings(&map, &modules);
|
write_strings(&map, &modules);
|
||||||
|
|
||||||
typescript::write_ts_interface(&modules)?;
|
typescript::write_ts_interface(&modules)?;
|
||||||
|
python::write_py_interface(&modules)?;
|
||||||
|
|
||||||
// write strings.json file to requested path
|
// write strings.json file to requested path
|
||||||
println!("cargo:rerun-if-env-changed=STRINGS_JSON");
|
println!("cargo:rerun-if-env-changed=STRINGS_JSON");
|
109
rslib/i18n/python.rs
Normal file
109
rslib/i18n/python.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anki_io::create_dir_all;
|
||||||
|
use anki_io::write_file_if_changed;
|
||||||
|
use anyhow::Result;
|
||||||
|
use inflections::Inflect;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::extract::Module;
|
||||||
|
use crate::extract::Variable;
|
||||||
|
use crate::extract::VariableKind;
|
||||||
|
|
||||||
|
pub fn write_py_interface(modules: &[Module]) -> Result<()> {
|
||||||
|
let mut out = header();
|
||||||
|
|
||||||
|
render_methods(modules, &mut out);
|
||||||
|
render_legacy_enum(modules, &mut out);
|
||||||
|
|
||||||
|
if let Ok(path) = env::var("STRINGS_PY") {
|
||||||
|
let path = PathBuf::from(path);
|
||||||
|
create_dir_all(path.parent().unwrap())?;
|
||||||
|
write_file_if_changed(path, out)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_legacy_enum(modules: &[Module], out: &mut String) {
|
||||||
|
out.push_str("class LegacyTranslationEnum:\n");
|
||||||
|
for (mod_idx, module) in modules.iter().enumerate() {
|
||||||
|
for (str_idx, translation) in module.translations.iter().enumerate() {
|
||||||
|
let upper = translation.key.replace('-', "_").to_upper_case();
|
||||||
|
writeln!(out, r#" {upper} = ({mod_idx}, {str_idx})"#).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_methods(modules: &[Module], out: &mut String) {
|
||||||
|
for (mod_idx, module) in modules.iter().enumerate() {
|
||||||
|
for (str_idx, translation) in module.translations.iter().enumerate() {
|
||||||
|
let text = &translation.text;
|
||||||
|
let key = &translation.key;
|
||||||
|
let func_name = key.replace('-', "_").to_snake_case();
|
||||||
|
let arg_types = get_arg_types(&translation.variables);
|
||||||
|
let args = get_args(&translation.variables);
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
r#"
|
||||||
|
def {func_name}(self, {arg_types}) -> str:
|
||||||
|
r''' {text} '''
|
||||||
|
return self._translate({mod_idx}, {str_idx}, {{{args}}})
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header() -> String {
|
||||||
|
"# Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
# This file is automatically generated from the *.ftl files.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
FluentVariable = Union[str, int, float]
|
||||||
|
|
||||||
|
class GeneratedTranslations:
|
||||||
|
def _translate(self, module: int, translation: int, args: dict) -> str:
|
||||||
|
raise Exception('not implemented')
|
||||||
|
|
||||||
|
"
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_arg_types(args: &[Variable]) -> String {
|
||||||
|
let args = args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| format!("{}: {}", arg.name.to_snake_case(), arg_kind(&arg.kind)))
|
||||||
|
.join(", ");
|
||||||
|
if args.is_empty() {
|
||||||
|
"".into()
|
||||||
|
} else {
|
||||||
|
args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_args(args: &[Variable]) -> String {
|
||||||
|
args.iter()
|
||||||
|
.map(|arg| format!("\"{}\": {}", arg.name, arg.name.to_snake_case()))
|
||||||
|
.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_kind(kind: &VariableKind) -> &str {
|
||||||
|
match kind {
|
||||||
|
VariableKind::Int => "int",
|
||||||
|
VariableKind::Float => "float",
|
||||||
|
VariableKind::String => "str",
|
||||||
|
VariableKind::Any => "FluentVariable",
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
pub mod python;
|
pub mod python;
|
||||||
pub mod rust;
|
pub mod rust;
|
||||||
pub mod ts;
|
pub mod typescript;
|
||||||
|
|
||||||
use anki_proto_gen::descriptors_path;
|
use anki_proto_gen::descriptors_path;
|
||||||
use anki_proto_gen::get_services;
|
use anki_proto_gen::get_services;
|
||||||
|
@ -15,7 +15,7 @@ fn main() -> Result<()> {
|
||||||
let pool = rust::write_rust_protos(descriptors_path)?;
|
let pool = rust::write_rust_protos(descriptors_path)?;
|
||||||
let (_, services) = get_services(&pool);
|
let (_, services) = get_services(&pool);
|
||||||
python::write_python_interface(&services)?;
|
python::write_python_interface(&services)?;
|
||||||
ts::write_ts_interface(&services)?;
|
typescript::write_ts_interface(&services)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue