Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix csr locale fetching #108

Merged
merged 5 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading