Compare commits
No commits in common. "5c7f7766def86d826724b772843030837f3e1ed7" and "15b1d4bfd67b627cb8bdca0ca0d7863f9933a8c4" have entirely different histories.
5c7f7766de
...
15b1d4bfd6
|
@ -2734,11 +2734,9 @@ dependencies = [
|
||||||
"icu",
|
"icu",
|
||||||
"icu_locid",
|
"icu_locid",
|
||||||
"icu_provider",
|
"icu_provider",
|
||||||
"intl-memoizer",
|
|
||||||
"serde 1.0.193",
|
"serde 1.0.193",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
"thiserror",
|
|
||||||
"unic-langid",
|
"unic-langid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file was autogenerated from l10n-codegen-rust. Edits will be lost
|
// This file was autogenerated from l10n-codegen-rust. Edits will be lost
|
||||||
// on next generation.
|
// on next generation.
|
||||||
use l10n::Message;
|
use l10n::Message;
|
||||||
|
use fluent_ergonomics::FluentErgo;
|
||||||
use fluent::FluentArgs;
|
use fluent::FluentArgs;
|
||||||
|
|
||||||
pub struct Hello {
|
pub struct Hello {
|
||||||
|
@ -8,25 +9,24 @@ pub struct Hello {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for Hello {
|
impl Message for Hello {
|
||||||
fn msgid(&self) -> &str {
|
fn localize(&self, bundle: &FluentErgo) -> String {
|
||||||
"hello"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn args(&self) -> Option<FluentArgs> {
|
|
||||||
let mut args = FluentArgs::new();
|
let mut args = FluentArgs::new();
|
||||||
args.set("name", self.name.clone());
|
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;
|
pub struct Welcome;
|
||||||
impl Message for Welcome {
|
impl Message for Welcome {
|
||||||
fn msgid(&self) -> &str {
|
fn localize(&self, bundle: &FluentErgo) -> String {
|
||||||
"welcome"
|
bundle.tr("welcome", None).unwrap()
|
||||||
}
|
|
||||||
|
|
||||||
fn args(&self) -> Option<FluentArgs> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,24 +35,9 @@ pub struct GamesInDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for GamesInDatabase {
|
impl Message for GamesInDatabase {
|
||||||
fn msgid(&self) -> &str {
|
fn localize(&self, bundle: &FluentErgo) -> String {
|
||||||
"games-in-database"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn args(&self) -> Option<FluentArgs> {
|
|
||||||
let mut args = FluentArgs::new();
|
let mut args = FluentArgs::new();
|
||||||
args.set("count", self.count.clone());
|
args.set("count", self.count.clone());
|
||||||
Some(args)
|
bundle.tr("games-in-database", Some(&args)).unwrap()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NothingHere;
|
|
||||||
impl Message for NothingHere {
|
|
||||||
fn msgid(&self) -> &str {
|
|
||||||
"nothing-here"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn args(&self) -> Option<FluentArgs> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,9 @@ fluent = { version = "0.16" }
|
||||||
icu_locid = { version = "1" }
|
icu_locid = { version = "1" }
|
||||||
icu_provider = { version = "1" }
|
icu_provider = { version = "1" }
|
||||||
icu = { version = "1" }
|
icu = { version = "1" }
|
||||||
intl-memoizer = { version = "*" }
|
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = [ "derive" ] }
|
||||||
serde_yaml = { version = "0.9" }
|
serde_yaml = { version = "0.9" }
|
||||||
sys-locale = { version = "0.3" }
|
sys-locale = { version = "0.3" }
|
||||||
thiserror = { version = "1" }
|
|
||||||
unic-langid = { version = "*" }
|
unic-langid = { version = "*" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -81,12 +81,8 @@ impl Message {
|
||||||
|
|
||||||
struct_strs.push(format!("pub struct {}; ", self.name.to_case(Case::Pascal)));
|
struct_strs.push(format!("pub struct {}; ", self.name.to_case(Case::Pascal)));
|
||||||
struct_strs.push(format!("impl Message for {} {{
|
struct_strs.push(format!("impl Message for {} {{
|
||||||
fn msgid(&self) -> &str {{
|
fn localize(&self, bundle: &FluentErgo) -> String {{
|
||||||
\"{}\"
|
bundle.tr(\"{}\", None).unwrap()
|
||||||
}}
|
|
||||||
|
|
||||||
fn args(&self) -> Option<FluentArgs> {{
|
|
||||||
None
|
|
||||||
}}
|
}}
|
||||||
}}",
|
}}",
|
||||||
self.name.to_case(Case::Pascal),
|
self.name.to_case(Case::Pascal),
|
||||||
|
@ -118,19 +114,15 @@ impl Message {
|
||||||
|
|
||||||
struct_strs.push(format!(
|
struct_strs.push(format!(
|
||||||
"impl Message for {} {{
|
"impl Message for {} {{
|
||||||
fn msgid(&self) -> &str {{
|
fn localize(&self, bundle: &FluentErgo) -> String {{
|
||||||
\"{}\"
|
|
||||||
}}
|
|
||||||
|
|
||||||
fn args(&self) -> Option<FluentArgs> {{
|
|
||||||
let mut args = FluentArgs::new();
|
let mut args = FluentArgs::new();
|
||||||
{}
|
{}
|
||||||
Some(args)
|
bundle.tr(\"{}\", Some(&args)).unwrap()
|
||||||
}}
|
}}
|
||||||
}}",
|
}}",
|
||||||
self.name.to_case(Case::Pascal),
|
self.name.to_case(Case::Pascal),
|
||||||
self.name,
|
|
||||||
parameters,
|
parameters,
|
||||||
|
self.name,
|
||||||
));
|
));
|
||||||
|
|
||||||
struct_strs.join("\n")
|
struct_strs.join("\n")
|
||||||
|
@ -152,6 +144,7 @@ fn main() {
|
||||||
println!("// This file was autogenerated from l10n-codegen-rust. Edits will be lost
|
println!("// This file was autogenerated from l10n-codegen-rust. Edits will be lost
|
||||||
// on next generation.");
|
// on next generation.");
|
||||||
println!("use l10n::Message;");
|
println!("use l10n::Message;");
|
||||||
|
println!("use fluent_ergonomics::FluentErgo;");
|
||||||
println!("use fluent::FluentArgs;");
|
println!("use fluent::FluentArgs;");
|
||||||
|
|
||||||
let messages = messages
|
let messages = messages
|
||||||
|
|
107
l10n/src/lib.rs
107
l10n/src/lib.rs
|
@ -1,13 +1,12 @@
|
||||||
use chrono::{Datelike, NaiveDate, Timelike};
|
use chrono::{Datelike, NaiveDate, Timelike};
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use fixed_decimal::FixedDecimal;
|
use fixed_decimal::FixedDecimal;
|
||||||
use fluent::{bundle::FluentBundle, FluentResource};
|
use fluent::{FluentBundle, FluentResource};
|
||||||
|
use fluent_ergonomics::FluentErgo;
|
||||||
use icu::{datetime::options::length, decimal::FixedDecimalFormatter, locid::Locale};
|
use icu::{datetime::options::length, decimal::FixedDecimalFormatter, locid::Locale};
|
||||||
use icu_provider::DataLocale;
|
use icu_provider::DataLocale;
|
||||||
use std::{fs::File, io::Read, ops::Deref};
|
use std::{collections::HashMap, ops::Deref, path::Path};
|
||||||
use sys_locale::get_locale;
|
use sys_locale::get_locale;
|
||||||
use thiserror::Error;
|
|
||||||
use unic_langid::LanguageIdentifierError;
|
|
||||||
|
|
||||||
// Re-exports. I'm doing these so that clients of this library don't have to go tracking down
|
// Re-exports. I'm doing these so that clients of this library don't have to go tracking down
|
||||||
// additional structures
|
// additional structures
|
||||||
|
@ -47,9 +46,7 @@ impl<A> Deref for NonEmptyList<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum L10NError {
|
pub enum L10NError {
|
||||||
#[error("Unparsable Locale")]
|
|
||||||
UnparsableLocale,
|
UnparsableLocale,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,33 +56,6 @@ impl From<icu::locid::Error> for L10NError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum FileLoadError {
|
|
||||||
#[error("Unparsable Locale")]
|
|
||||||
UnparsableLocale,
|
|
||||||
|
|
||||||
#[error("Source string file not found")]
|
|
||||||
FileNotFound,
|
|
||||||
|
|
||||||
#[error("The Fluent file is malformed")]
|
|
||||||
FluentParseError,
|
|
||||||
|
|
||||||
#[error("An unknown IO error was found")]
|
|
||||||
IOError(std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LanguageIdentifierError> for FileLoadError {
|
|
||||||
fn from(_: LanguageIdentifierError) -> Self {
|
|
||||||
Self::UnparsableLocale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for FileLoadError {
|
|
||||||
fn from(err: std::io::Error) -> Self {
|
|
||||||
Self::IOError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Potential Message structure.
|
// Potential Message structure.
|
||||||
//
|
//
|
||||||
// Let's assume the application has an enumeration that implements Message. For each element of the
|
// Let's assume the application has an enumeration that implements Message. For each element of the
|
||||||
|
@ -98,13 +68,12 @@ impl From<std::io::Error> for FileLoadError {
|
||||||
// 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,
|
||||||
message_bundles: Vec<FluentBundle<FluentResource, intl_memoizer::concurrent::IntlLangMemoizer>>,
|
message_bundle: FluentErgo,
|
||||||
|
|
||||||
locales: NonEmptyList<Locale>,
|
locales: NonEmptyList<Locale>,
|
||||||
zone: chrono_tz::Tz,
|
zone: chrono_tz::Tz,
|
||||||
|
@ -125,39 +94,31 @@ impl L10N {
|
||||||
let english_phrases = FluentResource::try_new
|
let english_phrases = FluentResource::try_new
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let mut s = Self {
|
let message_bundle = {
|
||||||
messages_root,
|
let mut english_messages = messages_root.clone();
|
||||||
message_bundles: vec![],
|
english_messages.push("en-US.ftl");
|
||||||
locales,
|
|
||||||
zone,
|
let langid: unic_langid::LanguageIdentifier = english.to_string().parse().unwrap();
|
||||||
|
let mut messages = FluentErgo::new(&[langid.clone()]);
|
||||||
|
let _ = messages.add_from_file(langid, &english_messages);
|
||||||
|
|
||||||
|
messages
|
||||||
};
|
};
|
||||||
|
|
||||||
s.load_messages_from_file("en-US".to_owned()).unwrap();
|
Self {
|
||||||
|
messages_root,
|
||||||
s
|
message_bundle,
|
||||||
|
locales,
|
||||||
|
zone,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_messages_from_file(&mut self, locale: String) -> Result<(), FileLoadError> {
|
pub fn load_messages_from_file(
|
||||||
let langid: unic_langid::LanguageIdentifier = locale.parse()?;
|
&mut self,
|
||||||
|
locale: String,
|
||||||
let mut path = self.messages_root.clone();
|
path: &Path,
|
||||||
path.push(locale);
|
) -> Result<(), L10NError> {
|
||||||
path.set_extension("ftl");
|
unimplemented!()
|
||||||
println!("{:?}", path);
|
|
||||||
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
let mut f = File::open(path)?;
|
|
||||||
f.read_to_end(&mut buffer)?;
|
|
||||||
let text = String::from_utf8(buffer).unwrap();
|
|
||||||
match FluentResource::try_new(text) {
|
|
||||||
Ok(resource) => {
|
|
||||||
let mut bundle = FluentBundle::new_concurrent(vec![langid]);
|
|
||||||
let _ = bundle.add_resource(resource);
|
|
||||||
self.message_bundles.push(bundle);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err((_, _error)) => Err(FileLoadError::FluentParseError),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, whenever the user changes the locales, the list of messages has to change. How do we
|
// Now, whenever the user changes the locales, the list of messages has to change. How do we
|
||||||
|
@ -195,20 +156,12 @@ impl L10N {
|
||||||
// parameters. In an ideal world, neither of these can be incorrect. Messages are all checked
|
// parameters. In an ideal world, neither of these can be incorrect. Messages are all checked
|
||||||
// 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) -> Vec<FluentBundle<FluentResource>> {
|
pub fn messages(&self) -> FluentErgo {
|
||||||
// self.message_bundles.clone()
|
self.message_bundle.clone()
|
||||||
// }
|
}
|
||||||
|
|
||||||
pub fn tr(&self, message: impl Message) -> String {
|
pub fn tr(&self, message: impl Message) -> String {
|
||||||
println!("message: {}", message.msgid());
|
message.localize(&self.message_bundle)
|
||||||
let msg = self.message_bundles[0]
|
|
||||||
.get_message(message.msgid())
|
|
||||||
.and_then(|msg| msg.value())
|
|
||||||
.unwrap();
|
|
||||||
let mut errors = vec![];
|
|
||||||
self.message_bundles[0]
|
|
||||||
.format_pattern(msg, message.args().as_ref(), &mut errors)
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_date_time_utc(
|
pub fn format_date_time_utc(
|
||||||
|
|
Loading…
Reference in New Issue