Skip to content

Commit

Permalink
Merge pull request #108 from Baptistemontan/fix-csr-fetch-locale
Browse files Browse the repository at this point in the history
Fix csr locale fetching
  • Loading branch information
Baptistemontan authored Jul 27, 2024
2 parents 789da92 + 59f2b8e commit 585c7dc
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 91 deletions.
15 changes: 10 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,25 @@ env:
CARGO_TERM_COLOR: always

jobs:
test:
name: Test
tests:
name: Execute all lib tests (chunk ${{ matrix.chunk }}/${{ strategy.job-total }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
chunk: [1, 2]
steps:
- name: "Checkout repo"
uses: actions/checkout@v3

- name: "Load cargo toolchain"
uses: dtolnay/rust-toolchain@stable

- name: "Install cargo-all-features"
run: cargo install cargo-all-features

- name: "Test all features"
run: |
cargo install cargo-all-features
cargo test-all-features
run: cargo test-all-features --n-chunks ${{ strategy.job-total }} --chunk ${{ matrix.chunk }}

compile_ssr_examples:
name: Compile ${{ matrix.examples }} example
Expand Down
11 changes: 6 additions & 5 deletions leptos_i18n/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,21 @@ readme = "../README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
cfg-if = "1.0.0"
leptos_i18n_macro = { workspace = true }
leptos = "0.6"
leptos_meta = "0.6"
actix-web = { version = "4", optional = true }
axum = { version = "0.7", optional = true }
leptos_axum = { version = "0.6", optional = true }
web-sys = { version = "0.3", optional = true, features = ["HtmlDocument"] }
wasm-bindgen = { version = "0.2", optional = true }
web-sys = { version = "0.3", features = ["HtmlDocument", "Navigator"] }
wasm-bindgen = { version = "0.2" }
http = { version = "1", optional = true }

[features]
default = ["cookie", "json_files"]
nightly = ["leptos/nightly", "leptos_meta/nightly", "leptos_i18n_macro/nightly"]
cookie = ["dep:web-sys", "dep:wasm-bindgen"]
cookie = []
hydrate = ["leptos/hydrate", "leptos_meta/hydrate"]
ssr = ["leptos/ssr", "leptos_meta/ssr"]
actix = ["ssr", "dep:actix-web"]
axum = ["ssr", "dep:axum", "dep:leptos_axum", "dep:http"]
csr = ["leptos/csr", "leptos_meta/csr"]
Expand All @@ -44,6 +42,9 @@ experimental-islands = [
]
show_keys_only = ["leptos_i18n_macro/show_keys_only"]

# internally used features, not meant to be manually enabled
ssr = ["leptos/ssr", "leptos_meta/ssr"]


[package.metadata.cargo-all-features]
denylist = [
Expand Down
9 changes: 6 additions & 3 deletions leptos_i18n/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ fn init_context<T: Locale>() -> I18nContext<T> {
create_isomorphic_effect(move |_| {
let new_lang = locale.get();
set_html_lang_attr(new_lang.as_str());
#[cfg(all(feature = "cookie", any(feature = "hydrate", feature = "csr")))]
set_lang_cookie::<T>(new_lang);
if cfg!(all(
feature = "cookie",
any(feature = "hydrate", feature = "csr")
)) {
set_lang_cookie::<T>(new_lang);
}
});

let context = I18nContext::<T>(locale);
Expand Down Expand Up @@ -101,7 +105,6 @@ pub fn use_i18n_context<T: Locale>() -> I18nContext<T> {
use_context().expect("I18nContext is missing, use provide_i18n_context() to provide it.")
}

#[cfg(all(feature = "cookie", any(feature = "hydrate", feature = "csr")))]
fn set_lang_cookie<T: Locale>(lang: T) -> Option<()> {
use crate::COOKIE_PREFERED_LANG;
let document = super::get_html_document()?;
Expand Down
85 changes: 52 additions & 33 deletions leptos_i18n/src/fetch_locale.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,58 @@
use crate::Locale;

cfg_if::cfg_if! {
if #[cfg(all(feature = "ssr", not(any(feature = "hydrate", all(feature="csr", feature="cookie")))))] {
#[inline]
pub fn fetch_locale<T: Locale>() -> T {
crate::server::fetch_locale_server_side::<T>()
}
} else if #[cfg(all(feature = "hydrate", not(any(all(feature="csr", feature="cookie"), feature = "ssr"))))] {
pub fn fetch_locale<T: Locale>() -> T {
leptos::document()
.document_element()
.and_then(|el| el.get_attribute("lang"))
.and_then(|lang| T::from_str(&lang))
.unwrap_or_default()
}
} else if #[cfg(all(all(feature="csr", feature="cookie"), not(any(feature = "ssr", feature = "hydrate"))))] {
pub fn fetch_locale<T: Locale>() -> T {
fn inner<T: Locale>() -> Option<T> {
let document = super::get_html_document()?;
let cookies = document.cookie().ok()?;
cookies.split(';').find_map(|cookie| {
let (key, value) = cookie.split_once('=')?;
if key.trim() == super::COOKIE_PREFERED_LANG {
T::from_str(value)
} else {
None
}
})
}
inner().unwrap_or_default()
}
#[inline]
pub fn fetch_locale<T: Locale>() -> T {
if cfg!(feature = "ssr") {
fetch_locale_ssr()
} else if cfg!(feature = "hydrate") {
fetch_locale_hydrate()
} else if cfg!(feature = "csr") {
fetch_locale_csr()
} else {
#[inline]
pub fn fetch_locale<T: Locale>() -> T {
Default::default()
Default::default()
}
}

// ssr fetch
fn fetch_locale_ssr<T: Locale>() -> T {
crate::server::fetch_locale_server_side::<T>()
}

// hydrate fetch
fn fetch_locale_hydrate<T: Locale>() -> T {
leptos::document()
.document_element()
.and_then(|el| el.get_attribute("lang"))
.and_then(|lang| T::from_str(&lang))
.unwrap_or_default()
}

// csr fetch
fn fetch_locale_csr<T: Locale>() -> T {
fn get_lang_cookie<T: Locale>() -> Option<T> {
let document = super::get_html_document()?;
let cookies = document.cookie().ok()?;
cookies.split(';').find_map(|cookie| {
let (key, value) = cookie.split_once('=')?;
if key.trim() == super::COOKIE_PREFERED_LANG {
T::from_str(value)
} else {
None
}
})
}

if cfg!(feature = "cookie") {
if let Some(lang) = get_lang_cookie() {
return lang;
}
}

leptos::window()
.navigator()
.languages()
.into_iter()
.filter_map(|js_val| js_val.as_string())
.find_map(|pref_lang| T::from_str(&pref_lang))
.unwrap_or_default()
}
17 changes: 3 additions & 14 deletions leptos_i18n/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,36 +128,25 @@
mod context;
mod fetch_locale;
mod locale_traits;
#[cfg(all(
feature = "ssr",
not(any(feature = "hydrate", all(feature = "csr", feature = "cookie")))
))]
mod server;

#[cfg(feature = "interpolate_display")]
pub mod display;

pub use locale_traits::*;

pub use context::{provide_i18n_context, use_i18n_context, I18nContext};

pub use leptos_i18n_macro::{load_locales, t, td, tu};

#[cfg(feature = "interpolate_display")]
pub use leptos_i18n_macro::{t_display, t_string, td_display, td_string, tu_display, tu_string};
pub use leptos_i18n_macro::{
load_locales, t, t_display, t_string, td, td_display, td_string, tu, tu_display, tu_string,
};

#[doc(hidden)]
pub mod __private {
pub use super::locale_traits::BuildStr;
}

#[cfg(all(
feature = "cookie",
any(feature = "ssr", feature = "hydrate", feature = "csr")
))]
pub(crate) const COOKIE_PREFERED_LANG: &str = "i18n_pref_locale";

