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-channel 2.1.1",
|
||||||
"async-std",
|
"async-std",
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
|
"fluent",
|
||||||
|
"fluent-ergonomics",
|
||||||
"gio",
|
"gio",
|
||||||
"glib",
|
"glib",
|
||||||
"glib-build-tools 0.17.10",
|
"glib-build-tools 0.17.10",
|
||||||
|
|
|
@ -13,6 +13,8 @@ adw = { version = "0.5", package = "libadwaita", features = [ "v1_2"
|
||||||
async-channel = { version = "2" }
|
async-channel = { version = "2" }
|
||||||
async-std = { version = "1" }
|
async-std = { version = "1" }
|
||||||
cairo-rs = { version = "0.18" }
|
cairo-rs = { version = "0.18" }
|
||||||
|
fluent = { version = "0.16" }
|
||||||
|
fluent-ergonomics = { path = "../../fluent-ergonomics" }
|
||||||
gio = { version = "0.18" }
|
gio = { version = "0.18" }
|
||||||
glib = { version = "0.18" }
|
glib = { version = "0.18" }
|
||||||
gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] }
|
gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] }
|
||||||
|
@ -25,13 +27,3 @@ tokio = { version = "1.26", features = [ "full" ] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
glib-build-tools = "0.17"
|
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",
|
"gresources.xml",
|
||||||
"com.luminescent-dreams.kifu-gtk.gresource",
|
"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:
|
welcome:
|
||||||
content: "Welcome to Kifu"
|
content: "Welcome to Kifu"
|
||||||
|
|
||||||
hello:
|
hello:
|
||||||
parameters:
|
parameters:
|
||||||
name: string
|
name: string
|
||||||
content: "Hello, ${name}"
|
content: "Hello, ${name}"
|
||||||
|
|
||||||
games-in-database:
|
games-in-database:
|
||||||
parameters:
|
parameters:
|
||||||
count: count
|
count: count
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
welcome = Welcome
|
nothing-here = Nothing Here
|
||||||
games-in-database = {count ->
|
hello = Hello, ${name}
|
||||||
|
games-in-database = {$count ->
|
||||||
[one] There is one game in the database
|
[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
|
welcome = Bonvenon
|
||||||
games-in-database = {count ->
|
games-in-database = {count ->
|
||||||
[one] Estas unu ludon en la datumbazo.
|
[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 {
|
pub struct Hello {
|
||||||
Welcome,
|
name: String,
|
||||||
GamesInDatabase(i32),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for Phrase {
|
impl Message for Hello {
|
||||||
fn msgid(&self) -> &str {
|
fn localize(&self, bundle: &FluentErgo) -> String {
|
||||||
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();
|
let mut args = FluentArgs::new();
|
||||||
args.set("count", FluentValue::from(val));
|
args.set("name", self.name.clone());
|
||||||
Some(args)
|
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 gio::resources_lookup_data;
|
||||||
use glib::IsA;
|
use glib::IsA;
|
||||||
use gtk::STYLE_PROVIDER_PRIORITY_USER;
|
use gtk::STYLE_PROVIDER_PRIORITY_USER;
|
||||||
|
use l10n::L10N;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use crate::messages;
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
pub use chat::Chat;
|
pub use chat::Chat;
|
||||||
|
@ -75,8 +78,9 @@ impl AppWindow {
|
||||||
header.pack_end(&hamburger);
|
header.pack_end(&hamburger);
|
||||||
|
|
||||||
let content = adw::Bin::builder().css_classes(vec!["content"]).build();
|
let content = adw::Bin::builder().css_classes(vec!["content"]).build();
|
||||||
|
let l10n = L10N::new(PathBuf::from("resources"));
|
||||||
content.set_child(Some(
|
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()
|
let layout = gtk::Box::builder()
|
||||||
|
|
|
@ -77,39 +77,53 @@ impl Message {
|
||||||
|
|
||||||
fn rust_code(&self) -> String {
|
fn rust_code(&self) -> String {
|
||||||
if self.parameters.is_empty() {
|
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 {
|
} else {
|
||||||
let mut struct_strs = vec![];
|
let mut struct_strs = vec![];
|
||||||
|
|
||||||
struct_strs.push(format!("pub struct {} {{", self.name.to_case(Case::Pascal)));
|
let parameters: String = self
|
||||||
let mut parameters: Vec<String> = self
|
|
||||||
.parameters
|
.parameters
|
||||||
.iter()
|
.iter()
|
||||||
.map(|param| format!(" {}: {},", param.name, param.ty))
|
.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("".to_owned());
|
||||||
|
|
||||||
struct_strs.push(format!(
|
let parameters: String = self
|
||||||
"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
|
|
||||||
.parameters
|
.parameters
|
||||||
.iter()
|
.iter()
|
||||||
.map(|param| format!(" args.set(\"{}\", self.{})", param.name, param.name))
|
.map(|param| format!(" args.set(\"{}\", self.{}.clone());", param.name, param.name))
|
||||||
.collect();
|
.collect::<Vec<String>>().join("\n");
|
||||||
struct_strs.append(&mut parameters);
|
|
||||||
struct_strs.push("".to_owned());
|
struct_strs.push(format!(
|
||||||
struct_strs.push(format!(" let msg = bundle.get_message(\"{}\").unwrap();", self.name));
|
"impl Message for {} {{
|
||||||
struct_strs.push(format!(" let mut errors = vec![]"));
|
fn localize(&self, bundle: &FluentErgo) -> String {{
|
||||||
struct_strs.push(format!(" msg.format_pattern(&msg.value().unwrap(), args, &mut errors)"));
|
let mut args = FluentArgs::new();
|
||||||
struct_strs.push(" }".to_owned());
|
{}
|
||||||
struct_strs.push("}".to_owned());
|
bundle.tr(\"{}\", Some(&args)).unwrap()
|
||||||
|
}}
|
||||||
|
}}",
|
||||||
|
self.name.to_case(Case::Pascal),
|
||||||
|
parameters,
|
||||||
|
self.name,
|
||||||
|
));
|
||||||
|
|
||||||
struct_strs.join("\n")
|
struct_strs.join("\n")
|
||||||
}
|
}
|
||||||
|
@ -127,6 +141,12 @@ fn main() {
|
||||||
let messages: HashMap<String, MessageJS> =
|
let messages: HashMap<String, MessageJS> =
|
||||||
serde_yaml::from_reader(File::open(input_file).unwrap()).unwrap();
|
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
|
let messages = messages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, msg)| Message::from_js(name, msg));
|
.map(|(name, msg)| Message::from_js(name, msg));
|
||||||
|
|
|
@ -1,3 +1,21 @@
|
||||||
fn main() {
|
use std::{collections::HashMap, fs::File, env};
|
||||||
println!("msggen running");
|
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
|
// 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.
|
// message, so I'm not even sure that I can automate this code generation.
|
||||||
pub trait Message {
|
pub trait Message {
|
||||||
fn msgid(&self) -> &str;
|
fn localize(&self, bundle: &FluentErgo) -> String;
|
||||||
fn args(&self) -> Option<FluentArgs>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct L10N {
|
pub struct L10N {
|
||||||
messages_root: std::path::PathBuf,
|
messages_root: std::path::PathBuf,
|
||||||
messages: FluentErgo,
|
message_bundle: FluentErgo,
|
||||||
|
|
||||||
locales: NonEmptyList<Locale>,
|
locales: NonEmptyList<Locale>,
|
||||||
zone: chrono_tz::Tz,
|
zone: chrono_tz::Tz,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl L10N {
|
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 english = "en-US".parse::<Locale>().unwrap();
|
||||||
let sys_locale = get_locale()
|
let sys_locale = get_locale()
|
||||||
.and_then(|locale_str| locale_str.parse::<Locale>().ok())
|
.and_then(|locale_str| locale_str.parse::<Locale>().ok())
|
||||||
|
@ -95,7 +94,7 @@ impl L10N {
|
||||||
let english_phrases = FluentResource::try_new
|
let english_phrases = FluentResource::try_new
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let messages = {
|
let message_bundle = {
|
||||||
let mut english_messages = messages_root.clone();
|
let mut english_messages = messages_root.clone();
|
||||||
english_messages.push("en-US.ftl");
|
english_messages.push("en-US.ftl");
|
||||||
|
|
||||||
|
@ -108,7 +107,7 @@ impl L10N {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
messages_root,
|
messages_root,
|
||||||
messages,
|
message_bundle,
|
||||||
locales,
|
locales,
|
||||||
zone,
|
zone,
|
||||||
}
|
}
|
||||||
|
@ -158,7 +157,11 @@ impl L10N {
|
||||||
// at compile time, as are their parameters. That implies an enumeration, with one element per
|
// at compile time, as are their parameters. That implies an enumeration, with one element per
|
||||||
// message, and with each element knowing its parameters.
|
// message, and with each element knowing its parameters.
|
||||||
pub fn messages(&self) -> FluentErgo {
|
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(
|
pub fn format_date_time_utc(
|
||||||
|
|
Loading…
Reference in New Issue