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]
|
||||
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_DTS = { value = "out/ts/lib/ftl.d.ts", relative = true }
|
||||
DESCRIPTORS_BIN = { value = "out/rslib/proto/descriptors.bin", relative = true }
|
||||
|
|
|
@ -147,6 +147,10 @@ debug = 0
|
|||
opt-level = 1
|
||||
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
|
||||
# smaller library.
|
||||
[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: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(
|
||||
"pylib:anki:hooks_gen.py",
|
||||
RunCommand {
|
||||
|
|
|
@ -48,10 +48,11 @@ fn prepare_translations(build: &mut Build) -> Result<()> {
|
|||
glob!["ftl/{core,core-repo,qt,qt-repo}/**"],
|
||||
":ftl:repo",
|
||||
],
|
||||
outputs: &[RustOutput::Data(
|
||||
"strings.json",
|
||||
"$builddir/rslib/i18n/strings.json",
|
||||
)],
|
||||
outputs: &[
|
||||
RustOutput::Data("py", "pylib/anki/_fluent.py"),
|
||||
RustOutput::Data("ts", "ts/lib/ftl.d.ts"),
|
||||
RustOutput::Data("ts", "ts/lib/ftl.js"),
|
||||
],
|
||||
target: None,
|
||||
extra_args: "-p anki_i18n",
|
||||
release_override: None,
|
||||
|
|
|
@ -114,7 +114,7 @@ fn setup_node(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(
|
||||
"ts:lib:proto",
|
||||
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"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
build = "build/main.rs"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
mod check;
|
||||
mod extract;
|
||||
mod gather;
|
||||
mod python;
|
||||
mod typescript;
|
||||
mod write_strings;
|
||||
|
||||
|
@ -27,6 +28,7 @@ fn main() -> Result<()> {
|
|||
write_strings(&map, &modules);
|
||||
|
||||
typescript::write_ts_interface(&modules)?;
|
||||
python::write_py_interface(&modules)?;
|
||||
|
||||
// write strings.json file to requested path
|
||||
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 rust;
|
||||
pub mod ts;
|
||||
pub mod typescript;
|
||||
|
||||
use anki_proto_gen::descriptors_path;
|
||||
use anki_proto_gen::get_services;
|
||||
|
@ -15,7 +15,7 @@ fn main() -> Result<()> {
|
|||
let pool = rust::write_rust_protos(descriptors_path)?;
|
||||
let (_, services) = get_services(&pool);
|
||||
python::write_python_interface(&services)?;
|
||||
ts::write_ts_interface(&services)?;
|
||||
typescript::write_ts_interface(&services)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue