diff --git a/Cargo.lock b/Cargo.lock index e8141b1..8062b18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2709,6 +2709,8 @@ dependencies = [ "libadwaita", "pango", "sgf", + "sys-locale", + "thiserror", "tokio", ] diff --git a/kifu/gtk/Cargo.toml b/kifu/gtk/Cargo.toml index f3fe180..d02b7fe 100644 --- a/kifu/gtk/Cargo.toml +++ b/kifu/gtk/Cargo.toml @@ -13,8 +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" } +fluent = { version = "0.16" } gio = { version = "0.18" } glib = { version = "0.18" } gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] } @@ -23,6 +23,8 @@ kifu-core = { path = "../core" } l10n = { path = "../../l10n" } pango = { version = "*" } sgf = { path = "../../sgf" } +sys-locale = { version = "0.3" } +thiserror = { version = "1" } tokio = { version = "1.26", features = [ "full" ] } [build-dependencies] diff --git a/kifu/gtk/resources/en-US.ftl b/kifu/gtk/messages/en-US.ftl similarity index 100% rename from kifu/gtk/resources/en-US.ftl rename to kifu/gtk/messages/en-US.ftl diff --git a/kifu/gtk/resources/eo.ftl b/kifu/gtk/messages/eo.ftl similarity index 100% rename from kifu/gtk/resources/eo.ftl rename to kifu/gtk/messages/eo.ftl diff --git a/kifu/gtk/messages/en.yaml b/kifu/gtk/messages/source.yaml similarity index 100% rename from kifu/gtk/messages/en.yaml rename to kifu/gtk/messages/source.yaml diff --git a/kifu/gtk/src/lib.rs b/kifu/gtk/src/lib.rs index 1f0364b..7843e36 100644 --- a/kifu/gtk/src/lib.rs +++ b/kifu/gtk/src/lib.rs @@ -1,3 +1,9 @@ +use async_std::task::yield_now; +use kifu_core::{Core, CoreRequest, Observable}; +use l10n::{NonEmptyList, L10N}; +use std::{rc::Rc, sync::Arc}; +use tokio::runtime::Runtime; + mod messages { include!(env!("KIFU_GTK_MESSAGES")); } @@ -7,11 +13,6 @@ pub mod ui; mod view_models; mod views; -use async_std::task::yield_now; -use kifu_core::{Core, CoreRequest, CoreResponse, Observable}; -use std::{rc::Rc, sync::Arc}; -use tokio::runtime::Runtime; - #[derive(Clone)] pub struct CoreApi { pub rt: Arc, @@ -43,6 +44,45 @@ where result } +/// AppContext is our global store for things that need to be available everywhere. Developers +/// should never pass this around directly, but should instead pass it around by traits. This is to +/// provide systems such as: +/// +/// - Feature Flags +/// - L10N +pub struct AppContext { + pub l10n: L10N, +} + +#[derive(Debug)] +pub enum AppContextInitError {} + +impl AppContext { + pub fn new() -> Result { + let mut locale_list = NonEmptyList::new("en-US"); + + let user_locale = sys_locale::get_locale().unwrap(); + println!("user locale: {}", user_locale); + if locale_list.find(|l| *l == user_locale).is_none() { + locale_list.push(&user_locale); + } + let mut l10n = l10n::L10N::new("messages".into()); + l10n.set_locales(locale_list); + + Ok(Self { l10n }) + } +} + +pub trait ProvidesL10N { + fn l10n(&self) -> &L10N; +} + +impl ProvidesL10N for AppContext { + fn l10n(&self) -> &L10N { + &self.l10n + } +} + /// LocalObserver creates a task on the current thread which watches the specified observer for notifications and calls the handler function with each one. /// /// The LocalObserver starts a task which listens for notifications during the constructor. When the observer goes out of scope, it will make a point of aborting the task. This combination means that anything which uses the observer can create it, hold on to a reference of it, and then drop it when done, and not have to do anything else with the observer object. diff --git a/kifu/gtk/src/main.rs b/kifu/gtk/src/main.rs index 4601569..6459dda 100644 --- a/kifu/gtk/src/main.rs +++ b/kifu/gtk/src/main.rs @@ -3,8 +3,9 @@ use kifu_core::{Config, ConfigOption, Core, CoreRequest, CoreResponse, DatabaseP use kifu_gtk::{ perftrace, ui::{AppWindow, ConfigurationPage, Home, PlayingField}, - CoreApi, + AppContext, CoreApi, }; +use l10n::NonEmptyList; use std::sync::{Arc, RwLock}; const APP_ID_DEV: &str = "com.luminescent-dreams.kifu-gtk.dev"; @@ -104,7 +105,9 @@ fn main() { app.connect_activate({ let runtime = runtime.clone(); move |app| { - let app_window = AppWindow::new(app); + + let ctx = AppContext::new().unwrap(); + let app_window = AppWindow::new(app, &ctx); let api = CoreApi { rt: runtime.clone(), diff --git a/kifu/gtk/src/ui/mod.rs b/kifu/gtk/src/ui/mod.rs index 0fde919..e33aaea 100644 --- a/kifu/gtk/src/ui/mod.rs +++ b/kifu/gtk/src/ui/mod.rs @@ -4,7 +4,7 @@ use glib::IsA; use gtk::STYLE_PROVIDER_PRIORITY_USER; use l10n::{L10N, NonEmptyList}; use std::path::PathBuf; -use crate::messages; +use crate::{ProvidesL10N, messages}; mod chat; pub use chat::Chat; @@ -40,7 +40,7 @@ pub struct AppWindow { } impl AppWindow { - pub fn new(app: &adw::Application) -> Self { + pub fn new(app: &adw::Application, ctx: &impl ProvidesL10N) -> Self { let window = adw::ApplicationWindow::builder() .application(app) .width_request(800) @@ -78,10 +78,8 @@ impl AppWindow { header.pack_end(&hamburger); let content = adw::Bin::builder().css_classes(vec!["content"]).build(); - let mut l10n = L10N::new(PathBuf::from("resources")); - l10n.set_locales(NonEmptyList::from_iter(vec!["en-US", "eo"]).unwrap()); content.set_child(Some( - &adw::StatusPage::builder().title(l10n.tr(messages::NothingHere)).build(), + &adw::StatusPage::builder().title(ctx.l10n().tr(messages::NothingHere)).build(), )); let layout = gtk::Box::builder() diff --git a/l10n/src/lib.rs b/l10n/src/lib.rs index 51b98a8..dda3b6a 100644 --- a/l10n/src/lib.rs +++ b/l10n/src/lib.rs @@ -22,7 +22,7 @@ pub enum NonEmptyListError { pub struct NonEmptyList(Vec); impl NonEmptyList { - fn new(elem: A) -> Self { + pub fn new(elem: A) -> Self { Self(vec![elem]) } @@ -37,6 +37,14 @@ impl NonEmptyList { } } + pub fn push(&mut self, item: A) { + self.0.push(item); + } + + pub fn find(&self, f: impl Fn(&A) -> bool) -> Option<&A> { + self.0.iter().find(|item| f(*item)) + } + fn first(&self) -> &A { &self.0[0] }