Add language switching, fix logout

This commit is contained in:
2021-08-12 15:48:02 +02:00
committed by dietrich
parent fa924a8e8c
commit 0a23b786b0
16 changed files with 220 additions and 85 deletions
+20 -34
View File
@@ -2,7 +2,7 @@ use std::sync::Arc;
use fluent::{FluentArgs, FluentBundle, FluentResource};
use seed::log;
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
use shared::datatypes::Lang;
use unic_langid::LanguageIdentifier;
// A struct containing the functions and the current language to query the localized strings.
@@ -22,10 +22,8 @@ impl I18n {
/// Create a new translator struct
#[must_use]
pub fn new(lang: Lang) -> Self {
Self {
lang,
ftl_bundle: Arc::new(lang.create_ftl_bundle()),
}
let ftl_bundle = Arc::new(Self::create_ftl_bundle(lang));
Self { lang, ftl_bundle }
}
/// Get the current language
@@ -35,10 +33,9 @@ impl I18n {
}
/// Set the current language
pub fn set_lang(&mut self, lang: Lang) -> &Self {
pub fn set_lang(&mut self, lang: Lang) {
self.lang = lang;
self.ftl_bundle = Arc::new(lang.create_ftl_bundle());
self
self.ftl_bundle = Arc::new(Self::create_ftl_bundle(lang));
}
/// Get a localized string. Optionally with parameters provided in `args`.
@@ -57,55 +54,44 @@ impl I18n {
}
}
/// An `enum` containing the available languages.
/// To add an additional language add it to this enum aswell as an appropriate file into the locales folder.
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Copy, Clone, Display, EnumIter, EnumString, AsRefStr, Eq, PartialEq)]
pub enum Lang {
#[strum(serialize = "en-US")]
EnUS,
#[strum(serialize = "de-DE")]
DeDE,
}
impl Lang {
impl I18n {
/// Prettyprint the language name
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::EnUS => "English (US)",
Self::DeDE => "Deutsch (Deutschland)",
pub const fn label(&self) -> &'static str {
match self.lang {
Lang::EnUS => "English (US)",
Lang::DeDE => "Deutsch (Deutschland)",
}
}
/// include the fluent messages into the binary
#[must_use]
pub const fn ftl_messages(self) -> &'static str {
pub const fn ftl_messages(lang: Lang) -> &'static str {
macro_rules! include_ftl_messages {
( $lang_id:literal ) => {
include_str!(concat!("../../locales/", $lang_id, "/main.ftl"))
};
}
match self {
Self::EnUS => include_ftl_messages!("en"),
Self::DeDE => include_ftl_messages!("de"),
match lang {
Lang::EnUS => include_ftl_messages!("en"),
Lang::DeDE => include_ftl_messages!("de"),
}
}
#[must_use]
pub fn to_language_identifier(self) -> LanguageIdentifier {
self.as_ref()
pub fn language_identifier(lang: Lang) -> LanguageIdentifier {
lang.as_ref()
.parse()
.expect("parse Lang to LanguageIdentifier")
}
/// Create and initialize a fluent bundle.
#[must_use]
pub fn create_ftl_bundle(self) -> FluentBundle<FluentResource> {
let ftl_resource =
FluentResource::try_new(self.ftl_messages().to_owned()).expect("parse FTL messages");
pub fn create_ftl_bundle(lang: Lang) -> FluentBundle<FluentResource> {
let ftl_resource = FluentResource::try_new(Self::ftl_messages(lang).to_owned())
.expect("parse FTL messages");
let mut bundle = FluentBundle::new(vec![self.to_language_identifier()]);
let mut bundle = FluentBundle::new(vec![Self::language_identifier(lang)]);
bundle.add_resource(ftl_resource).expect("add FTL resource");
bundle
}
+55 -1
View File
@@ -7,9 +7,10 @@ use pages::list_users;
use seed::window;
use seed::{attrs, button, div, input, label, log, prelude::*, App, Url, C};
use shared::apirequests::users::LoginUser;
use shared::datatypes::Lang;
use shared::datatypes::{Loadable, User};
use crate::i18n::{I18n, Lang};
use crate::i18n::I18n;
// ------ ------
// Init
@@ -47,6 +48,17 @@ struct Model {
login_data: LoginUser,
}
impl Model {
fn set_lang(&mut self, l: Lang) {
self.i18n.set_lang(l);
match &mut self.page {
Page::Home(ref mut m) => m.set_lang(l),
Page::ListUsers(ref mut m) => m.set_lang(l),
Page::NotFound => (),
}
}
}
#[derive(Default, Debug)]
struct LoginForm {
username: ElRef<web_sys::HtmlInputElement>,
@@ -104,6 +116,18 @@ impl Page {
_other => Self::NotFound,
};
orders.perform_cmd(async {
// create request
let request = Request::new("/admin/json/get_language/");
// perform and get response
let response = unwrap_or_return!(fetch(request).await, Msg::NoMessage);
// validate response status
let response = unwrap_or_return!(response.check_status(), Msg::NoMessage);
let lang: Lang = unwrap_or_return!(response.json().await, Msg::NoMessage);
Msg::LanguageChanged(lang)
});
log!("Page initialized");
result
}
@@ -125,6 +149,8 @@ pub enum Msg {
Login,
UsernameChanged(String),
PasswordChanged(String),
SetLanguage(Lang),
LanguageChanged(Lang),
}
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
@@ -163,6 +189,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
});
}
Msg::UserReceived(user) => {
model.set_lang(user.language);
model.user = Loadable::Data(Some(user));
model.page = Page::init(
model.location.current_url.clone(),
@@ -175,6 +202,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
model.user = Loadable::Data(None);
logout(orders)
}
model.user = Loadable::Data(None);
}
Msg::Logout => {
model.user = Loadable::Data(None);
@@ -183,9 +211,35 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
Msg::Login => login_user(model, orders),
Msg::UsernameChanged(s) => model.login_data.username = s,
Msg::PasswordChanged(s) => model.login_data.password = s,
Msg::SetLanguage(l) => {
change_language(l, orders);
}
Msg::LanguageChanged(l) => {
log!("Changed Language", &l);
model.set_lang(l);
}
}
}
fn change_language(l: Lang, orders: &mut impl Orders<Msg>) {
orders.perform_cmd(async move {
// create request
let request = unwrap_or_return!(
Request::new("/admin/json/change_language/")
.method(Method::Post)
.json(&l),
Msg::NoMessage
);
// perform and get response
let response = unwrap_or_return!(fetch(request).await, Msg::NoMessage);
// validate response status
let response = unwrap_or_return!(response.check_status(), Msg::NoMessage);
let l: Lang = unwrap_or_return!(response.json().await, Msg::NoMessage);
Msg::LanguageChanged(l)
});
}
fn logout(orders: &mut impl Orders<Msg>) {
orders.perform_cmd(async {
let request = Request::new("/admin/logout/");
+8 -2
View File
@@ -1,6 +1,6 @@
use fluent::fluent_args;
use seed::{a, attrs, div, li, nav, ol, prelude::*, Url};
use shared::datatypes::User;
use seed::{a, attrs, div, li, nav, ol, prelude::*, Url, C};
use shared::datatypes::{Lang, User};
use crate::{i18n::I18n, Msg};
@@ -50,6 +50,12 @@ pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
],],
],
ol![
li![div![
C!("languageselector"),
t("language"),
a![ev(Ev::Click, |_| Msg::SetLanguage(Lang::DeDE)), "de"],
a![ev(Ev::Click, |_| Msg::SetLanguage(Lang::EnUS)), "en"]
]],
// The Welcome message
li![div![welcome]],
// The logout button
+7 -1
View File
@@ -13,7 +13,7 @@ use shared::{
general::{EditMode, Message, Operation, Status},
links::{LinkDelta, LinkOverviewColumns, LinkRequestForm},
},
datatypes::{FullLink, Loadable},
datatypes::{FullLink, Lang, Loadable},
};
use crate::{get_host, i18n::I18n, unwrap_or_return};
@@ -54,6 +54,12 @@ pub struct Model {
handle_timeout: Option<CmdHandle>, // Rendering qr-codes takes time... it is aborted when this handle is dropped and replaced.
}
impl Model {
pub fn set_lang(&mut self, l: Lang) {
self.i18n.set_lang(l);
}
}
#[derive(Debug, Clone)]
enum Dialog {
EditLink {
+7 -1
View File
@@ -10,7 +10,7 @@ use shared::{
general::{EditMode, Status},
users::{UserDelta, UserOverviewColumns, UserRequestForm},
},
datatypes::User,
datatypes::{Lang, User},
};
/*
* init
@@ -42,6 +42,12 @@ pub struct Model {
last_message: Option<Status>,
}
impl Model {
pub fn set_lang(&mut self, l: Lang) {
self.i18n.set_lang(l);
}
}
impl Model {
fn clean_dialogs(&mut self) {
self.last_message = None;