Set up code and message generation for the Kifu localization strings

This commit is contained in:
Savanni D'Gerinel 2024-03-11 08:44:43 -04:00
parent 8ceca454b1
commit 15b1d4bfd6
11 changed files with 133 additions and 74 deletions

2
Cargo.lock generated
View File

@ -2697,6 +2697,8 @@ dependencies = [
"async-channel 2.1.1",
"async-std",
"cairo-rs",
"fluent",
"fluent-ergonomics",
"gio",
"glib",
"glib-build-tools 0.17.10",

View File

@ -13,6 +13,8 @@ adw = { version = "0.5", package = "libadwaita", features = [ "v1_2"
async-channel = { version = "2" }
async-std = { version = "1" }
cairo-rs = { version = "0.18" }
fluent = { version = "0.16" }
fluent-ergonomics = { path = "../../fluent-ergonomics" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] }
@ -25,13 +27,3 @@ tokio = { version = "1.26", features = [ "full" ] }
[build-dependencies]
glib-build-tools = "0.17"
# [[bin]]
# name = "kifu-gtk"
# path = "src/main.rs"
# [[bin]]
# name = "screenplay"
# path = "src/bin/screenplay.rs"
# required-features = [ "screenplay" ]

View File

@ -4,11 +4,4 @@ fn main() {
"gresources.xml",
"com.luminescent-dreams.kifu-gtk.gresource",
);
let messages = std::process::Command::new("codegen-rust")
.arg("messages/en.yaml")
.output()
.expect("Failed to execute command");
println!("{}", String::from_utf8(messages.stdout).unwrap());
}

View File

@ -1,9 +1,14 @@
nothing-here:
content: "Nothing Here"
welcome:
content: "Welcome to Kifu"
hello:
parameters:
name: string
content: "Hello, ${name}"
games-in-database:
parameters:
count: count

View File

@ -1,5 +1,8 @@
welcome = Welcome
games-in-database = {count ->
[one] There is one game in the database
[other] There are {count} games in the database
nothing-here = Nothing Here
hello = Hello, ${name}
games-in-database = {$count ->
[one] There is one game in the database
*[other] There are ${count} games in the database
}
welcome = Welcome to Kifu

View File

@ -1,3 +1,5 @@
nothing-here = Nenio estas ĉi tie
hello = Saluton, ${name}
welcome = Bonvenon
games-in-database = {count ->
[one] Estas unu ludon en la datumbazo.

View File

@ -1,26 +1,43 @@
use l10n::{FluentArgs, FluentValue, Message};
// This file was autogenerated from l10n-codegen-rust. Edits will be lost
// on next generation.
use l10n::Message;
use fluent_ergonomics::FluentErgo;
use fluent::FluentArgs;
enum Phrase {
Welcome,
GamesInDatabase(i32),
pub struct Hello {
name: String,
}
impl Message for Phrase {
fn msgid(&self) -> &str {
match self {
Phrase::Welcome => "welcome",
Phrase::GamesInDatabase(_) => "games-in-database",
}
}
fn args(&self) -> Option<FluentArgs> {
match self {
Phrase::Welcome => None,
Phrase::GamesInDatabase(val) => {
let mut args = FluentArgs::new();
args.set("count", FluentValue::from(val));
Some(args)
}
}
impl Message for Hello {
fn localize(&self, bundle: &FluentErgo) -> String {
let mut args = FluentArgs::new();
args.set("name", self.name.clone());
bundle.tr("hello", Some(&args)).unwrap()
}
}
pub struct NothingHere;
impl Message for NothingHere {
fn localize(&self, bundle: &FluentErgo) -> String {
bundle.tr("nothing-here", None).unwrap()
}
}
pub struct Welcome;
impl Message for Welcome {
fn localize(&self, bundle: &FluentErgo) -> String {
bundle.tr("welcome", None).unwrap()
}
}
pub struct GamesInDatabase {
count: usize,
}
impl Message for GamesInDatabase {
fn localize(&self, bundle: &FluentErgo) -> String {
let mut args = FluentArgs::new();
args.set("count", self.count.clone());
bundle.tr("games-in-database", Some(&args)).unwrap()
}
}

View File

@ -2,6 +2,9 @@ use adw::prelude::*;
use gio::resources_lookup_data;
use glib::IsA;
use gtk::STYLE_PROVIDER_PRIORITY_USER;
use l10n::L10N;
use std::path::PathBuf;
use crate::messages;
mod chat;
pub use chat::Chat;
@ -75,8 +78,9 @@ impl AppWindow {
header.pack_end(&hamburger);
let content = adw::Bin::builder().css_classes(vec!["content"]).build();
let l10n = L10N::new(PathBuf::from("resources"));
content.set_child(Some(
&adw::StatusPage::builder().title("Nothing here").build(),
&adw::StatusPage::builder().title(l10n.tr(messages::NothingHere)).build(),
));
let layout = gtk::Box::builder()

View File

@ -77,39 +77,53 @@ impl Message {
fn rust_code(&self) -> String {
if self.parameters.is_empty() {
format!(r"pub struct {}; ", self.name.to_case(Case::Pascal))
let mut struct_strs = vec![];
struct_strs.push(format!("pub struct {}; ", self.name.to_case(Case::Pascal)));
struct_strs.push(format!("impl Message for {} {{
fn localize(&self, bundle: &FluentErgo) -> String {{
bundle.tr(\"{}\", None).unwrap()
}}
}}",
self.name.to_case(Case::Pascal),
self.name,
));
struct_strs.join("\n")
} else {
let mut struct_strs = vec![];
struct_strs.push(format!("pub struct {} {{", self.name.to_case(Case::Pascal)));
let mut parameters: Vec<String> = self
let parameters: String = self
.parameters
.iter()
.map(|param| format!(" {}: {},", param.name, param.ty))
.collect();
.collect::<Vec<String>>().join("\n");
struct_strs.push(format!("pub struct {} {{
{}
}}", self.name.to_case(Case::Pascal),
parameters));
struct_strs.append(&mut parameters);
struct_strs.push("}".to_owned());
struct_strs.push("".to_owned());
struct_strs.push(format!(
"impl Message for {} {{",
self.name.to_case(Case::Pascal)
));
struct_strs.push(format!(" pub fn localize(&self, bundle: &FluentBundle) -> String {{"));
struct_strs.push(" let mut args = FluentArgs::new();".to_owned());
let mut parameters: Vec<String> = self
let parameters: String = self
.parameters
.iter()
.map(|param| format!(" args.set(\"{}\", self.{})", param.name, param.name))
.collect();
struct_strs.append(&mut parameters);
struct_strs.push("".to_owned());
struct_strs.push(format!(" let msg = bundle.get_message(\"{}\").unwrap();", self.name));
struct_strs.push(format!(" let mut errors = vec![]"));
struct_strs.push(format!(" msg.format_pattern(&msg.value().unwrap(), args, &mut errors)"));
struct_strs.push(" }".to_owned());
struct_strs.push("}".to_owned());
.map(|param| format!(" args.set(\"{}\", self.{}.clone());", param.name, param.name))
.collect::<Vec<String>>().join("\n");
struct_strs.push(format!(
"impl Message for {} {{
fn localize(&self, bundle: &FluentErgo) -> String {{
let mut args = FluentArgs::new();
{}
bundle.tr(\"{}\", Some(&args)).unwrap()
}}
}}",
self.name.to_case(Case::Pascal),
parameters,
self.name,
));
struct_strs.join("\n")
}
@ -127,6 +141,12 @@ fn main() {
let messages: HashMap<String, MessageJS> =
serde_yaml::from_reader(File::open(input_file).unwrap()).unwrap();
println!("// This file was autogenerated from l10n-codegen-rust. Edits will be lost
// on next generation.");
println!("use l10n::Message;");
println!("use fluent_ergonomics::FluentErgo;");
println!("use fluent::FluentArgs;");
let messages = messages
.into_iter()
.map(|(name, msg)| Message::from_js(name, msg));

View File

@ -1,3 +1,21 @@
fn main() {
println!("msggen running");
use std::{collections::HashMap, fs::File, env};
use serde::Deserialize;
use serde_yaml;
#[derive(Debug, Clone, Deserialize)]
struct MessageJS {
#[serde(default)]
content: String,
}
fn main() {
let mut args = env::args();
let _ = args.next();
let input_file = args.next().unwrap();
let messages: HashMap<String, MessageJS> =
serde_yaml::from_reader(File::open(input_file).unwrap()).unwrap();
for (name, message) in messages {
println!("{} = {}", name, message.content);
}
}

View File

@ -68,20 +68,19 @@ impl From<icu::locid::Error> for L10NError {
// However, I have not found a mechanism in Fluent to identify all of the placeholders within a
// message, so I'm not even sure that I can automate this code generation.
pub trait Message {
fn msgid(&self) -> &str;
fn args(&self) -> Option<FluentArgs>;
fn localize(&self, bundle: &FluentErgo) -> String;
}
pub struct L10N {
messages_root: std::path::PathBuf,
messages: FluentErgo,
message_bundle: FluentErgo,
locales: NonEmptyList<Locale>,
zone: chrono_tz::Tz,
}
impl L10N {
fn new(messages_root: std::path::PathBuf) -> Self {
pub fn new(messages_root: std::path::PathBuf) -> Self {
let english = "en-US".parse::<Locale>().unwrap();
let sys_locale = get_locale()
.and_then(|locale_str| locale_str.parse::<Locale>().ok())
@ -95,7 +94,7 @@ impl L10N {
let english_phrases = FluentResource::try_new
*/
let messages = {
let message_bundle = {
let mut english_messages = messages_root.clone();
english_messages.push("en-US.ftl");
@ -108,7 +107,7 @@ impl L10N {
Self {
messages_root,
messages,
message_bundle,
locales,
zone,
}
@ -158,7 +157,11 @@ impl L10N {
// at compile time, as are their parameters. That implies an enumeration, with one element per
// message, and with each element knowing its parameters.
pub fn messages(&self) -> FluentErgo {
self.messages.clone()
self.message_bundle.clone()
}
pub fn tr(&self, message: impl Message) -> String {
message.localize(&self.message_bundle)
}
pub fn format_date_time_utc(