Set up code and message generation for the Kifu localization strings
This commit is contained in:
parent
8ceca454b1
commit
15b1d4bfd6
|
@ -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",
|
||||
|
|
|
@ -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" ]
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
welcome = Welcome
|
||||
games-in-database = {count ->
|
||||
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
|
||||
*[other] There are ${count} games in the database
|
||||
}
|
||||
|
||||
welcome = Welcome to Kifu
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) => {
|
||||
impl Message for Hello {
|
||||
fn localize(&self, bundle: &FluentErgo) -> String {
|
||||
let mut args = FluentArgs::new();
|
||||
args.set("count", FluentValue::from(val));
|
||||
Some(args)
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue