Skip to content

Commit

Permalink
feat: ✨ added i18n
Browse files Browse the repository at this point in the history
Server-side translation caching not yet supported. Translation engine still primitive.

BREAKING CHANGE: all user-facing interfaces take new i18n parameters
  • Loading branch information
arctic-hen7 committed Sep 7, 2021
1 parent d1a6bb8 commit a4402c0
Show file tree
Hide file tree
Showing 45 changed files with 789 additions and 101 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ members = [
"packages/perseus",
"packages/perseus-actix-web",
"packages/perseus-cli",
"examples/showcase/app",
"examples/showcase/server-actix-web",
# TODO remake showcase app for CLI
# "examples/showcase/app",
# "examples/showcase/server-actix-web",
"examples/cli",
# We have the CLI subcrates as workspace members so we can actively develop on them
# They also can't be a workspace until nested workspaces are supported
"examples/cli/.perseus",
"examples/cli/.perseus/server",
"examples/basic"
"examples/basic",
"examples/i18n"
]
2 changes: 1 addition & 1 deletion examples/basic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
perseus = "0.1.1"
perseus = { path = "../../packages/perseus" }
sycamore = { version = "0.5.1", features = ["ssr"] }
sycamore-router = "0.5.1"
serde = { version = "1", features = ["derive"] }
Expand Down
4 changes: 3 additions & 1 deletion examples/cli/.perseus/server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use actix_web::{App, HttpServer};
use app::{get_config_manager, get_templates_map};
use app::{get_config_manager, get_locales, get_templates_map, get_translations_manager};
use futures::executor::block_on;
use perseus_actix_web::{configurer, Options};
use std::env;
Expand All @@ -23,8 +23,10 @@ async fn main() -> std::io::Result<()> {
// Our crate has the same name, so this will be predictable
wasm_bundle: "dist/pkg/perseus_cli_builder_bg.wasm".to_string(),
templates_map: get_templates_map(),
locales: get_locales(),
},
get_config_manager(),
get_translations_manager(),
)))
})
.bind((host, port))?
Expand Down
21 changes: 18 additions & 3 deletions examples/cli/.perseus/src/bin/build.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
use app::{get_config_manager, get_templates_vec};
use app::{get_config_manager, get_locales, get_templates_vec, get_translations_manager};
use futures::executor::block_on;
use perseus::{build_templates, SsrNode};
use perseus::{build_app, SsrNode};

fn main() {
let exit_code = real_main();
std::process::exit(exit_code)
}

fn real_main() -> i32 {
let config_manager = get_config_manager();
let translations_manager = get_translations_manager();
let locales = get_locales();

let fut = build_templates(get_templates_vec::<SsrNode>(), &config_manager);
// Build the site for all the common locales (done in parallel)
let fut = build_app(
get_templates_vec::<SsrNode>(),
&locales,
&config_manager,
&translations_manager,
);
let res = block_on(fut);
if let Err(err) = res {
eprintln!("Static generation failed: '{}'", err);
1
} else {
println!("Static generation successfully completed!");
0
}
}
Empty file removed examples/cli/.perseus/src/build.rs
Empty file.
20 changes: 15 additions & 5 deletions examples/cli/.perseus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use app::{get_error_pages, match_route, AppRoute, APP_ROUTE};
use perseus::app_shell;
use app::{get_error_pages, get_locales, match_route, AppRoute, APP_ROUTE};
use perseus::{app_shell, ClientTranslationsManager};
use std::cell::RefCell;
use std::rc::Rc;
use sycamore::prelude::template;
use sycamore_router::BrowserRouter;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
Expand All @@ -20,20 +22,28 @@ pub fn run() -> Result<(), JsValue> {
.unwrap()
.unwrap();

// Create a mutable translations manager to control caching
let translations_manager =
Rc::new(RefCell::new(ClientTranslationsManager::new(&get_locales())));

sycamore::render_to(
|| {
template! {
BrowserRouter(|route: AppRoute| {
// TODO improve performance rather than naively copying error pages for every template
BrowserRouter(move |route: AppRoute| {
// TODO improve performance rather than naively copying error pages for every template (use `Rc<ErrorPages>`)
match route {
// We handle the 404 for the user for convenience
AppRoute::NotFound => get_error_pages().get_template_for_page("", &404, "not found"),
// All other routes are based on the user's given statements
_ => {
let (name, render_fn) = match_route(route);
let (name, render_fn, locale) = match_route(route);

app_shell(
name,
render_fn,
locale,
// We give the app shell a translations manager and let it get the `Rc<Translator>` (because it can do async safely)
Rc::clone(&translations_manager),
get_error_pages()
)
}
Expand Down
Empty file removed examples/cli/.perseus/src/serve.rs
Empty file.
13 changes: 10 additions & 3 deletions examples/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,24 @@ define_app! {
router: {
Route::Index => [
"index".to_string(),
pages::index::template_fn()
pages::index::template_fn(),
"en-US".to_string()
],
Route::About => [
"about".to_string(),
pages::about::template_fn()
pages::about::template_fn(),
"en-US".to_string()
]
},
error_pages: crate::error_pages::get_error_pages(),
templates: [
crate::pages::index::get_page::<G>(),
crate::pages::about::get_page::<G>()
]
],
locales: {
default: "en-US",
common: [],
other: []
}
// config_manager: perseus::FsConfigManager::new()
}
2 changes: 1 addition & 1 deletion examples/cli/src/pages/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Arc::new(|_| {
Arc::new(|_, _| {
template! {
AboutPage()
}
Expand Down
2 changes: 1 addition & 1 deletion examples/cli/src/pages/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub async fn get_static_props(_path: String) -> StringResultWithCause<String> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Arc::new(|props: Option<String>| {
Arc::new(|props: Option<String>, _| {
template! {
IndexPage(
serde_json::from_str::<IndexPageProps>(&props.unwrap()).unwrap()
Expand Down
4 changes: 4 additions & 0 deletions examples/i18n/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
Cargo.lock

.perseus/
16 changes: 16 additions & 0 deletions examples/i18n/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "perseus-example-i18n"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# Perseus itself, which we (amazingly) need for a Perseus app
perseus = { path = "../../packages/perseus" }
# Sycamore, the library Perseus depends on for lower-leve reactivity primitivity
sycamore = { version = "0.5.1", features = ["ssr"] }
sycamore-router = "0.5.1"
# Serde, which lets you work with representations of data, like JSON
serde = { version = "1", features = ["derive"] }
serde_json = "1"
14 changes: 14 additions & 0 deletions examples/i18n/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Perseus Starter App</title>
<!-- Importing this runs Perseus -->
<script src="/.perseus/bundle.js" defer></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
28 changes: 28 additions & 0 deletions examples/i18n/src/error_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use perseus::ErrorPages;
use sycamore::template;

pub fn get_error_pages() -> ErrorPages {
let mut error_pages = ErrorPages::new(Box::new(|_, _, _| {
template! {
p { "Another error occurred." }
}
}));
error_pages.add_page(
404,
Box::new(|_, _, _| {
template! {
p { "Page not found." }
}
}),
);
error_pages.add_page(
400,
Box::new(|_, _, _| {
template! {
p { "Client error occurred..." }
}
}),
);

error_pages
}
41 changes: 41 additions & 0 deletions examples/i18n/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
mod error_pages;
mod templates;

use perseus::define_app;

#[derive(perseus::Route)]
pub enum Route {
#[to("/<locale>")]
Index { locale: String },
#[to("/<locale>/about")]
About { locale: String },
#[not_found]
NotFound,
}

define_app! {
root: "#root",
route: Route,
router: {
Route::Index { locale } => [
"index".to_string(),
templates::index::template_fn(),
locale
],
Route::About { locale } => [
"about".to_string(),
templates::about::template_fn(),
locale
]
},
error_pages: crate::error_pages::get_error_pages(),
templates: [
crate::templates::index::get_template::<G>(),
crate::templates::about::get_template::<G>()
],
locales: {
default: "en-US",
common: ["en-US", "fr-FR"],
other: ["es-ES"]
}
}
24 changes: 24 additions & 0 deletions examples/i18n/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use perseus::{t, Template, Translator};
use std::rc::Rc;
use std::sync::Arc;
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};

#[component(AboutPage<G>)]
pub fn about_page(translator: Rc<Translator>) -> SycamoreTemplate<G> {
template! {
// TODO switch to `t!` macro
p { (translator.translate("about")) }
}
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Arc::new(|_, translator: Rc<Translator>| {
template! {
AboutPage(translator)
}
})
}

pub fn get_template<G: GenericNode>() -> Template<G> {
Template::new("about").template(template_fn())
}
25 changes: 25 additions & 0 deletions examples/i18n/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use perseus::{t, Template, Translator};
use std::rc::Rc;
use std::sync::Arc;
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};

#[component(IndexPage<G>)]
pub fn index_page(translator: Rc<Translator>) -> SycamoreTemplate<G> {
template! {
// TODO switch to `t!` macro
p { (translator.translate("hello")) }
a(href = "/en-US/about") { "About" }
}
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Arc::new(|_, translator: Rc<Translator>| {
template! {
IndexPage(translator)
}
})
}

pub fn get_template<G: GenericNode>() -> Template<G> {
Template::new("index").template(template_fn())
}
2 changes: 2 additions & 0 deletions examples/i18n/src/templates/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod about;
pub mod index;
4 changes: 4 additions & 0 deletions examples/i18n/translations/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"hello": "Welcome to the app!",
"about": "Welcome to the about page (English)!"
}
4 changes: 4 additions & 0 deletions examples/i18n/translations/es-ES.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"hello": "Hola!",
"about": "Welcome to the about page (Spanish)!"
}
4 changes: 4 additions & 0 deletions examples/i18n/translations/fr-FR.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"hello": "Bonjour!",
"about": "Welcome to the about page (French)!"
}
5 changes: 3 additions & 2 deletions examples/showcase/app/src/bin/build.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use futures::executor::block_on;
use perseus::{build_templates, FsConfigManager, SsrNode};
use perseus::{build_templates_for_locale, FsConfigManager, SsrNode, Translator};
use perseus_showcase_app::pages;

fn main() {
let config_manager = FsConfigManager::new("./dist".to_string());

let fut = build_templates(
let fut = build_templates_for_locale(
vec![
pages::index::get_page::<SsrNode>(),
pages::about::get_page::<SsrNode>(),
Expand All @@ -16,6 +16,7 @@ fn main() {
pages::time_root::get_page::<SsrNode>(),
pages::amalgamation::get_page::<SsrNode>(),
],
Translator::empty(),
&config_manager,
);
block_on(fut).expect("Static generation failed!");
Expand Down
2 changes: 1 addition & 1 deletion examples/showcase/app/src/pages/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Arc::new(|_| {
Arc::new(|_, _| {
template! {
AboutPage()
}
Expand Down
2 changes: 1 addition & 1 deletion examples/showcase/app/src/pages/amalgamation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub async fn get_request_state(_path: String, _req: Request) -> StringResultWith
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Arc::new(|props| {
Arc::new(|props, _| {
template! {
AboutPage(
serde_json::from_str::<AmalagamationPageProps>(&props.unwrap()).unwrap()
Expand Down
2 changes: 1 addition & 1 deletion examples/showcase/app/src/pages/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub async fn get_static_props(_path: String) -> StringResultWithCause<String> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Arc::new(|props: Option<String>| {
Arc::new(|props, _| {
template! {
IndexPage(
serde_json::from_str::<IndexPageProps>(&props.unwrap()).unwrap()
Expand Down
Loading

0 comments on commit a4402c0

Please sign in to comment.