Skip to content

Commit

Permalink
feat(i18n): added fallback non-wasm locale redirection
Browse files Browse the repository at this point in the history
Perseus will now work with i18n if JS, Wasm, or both are disabled,
meaning progressive enhancement is now complete.
  • Loading branch information
arctic-hen7 committed Nov 27, 2021
1 parent de1c217 commit 589ac1b
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 7 deletions.
16 changes: 11 additions & 5 deletions packages/perseus-actix-web/src/initial_load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use perseus::{
error_pages::ErrorPageData,
i18n::{TranslationsManager, Translator},
router::{match_route, RouteInfo, RouteVerdict},
serve::{get_page_for_template, interpolate_page_data},
serve::{
get_page_for_template, interpolate_locale_redirection_fallback, interpolate_page_data,
},
},
stores::{ImmutableStore, MutableStore},
ErrorPages, SsrNode,
Expand Down Expand Up @@ -150,12 +152,16 @@ pub async fn initial_load<M: MutableStore, T: TranslationsManager>(
http_res.body(final_html)
}
// For locale detection, we don't know the user's locale, so there's not much we can do except send down the app shell, which will do the rest and fetch from `.perseus/page/...`
RouteVerdict::LocaleDetection(_) => {
RouteVerdict::LocaleDetection(path) => {
// We use a `302 Found` status code to indicate a redirect
// We 'should' generate a `Location` field for the redirect, but it's not RFC-mandated, so we can use the app shell
HttpResponse::Found()
.content_type("text/html")
.body(html_shell.get_ref())
HttpResponse::Found().content_type("text/html").body(
interpolate_locale_redirection_fallback(
html_shell.get_ref(),
// We'll redirect the user to the default locale
&format!("{}/{}", opts.locales.default, path),
),
)
}
RouteVerdict::NotFound => html_err(404, "page not found"),
}
Expand Down
10 changes: 8 additions & 2 deletions packages/perseus/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::errors::*;
use crate::html_shell::{interpolate_page_data, prep_html_shell};
use crate::html_shell::{
interpolate_locale_redirection_fallback, interpolate_page_data, prep_html_shell,
};
use crate::locales::Locales;
use crate::serve::get_render_cfg;
use crate::serve::PageData;
Expand Down Expand Up @@ -87,12 +89,16 @@ pub async fn export_app(
};
// Create a locale detection file for it if we're using i18n
// These just send the app shell, which will perform a redirect as necessary
// Notably, these also include fallback redirectors if either Wasm or JS is disabled (or both)
// TODO put everything inside its own folder for initial loads?
if locales.using_i18n {
immutable_store
.write(
&format!("exported/{}.html", &initial_load_path),
&html_shell,
&interpolate_locale_redirection_fallback(
&html_shell,
&format!("{}/{}", locales.default, &path),
),
)
.await?;
}
Expand Down
43 changes: 43 additions & 0 deletions packages/perseus/src/html_shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,46 @@ pub fn interpolate_page_data(html_shell: &str, page_data: &PageData, root_id: &s
.replace(&html_to_replace_double, &html_replacement)
.replace(&html_to_replace_single, &html_replacement)
}

/// Intepolates a fallback for locale redirection pages such that, even if JavaScript is disabled, the user will still be redirected to the default locale.
/// From there, Perseus' inbuilt progressive enhancement can occur, but without this a user directed to an unlocalized page with JS disabled would see a
/// blank screen, which is terrible UX. Note that this also includes a fallback for if JS is enable dbut Wasm is disabled.
pub fn interpolate_locale_redirection_fallback(html_shell: &str, redirect_url: &str) -> String {
// This will be used if JavaScript is completely disabled (it's then the site's responsibility to show a further message)
let dumb_redirector = format!(
r#"<noscript>
<meta http-equiv="refresh" content="0; url=/{}" />
</noscript>"#,
redirect_url
);
// This will be used if JS is enabled, but Wasm is disabled or not supported (it's then the site's responsibility to show a further message)
// Wasm support detector courtesy https://stackoverflow.com/a/47880734
let js_redirector = format!(
r#"<script>
function wasmSupported() {{
try {{
if (typeof WebAssembly === "object"
&& typeof WebAssembly.instantiate === "function") {{
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module) {{
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}}
}}
}} catch (e) {{}}
return false;
}}
if (!wasmSupported()) {{
window.location.replace("{}");
}}
</script>"#,
redirect_url
);

let html = html_shell.replace(
"</head>",
&format!("{}\n{}\n</head>", js_redirector, dumb_redirector),
);

html
}

0 comments on commit 589ac1b

Please sign in to comment.