#[cfg(all(feature = "cookie", any(feature = "hydrate", feature = "csr")))]
pub(crate) fn get_html_document() -> Option<web_sys::HtmlDocument> {
use wasm_bindgen::JsCast;
leptos::document().dyn_into::<web_sys::HtmlDocument>().ok()
Expand Down
13 changes: 7 additions & 6 deletions leptos_i18n/src/server/actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ pub fn fetch_locale_server<T: Locale>() -> T {
}

fn from_req<T: Locale>(req: &actix_web::HttpRequest) -> T {
#[cfg(feature = "cookie")]
if let Some(pref) = req
.cookie(crate::COOKIE_PREFERED_LANG)
.and_then(|ck| T::from_str(ck.value()))
{
return pref;
if cfg!(feature = "cookie") {
if let Some(pref) = req
.cookie(crate::COOKIE_PREFERED_LANG)
.and_then(|ck| T::from_str(ck.value()))
{
return pref;
}
}

let Some(header) = req
Expand Down
9 changes: 4 additions & 5 deletions leptos_i18n/src/server/axum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ pub fn fetch_locale_server<T: Locale>() -> T {
}

fn from_req<T: Locale>(req: &Parts) -> T {
#[cfg(feature = "cookie")]
if let Some(pref_lang_cookie) = get_prefered_lang_cookie::<T>(req) {
return pref_lang_cookie;
if cfg!(feature = "cookie") {
if let Some(pref_lang_cookie) = get_prefered_lang_cookie::<T>(req) {
return pref_lang_cookie;
}
}

let Some(header) = req
Expand All @@ -29,7 +30,6 @@ fn from_req<T: Locale>(req: &Parts) -> T {
T::find_locale(&langs)
}

#[cfg(feature = "cookie")]
fn get_prefered_lang_cookie<T: Locale>(req: &Parts) -> Option<T> {
req.headers
.get_all(header::COOKIE)
Expand All @@ -39,7 +39,6 @@ fn get_prefered_lang_cookie<T: Locale>(req: &Parts) -> Option<T> {
.next()
}

#[cfg(feature = "cookie")]
fn parse_cookie(cookie: &axum::http::HeaderValue) -> Option<&str> {
std::str::from_utf8(cookie.as_bytes())
.ok()?
Expand Down
21 changes: 14 additions & 7 deletions leptos_i18n/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,28 @@ use crate::Locale;
use actix as backend;
#[cfg(all(feature = "axum", not(feature = "actix")))]
use axum as backend;
#[cfg(any(
all(feature = "actix", feature = "axum"),
all(not(feature = "actix"), not(feature = "axum"))
))]
mod backend {
use super::Locale;
pub fn fetch_locale_server<T: Locale>() -> T {
unreachable!()
}
}

#[cfg(any(feature = "actix", feature = "axum"))]
pub fn fetch_locale_server_side<T: Locale>() -> T {
backend::fetch_locale_server::<T>()
}

#[cfg(all(feature = "actix", feature = "axum"))]
compile_error!("Can't enable \"actix\" and \"axum\" features together.");

#[cfg(not(any(feature = "actix", feature = "axum")))]
pub fn fetch_locale_server_side<T: Locale>() -> T {
compile_error!("Need either \"actix\" or \"axum\" feature to be enabled in ssr. Don't use the \"ssr\" feature, it is directly enable by the \"actix\" or \"axum\" feature.")
}
#[cfg(all(feature = "ssr", not(any(feature = "actix", feature = "axum"))))]
compile_error!("Need either \"actix\" or \"axum\" feature to be enabled in ssr. Don't use the \"ssr\" feature, it is directly enable by the \"actix\" or \"axum\" feature.");

#[cfg(any(feature = "actix", feature = "axum"))]
#[allow(unused)]
pub(crate) fn parse_header(header: &str) -> Vec<String> {
let mut parsed_lang: Vec<_> = header
.split(';')
Expand Down Expand Up @@ -52,7 +59,7 @@ pub(crate) fn parse_header(header: &str) -> Vec<String> {
.collect()
}

#[cfg(all(test, any(feature = "actix", feature = "axum")))]
#[cfg(test)]
mod tests {
use super::*;

Expand Down
41 changes: 28 additions & 13 deletions leptos_i18n_macro/src/load_locales/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use super::{
warning::{emit_warning, Warning},
};

macro_rules! files_formats {
macro_rules! define_by_format {
(json => $($tt:tt)*) => {
#[cfg(all(feature = "json_files", not(any(feature = "yaml_files"))))]
$($tt)*
Expand All @@ -34,35 +34,50 @@ macro_rules! files_formats {
}
}

files_formats!(json => pub type SerdeError = serde_json::Error;);
files_formats!(yaml => pub type SerdeError = serde_yaml::Error;);
files_formats!(none => pub type SerdeError = &'static str;); // whatever impl Display
files_formats!(multiple => pub type SerdeError = &'static str;); // whatever impl Display
macro_rules! define_error {
($ident:ident => $t:ty) => {
define_by_format!($ident => pub type SerdeError = $t;);
};
}

macro_rules! define_files_exts {
($ident:ident => $($lit:literal),*) => {
define_by_format!($ident => const FILE_EXTS: &[&str] = &[$($lit,)*];);
};
($ident:ident) => {
define_by_format!($ident => const FILE_EXTS: &[&str] = &[];);
};
}

define_error!(json => serde_json::Error);
define_error!(yaml => serde_yaml::Error);
define_error!(none => &'static str); // whatever impl Display
define_error!(multiple => &'static str); // whatever impl Display

files_formats!(json => const FILE_EXTS: &[&str] = &["json"];);
files_formats!(yaml => const FILE_EXTS: &[&str] = &["yaml", "yml"];);
files_formats!(none => const FILE_EXTS: &[&str] = &[];);
files_formats!(multiple => const FILE_EXTS: &[&str] = &[];);
define_files_exts!(json => "json");
define_files_exts!(yaml => "yaml", "yml");
define_files_exts!(none);
define_files_exts!(multiple);

files_formats!(json =>
define_by_format!(json =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let mut deserializer = serde_json::Deserializer::from_reader(locale_file);
serde::de::DeserializeSeed::deserialize(seed, &mut deserializer)
}
);
files_formats!(yaml =>
define_by_format!(yaml =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let deserializer = serde_yaml::Deserializer::from_reader(locale_file);
serde::de::DeserializeSeed::deserialize(seed, deserializer)
}
);
files_formats!(none =>
define_by_format!(none =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let _ = (locale_file, seed);
compile_error!("No file format has been provided for leptos_i18n, supported formats are: json and yaml")
}
);
files_formats!(multiple =>
define_by_format!(multiple =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let _ = (locale_file, seed);
compile_error!("Multiple file format have been provided for leptos_i18n, choose only one, supported formats are: json and yaml")
Expand Down

0 comments on commit 585c7dc

Please sign in to comment.