Update rocket_i18n and add gettext_macros (#431)

Internationalization now uses proc-macros that generate the .pot file
automatically.
This commit is contained in:
Baptiste Gelez 2019-02-02 15:23:50 +01:00 committed by GitHub
parent 8696185d1e
commit 7eef4643c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 6306 additions and 5484 deletions

View File

@ -1,10 +0,0 @@
[default]
name=Default
runtime=host
config-opts=
run-opts=
prefix=/home/elza/.cache/gnome-builder/install/plume/host
app-id=
postbuild=
prebuild=
default=true

2
.gitignore vendored
View File

@ -4,6 +4,7 @@ rls
rls
translations
po/*.po~
po/plume/*.po~
.env
Rocket.toml
!.gitkeep
@ -19,3 +20,4 @@ search_index
main.css
*.wasm
*.js
.buildconfig

31
Cargo.lock generated
View File

@ -965,6 +965,20 @@ dependencies = [
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gettext-macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gettext 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gettext-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "glob"
version = "0.2.11"
@ -1816,6 +1830,9 @@ dependencies = [
"diesel 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"guid-create 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"multipart 0.15.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1826,7 +1843,7 @@ dependencies = [
"rocket 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_contrib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_csrf 0.1.0 (git+https://github.com/fdb-hiroshima/rocket_csrf?rev=4a72ea2ec716cb0b26188fb00bccf2ef7d1e031c)",
"rocket_i18n 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_i18n 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rsass 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ructe 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1916,7 +1933,7 @@ dependencies = [
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
"tantivy 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tantivy 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"webfinger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"whatlang 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2320,7 +2337,7 @@ dependencies = [
[[package]]
name = "rocket_i18n"
version = "0.3.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gettext 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2754,7 +2771,7 @@ dependencies = [
[[package]]
name = "tantivy"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atomicwrites 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3437,6 +3454,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
"checksum gettext 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4378b8e09fd51cfdb0d48f40929a5c358efeeb62feb458c7d6eab979fae231f4"
"checksum gettext-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e977a8090ecd681d1c54f49ced1fa7cea8edca94e16e597642845c66e4b48aa8"
"checksum gettext-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46dd079379f756f6a1ae74b051813e242893f84fbf6ac898bce827fc77958d70"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum guid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e691c64d9b226c7597e29aeb46be753beb8c9eeef96d8c78dfd4d306338a38da"
"checksum guid-create 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcea207bf7a6092166ab590f98fe5dde5a7deed1f1920d98dcac31f80814c40d"
@ -3569,7 +3588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum rocket_contrib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f73e161dad5730435f51c815a5c6831d2e57b6b4299b1bf609d31b09aa9a2fa7"
"checksum rocket_csrf 0.1.0 (git+https://github.com/fdb-hiroshima/rocket_csrf?rev=4a72ea2ec716cb0b26188fb00bccf2ef7d1e031c)" = "<none>"
"checksum rocket_http 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba9d4f2ce5bba6e1b6d3100493bbad63879e99bbf6b4365d61e6f781daab324d"
"checksum rocket_i18n 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f10dc7394c8c400d20a86d25b8d6f6f8066cadd5e849ceed611bc6c28e1aaac5"
"checksum rocket_i18n 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc76ce146d0650cd38ee343202c95c8a891311f1ddad54feb9ecf8709cc2c86b"
"checksum rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37473170aedbe66ffa3ad3726939ba677d83c646ad4fd99e5b4bc38712f45ec"
"checksum rsass 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7a5dde55023a6c19470f7aeb59f75f897d8b80cbe00d61dfcaf7bbbe3de4c0a6"
"checksum ructe 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f9eb3b594c23d84efd966b7ce800d11eabc2d672f2d555b1e3acde43120ec6"
@ -3619,7 +3638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
"checksum tantivy 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8c326ada20e6ca90d39c3d10a51a740e4069d530b6d393b3af62d7f5f685ee11"
"checksum tantivy 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "252c9134bcd3a045c770ecc3a896a73e98fd8ed124b4380b837451f17b04d96b"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2"
"checksum tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b"

View File

@ -11,12 +11,15 @@ canapi = "0.2"
colored = "1.7"
dotenv = "0.13"
failure = "0.1"
gettext = "0.3"
gettext-macros = "0.3"
gettext-utils = "0.1"
guid-create = "0.1"
heck = "0.3.0"
num_cpus = "1.0"
rocket = "0.4.0"
rocket_contrib = { version = "0.4.0", features = ["json"] }
rocket_i18n = "0.3.1"
rocket_i18n = "0.4.0"
rpassword = "2.0"
scheduled-thread-pool = "0.2.0"
serde = "1.0"
@ -63,7 +66,6 @@ rev = "4a72ea2ec716cb0b26188fb00bccf2ef7d1e031c"
[build-dependencies]
ructe = "0.5.6"
rocket_i18n = { version = "0.3.1", features = ["build"] }
rsass = "0.9"
[features]

View File

@ -1,5 +1,4 @@
extern crate ructe;
extern crate rocket_i18n;
extern crate rsass;
use ructe::*;
use std::{env, fs::*, io::Write, path::PathBuf};
@ -10,10 +9,6 @@ fn main() {
.join("templates");
compile_templates(&in_dir, &out_dir).expect("compile templates");
println!("cargo:rerun-if-changed=po");
rocket_i18n::update_po("plume", &["de", "en", "fr", "gl", "it", "ja", "nb", "pl", "ru"]);
rocket_i18n::compile_po("plume", &["de", "en", "fr", "gl", "it", "ja", "nb", "pl", "ru"]);
println!("cargo:rerun-if-changed=static/css");
let mut out = File::create("static/css/main.css").expect("Couldn't create main.css");
out.write_all(

View File

@ -78,17 +78,6 @@ impl Notification {
.map_err(Error::from)
}
pub fn get_message(&self) -> &'static str {
match self.kind.as_ref() {
notification_kind::COMMENT => "{0} commented your article.",
notification_kind::FOLLOW => "{0} is now following you.",
notification_kind::LIKE => "{0} liked your article.",
notification_kind::MENTION => "{0} mentioned you.",
notification_kind::RESHARE => "{0} boosted your article.",
_ => unreachable!("Notification::get_message: Unknow type"),
}
}
pub fn get_url(&self, conn: &Connection) -> Option<String> {
match self.kind.as_ref() {
notification_kind::COMMENT => self.get_post(conn).and_then(|p| Some(format!("{}#comment-{}", p.url(conn).ok()?, self.object_id))),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,9 @@ extern crate ctrlc;
extern crate diesel;
extern crate dotenv;
extern crate failure;
#[macro_use]
extern crate gettext_macros;
extern crate gettext_utils;
extern crate guid_create;
extern crate heck;
extern crate multipart;
@ -21,7 +24,6 @@ extern crate plume_models;
extern crate rocket;
extern crate rocket_contrib;
extern crate rocket_csrf;
#[macro_use]
extern crate rocket_i18n;
extern crate scheduled_thread_pool;
extern crate serde;
@ -52,12 +54,18 @@ use std::process::exit;
use std::sync::Arc;
use std::time::Duration;
init_i18n!("plume", de, en, fr, gl, it, ja, nb, pl, ru);
mod api;
mod inbox;
#[macro_use]
mod template_utils;
mod routes;
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
compile_i18n!();
type Worker<'a> = State<'a, ScheduledThreadPool>;
type Searcher<'a> = State<'a, Arc<UnmanagedSearcher>>;
@ -216,7 +224,7 @@ Then try to restart Plume.
.manage(dbpool)
.manage(workpool)
.manage(searcher)
.manage(include_i18n!("plume", [ "de", "en", "fr", "gl", "it", "ja", "nb", "pl", "ru" ]))
.manage(include_i18n!())
.attach(CsrfFairingBuilder::new()
.set_default_target("/csrf-violation?target=<uri>".to_owned(), rocket::http::Method::Post)
.add_exceptions(vec![
@ -229,5 +237,3 @@ Then try to restart Plume.
.finalize().expect("main: csrf fairing creation error"))
.launch();
}
include!(concat!(env!("OUT_DIR"), "/templates.rs"));

View File

@ -62,7 +62,7 @@ pub fn new(user: User, conn: DbConn, intl: I18n) -> Ructe {
#[get("/blogs/new", rank = 2)]
pub fn new_auth(i18n: I18n) -> Flash<Redirect>{
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to create a new blog"),
&i18n!(i18n.catalog, "You need to be logged in order to create a new blog"),
uri!(new)
)
}
@ -133,7 +133,7 @@ pub fn delete(conn: DbConn, name: String, user: Option<User>, intl: I18n, search
// TODO actually return 403 error code
Err(render!(errors::not_authorized(
&(&*conn, &intl.catalog, user),
"You are not allowed to delete this blog."
i18n!(intl.catalog, "You are not allowed to delete this blog.")
)))
}
}

View File

@ -38,7 +38,7 @@ pub fn create(blog: String, slug: String, user: User, conn: DbConn, worker: Work
#[post("/~/<blog>/<slug>/like", rank = 2)]
pub fn create_auth(blog: String, slug: String, i18n: I18n) -> Flash<Redirect>{
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to like a post"),
&i18n!(i18n.catalog, "You need to be logged in order to like a post"),
uri!(create: blog = blog, slug = slug)
)
}

View File

@ -20,7 +20,7 @@ pub fn notifications(conn: DbConn, user: User, page: Option<Page>, intl: I18n) -
#[get("/notifications?<page>", rank = 2)]
pub fn notifications_auth(i18n: I18n, page: Option<Page>) -> Flash<Redirect>{
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to see your notifications"),
&i18n!(i18n.catalog, "You need to be logged in order to see your notifications"),
uri!(notifications: page = page)
)
}

View File

@ -78,7 +78,7 @@ pub fn details(blog: String, slug: String, conn: DbConn, user: Option<User>, res
} else {
Ok(render!(errors::not_authorized(
&(&*conn, &intl.catalog, user.clone()),
"This post isn't published yet."
i18n!(intl.catalog, "This post isn't published yet.")
)))
}
}
@ -97,7 +97,7 @@ pub fn activity_details(blog: String, slug: String, conn: DbConn, _ap: ApRequest
#[get("/~/<blog>/new", rank = 2)]
pub fn new_auth(blog: String, i18n: I18n) -> Flash<Redirect> {
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to write a new post"),
&i18n!(i18n.catalog, "You need to be logged in order to write a new post"),
uri!(new: blog = blog)
)
}
@ -110,12 +110,13 @@ pub fn new(blog: String, user: User, cl: ContentLen, conn: DbConn, intl: I18n) -
// TODO actually return 403 error code
Ok(render!(errors::not_authorized(
&(&*conn, &intl.catalog, Some(user)),
"You are not author in this blog."
i18n!(intl.catalog, "You are not author in this blog.")
)))
} else {
let medias = Media::for_user(&*conn, user.id)?;
Ok(render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
i18n!(intl.catalog, "New post"),
b,
false,
&NewPostForm {
@ -139,7 +140,7 @@ pub fn edit(blog: String, slug: String, user: User, cl: ContentLen, conn: DbConn
if !user.is_author_in(&*conn, &b)? {
Ok(render!(errors::not_authorized(
&(&*conn, &intl.catalog, Some(user)),
"You are not author in this blog."
i18n!(intl.catalog, "You are not author in this blog.")
)))
} else {
let source = if !post.source.is_empty() {
@ -149,8 +150,10 @@ pub fn edit(blog: String, slug: String, user: User, cl: ContentLen, conn: DbConn
};
let medias = Media::for_user(&*conn, user.id)?;
let title = post.title.clone();
Ok(render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
i18n!(intl.catalog, "Edit {0}"; &title),
b,
true,
&NewPostForm {
@ -257,6 +260,7 @@ pub fn update(blog: String, slug: String, user: User, cl: ContentLen, form: Leni
let medias = Media::for_user(&*conn, user.id).expect("posts:update: medias error");
Err(render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
i18n!(intl.catalog, "Edit {0}"; &form.title),
b,
true,
&*form,
@ -381,6 +385,7 @@ pub fn create(blog_name: String, form: LenientForm<NewPostForm>, user: User, cl:
let medias = Media::for_user(&*conn, user.id).expect("posts::create: medias error");
Err(Ok(render!(posts::new(
&(&*conn, &intl.catalog, Some(user)),
i18n!(intl.catalog, "New post"),
blog,
false,
&*form,

View File

@ -38,7 +38,7 @@ pub fn create(blog: String, slug: String, user: User, conn: DbConn, worker: Work
#[post("/~/<blog>/<slug>/reshare", rank=1)]
pub fn create_auth(blog: String, slug: String, i18n: I18n) -> Flash<Redirect> {
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to reshare a post"),
&i18n!(i18n.catalog, "You need to be logged in order to reshare a post"),
uri!(create: blog = blog, slug = slug)
)
}

View File

@ -128,7 +128,7 @@ pub fn dashboard(user: User, conn: DbConn, intl: I18n) -> Result<Ructe, ErrorPag
#[get("/dashboard", rank = 2)]
pub fn dashboard_auth(i18n: I18n) -> Flash<Redirect> {
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to access your dashboard"),
&i18n!(i18n.catalog, "You need to be logged in order to access your dashboard"),
uri!(dashboard),
)
}
@ -161,7 +161,7 @@ pub fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Result<
#[post("/@/<name>/follow", rank = 2)]
pub fn follow_auth(name: String, i18n: I18n) -> Flash<Redirect> {
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to follow someone"),
&i18n!(i18n.catalog, "You need to be logged in order to follow someone"),
uri!(follow: name = name),
)
}
@ -224,7 +224,7 @@ pub fn edit(name: String, user: User, conn: DbConn, intl: I18n) -> Result<Ructe,
#[get("/@/<name>/edit", rank = 2)]
pub fn edit_auth(name: String, i18n: I18n) -> Flash<Redirect> {
utils::requires_login(
i18n!(i18n.catalog, "You need to be logged in order to edit your profile"),
&i18n!(i18n.catalog, "You need to be logged in order to edit your profile"),
uri!(edit: name = name),
)
}

View File

@ -1,4 +1,4 @@
use plume_models::{Connection, users::User};
use plume_models::{Connection, notifications::*, users::User};
use rocket::response::Content;
use rocket_i18n::Catalog;
use templates::Html;
@ -28,6 +28,18 @@ macro_rules! render {
}
}
pub fn translate_notification(ctx: BaseContext, notif: Notification) -> String {
let name = notif.get_actor(ctx.0).unwrap().name(ctx.0);
match notif.kind.as_ref() {
notification_kind::COMMENT => i18n!(ctx.1, "{0} commented your article."; &name),
notification_kind::FOLLOW => i18n!(ctx.1, "{0} is now following you."; &name),
notification_kind::LIKE => i18n!(ctx.1, "{0} liked your article."; &name),
notification_kind::MENTION => i18n!(ctx.1, "{0} mentioned you."; &name),
notification_kind::RESHARE => i18n!(ctx.1, "{0} boosted your article."; &name),
_ => unreachable!("translate_notification: Unknow type"),
}
}
pub enum Size {
Small,
Medium,
@ -57,7 +69,7 @@ pub fn avatar(conn: &Connection, user: &User, size: Size, pad: bool, catalog: &C
))
}
pub fn tabs(links: &[(&str, &str, bool)]) -> Html<String> {
pub fn tabs(links: &[(&str, String, bool)]) -> Html<String> {
let mut res = String::from(r#"<div class="tabs">"#);
for (url, title, selected) in links {
res.push_str(r#"<a href=""#);
@ -117,6 +129,7 @@ macro_rules! input {
{
use validator::ValidationErrorsKind;
use std::borrow::Cow;
let cat = $catalog;
Html(format!(r#"
<label for="{name}">
@ -128,16 +141,16 @@ macro_rules! input {
<input type="{kind}" id="{name}" name="{name}" value="{val}" {props}/>
"#,
name = stringify!($name),
label = i18n!($catalog, $label),
label = i18n!(cat, $label),
kind = stringify!($kind),
optional = if $optional { format!("<small>{}</small>", i18n!($catalog, "Optional")) } else { String::new() },
optional = if $optional { format!("<small>{}</small>", i18n!(cat, "Optional")) } else { String::new() },
details = if $details.len() > 0 {
format!("<small>{}</small>", i18n!($catalog, $details))
format!("<small>{}</small>", i18n!(cat, $details))
} else {
String::new()
},
error = if let Some(ValidationErrorsKind::Field(errs)) = $err.errors().get(stringify!($name)) {
format!(r#"<p class="error">{}</p>"#, i18n!($catalog, &*errs[0].message.clone().unwrap_or(Cow::from("Unknown error"))))
format!(r#"<p class="error">{}</p>"#, errs[0].message.clone().unwrap_or(Cow::from("Unknown error")))
} else {
String::new()
},
@ -163,12 +176,13 @@ macro_rules! input {
};
($catalog:expr, $name:tt ($kind:tt), $label:expr, $props:expr) => {
{
let cat = $catalog;
Html(format!(r#"
<label for="{name}">{label}</label>
<input type="{kind}" id="{name}" name="{name}" {props}/>
"#,
name = stringify!($name),
label = i18n!($catalog, $label),
label = i18n!(cat, $label),
kind = stringify!($kind),
props = $props
))

View File

@ -1,12 +1,12 @@
@use template_utils::*;
@use routes::*;
@(ctx: BaseContext, title: &str, head: Content, header: Content, content: Content)
@(ctx: BaseContext, title: String, head: Content, header: Content, content: Content)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@i18n!(ctx.1, title) ⋅ @i18n!(ctx.1, "Plume")</title>
<title>@title ⋅ @i18n!(ctx.1, "Plume")</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="@uri!(static_files: file = "css/main.css")" />
<link rel="stylesheet" href="@uri!(static_files: file = "css/feather.css")" />

View File

@ -7,7 +7,7 @@
@(ctx: BaseContext, blog: Blog, fqn: String, authors: &Vec<User>, total_articles: i64, page: i32, n_pages: i32, is_author: bool, posts: Vec<Post>)
@:base(ctx, blog.title.as_ref(), {}, {
@:base(ctx, blog.title.clone(), {}, {
<a href="@uri!(blogs::details: name = &fqn, page = _)">@blog.title</a>
}, {
<div class="hidden">
@ -22,13 +22,13 @@
<h1><span class="p-name">@blog.title</span> <small>~@fqn</small></h1>
<p>@blog.summary</p>
<p>
@i18n!(ctx.1, "There's one author on this blog: ", "There are {0} authors on this blog: ", authors.len())
@i18n!(ctx.1, "There's one author on this blog: ", "There are {0} authors on this blog: "; authors.len())
@for author in authors {
<a class="author p-author" href="@uri!(user::details: name = author.get_fqn(ctx.0))">@author.name(ctx.0)</a>
}
</p>
<p>
@i18n!(ctx.1, "There's one article on this blog", "There are {0} articles on this blog", total_articles)
@i18n!(ctx.1, "There's one article on this blog", "There are {0} articles on this blog"; total_articles)
</p>
<section>

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, form: &NewBlogForm, errors: ValidationErrors)
@:base(ctx, "New Blog", {}, {}, {
@:base(ctx, i18n!(ctx.1, "New Blog"), {}, {}, {
<h1>@i18n!(ctx.1, "Create a blog")</h1>
<form method="post" action="@uri!(blogs::create)">
@input!(ctx.1, title (text), "Title", form, errors, "required minlength=\"1\"")

View File

@ -1,9 +1,9 @@
@use templates::base as base_template;
@use template_utils::*;
@(ctx: BaseContext, error_message: &str, error: Content)
@(ctx: BaseContext, error_message: String, error: Content)
@:base_template(ctx, error_message, {}, {}, {
@:base_template(ctx, error_message.clone(), {}, {}, {
@:error()
<p>@error_message</p>
})

View File

@ -3,10 +3,11 @@
@(ctx: BaseContext)
@:base(ctx, "", {
<h1>@i18n!(ctx.1, "Invalid CSRF token.")</h1>
<p>@i18n!(ctx.1, r#"Something is wrong with your CSRF token.
Make sure cookies are enabled in you browser, and try reloading this page.
If you continue to see this error message, please report it."#)
</p>
@:base(ctx, i18n!(ctx.1, "Invalid CSRF token"), {
<h1>@i18n!(ctx.1, "Invalid CSRF token")</h1>
<p>
@i18n!(ctx.1,
"Something is wrong with your CSRF token. Make sure cookies are enabled in you browser, and try reloading this page. If you continue to see this error message, please report it."
)
</p>
})

View File

@ -1,7 +1,7 @@
@use templates::errors::base;
@use template_utils::*;
@(ctx: BaseContext, error_message: &str)
@(ctx: BaseContext, error_message: String)
@:base(ctx, error_message, {
<h1>@i18n!(ctx.1, "You are not authorized.")</h1>

View File

@ -3,7 +3,7 @@
@(ctx: BaseContext)
@:base(ctx, "Page not found", {
@:base(ctx, i18n!(ctx.1, "Page not found"), {
<h1>@i18n!(ctx.1, "We couldn't find this page.")</h1>
<p>@i18n!(ctx.1, "The link that led you here may be broken.")</p>
})

View File

@ -3,7 +3,7 @@
@(ctx: BaseContext)
@:base(ctx, "Internal server error", {
@:base(ctx, i18n!(ctx.1, "Internal server error"), {
<h1>@i18n!(ctx.1, "Something broke on our side.")</h1>
<p>@i18n!(ctx.1, "Sorry about that. If you think this is a bug, please report it.")</p>
})

View File

@ -3,7 +3,7 @@
@(ctx: BaseContext)
@:base(ctx, "Unprocessable entity", {
@:base(ctx, "Unprocessable entity".to_string(), {
<h1>@i18n!(ctx.1, "The content you sent can't be processed.")</h1>
<p>@i18n!(ctx.1, "Maybe it was too long.")</p>
})

View File

@ -5,7 +5,7 @@
@(ctx: BaseContext, instance: Instance, admin: User, n_users: i64, n_articles: i64, n_instances: i64)
@:base(ctx, i18n!(ctx.1, "About {0}"; instance.name.clone()).as_str(), {}, {}, {
@:base(ctx, i18n!(ctx.1, "About {0}"; instance.name.clone()), {}, {}, {
<h1>@i18n!(ctx.1, "About {0}"; instance.name)</h1>
<section>
@Html(instance.short_description_html)

View File

@ -7,7 +7,7 @@
@(ctx: BaseContext, instance: Instance, form: InstanceSettingsForm, errors: ValidationErrors)
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()).as_str(), {}, {}, {
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()), {}, {}, {
<h1>@i18n!(ctx.1, "Administration")</h1>
@tabs(&[

View File

@ -5,7 +5,7 @@
@(ctx: BaseContext, articles: Vec<Post>, page: i32, n_pages: i32)
@:base(ctx, "All the articles of the Fediverse", {}, {}, {
@:base(ctx, i18n!(ctx.1, "All the articles of the Fediverse"), {}, {}, {
<div class="h-feed">
<h1 "p-name">@i18n!(ctx.1, "All the articles of the Fediverse")</h1>

View File

@ -5,7 +5,7 @@
@(ctx: BaseContext, articles: Vec<Post>, page: i32, n_pages: i32)
@:base(ctx, "Your feed", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Your feed"), {}, {}, {
<h1>@i18n!(ctx.1, "Your feed")</h1>
@tabs(&[

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, instance: Instance, n_users: i64, n_articles: i64, local: Vec<Post>, federated: Vec<Post>, user_feed: Option<Vec<Post>>)
@:base(ctx, instance.name.clone().as_ref(), {}, {}, {
@:base(ctx, instance.name.clone(), {}, {}, {
<h1>@i18n!(ctx.1, "Welcome on {}"; instance.name.as_str())</h1>
@if ctx.2.is_some() {
@ -17,9 +17,9 @@
(&uri!(instance::local: _).to_string(), i18n!(ctx.1, "Local feed"), false),
])
@:home_feed(ctx, user_feed.unwrap_or_default(), &uri!(instance::feed: _).to_string(), "Your feed")
@:home_feed(ctx, federated, &uri!(instance::federated: _).to_string(), "Federated feed")
@:home_feed(ctx, local, &uri!(instance::local: _).to_string(), "Local feed")
@:home_feed(ctx, user_feed.unwrap_or_default(), &uri!(instance::feed: _).to_string(), i18n!(ctx.1, "Your feed"))
@:home_feed(ctx, federated, &uri!(instance::federated: _).to_string(), i18n!(ctx.1, "Federated feed"))
@:home_feed(ctx, local, &uri!(instance::local: _).to_string(), i18n!(ctx.1, "Local feed"))
@:instance_description(ctx, instance, n_users, n_articles)
} else {
@tabs(&[
@ -28,8 +28,8 @@
(&uri!(instance::local: _).to_string(), i18n!(ctx.1, "Local feed"), false),
])
@:home_feed(ctx, federated, &uri!(instance::federated: _).to_string(), "Federated feed")
@:home_feed(ctx, local, &uri!(instance::local: _).to_string(), "Local feed")
@:home_feed(ctx, federated, &uri!(instance::federated: _).to_string(), i18n!(ctx.1, "Federated feed"))
@:home_feed(ctx, local, &uri!(instance::local: _).to_string(), i18n!(ctx.1, "Local feed"))
@:instance_description(ctx, instance, n_users, n_articles)
}
})

View File

@ -5,7 +5,7 @@
@(ctx: BaseContext, instance: Instance, instances: Vec<Instance>, page: i32, n_pages: i32)
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()).as_str(), {}, {}, {
@:base(ctx, i18n!(ctx.1, "Administration of {0}"; instance.name.clone()), {}, {}, {
<h1>@i18n!(ctx.1, "Instances")</h1>
@tabs(&[
@ -23,7 +23,7 @@
</p>
@if !instance.local {
<form class="inline" method="post" action="@uri!(instance::toggle_block: id = instance.id)">
<input type="submit" value="@i18n!(ctx.1, if instance.blocked { "Unblock" } else { "Block"})">
<input type="submit" value="@if instance.blocked { @i18n!(ctx.1, "Unblock") } else { @i18n!(ctx.1, "Block") }">
</form>
}
</div>

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, instance: Instance, articles: Vec<Post>, page: i32, n_pages: i32)
@:base(ctx, i18n!(ctx.1, "Articles from {}"; instance.name.clone()).as_str(), {}, {}, {
@:base(ctx, i18n!(ctx.1, "Articles from {}"; instance.name.clone()), {}, {}, {
<div class="h-feed">
<h1 class="p-name">@i18n!(ctx.1, "Articles from {}"; instance.name)</h1>

View File

@ -5,7 +5,7 @@
@(ctx: BaseContext, users: Vec<User>, page: i32, n_pages: i32)
@:base(ctx, "Users", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Users"), {}, {}, {
<h1>@i18n!(ctx.1, "Users")</h1>
@tabs(&[

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, media: Media)
@:base(ctx, "Media details", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Media details"), {}, {}, {
<h1>@i18n!(ctx.1, "Media details")</h1>
<section>
<a href="@uri!(medias::list)">@i18n!(ctx.1, "Go back to the gallery")</a>

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, medias: Vec<Media>)
@:base(ctx, "Your media", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Your media"), {}, {}, {
<h1>@i18n!(ctx.1, "Your media")</h1>
<div>
<a href="@uri!(medias::new)" class="inline-block button">@i18n!(ctx.1, "Upload")</a>

View File

@ -4,7 +4,7 @@
@(ctx: BaseContext)
@:base(ctx, "Media upload", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Media upload"), {}, {}, {
<h1>@i18n!(ctx.1, "Media upload")</h1>
<form method="post" enctype="multipart/form-data" action="@uri!(medias::upload)">
<label for="alt">

View File

@ -4,7 +4,7 @@
@(ctx: BaseContext, notifications: Vec<Notification>, page: i32, n_pages: i32)
@:base(ctx, "Notifications", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Notifications"), {}, {}, {
<h1>@i18n!(ctx.1, "Notifications")</h1>
<div class="list">
@ -15,10 +15,10 @@
<h3>
@if let Some(url) = notification.get_url(ctx.0) {
<a href="@url">
@i18n!(ctx.1, notification.get_message(); notification.get_actor(ctx.0).unwrap().name(ctx.0))
@translate_notification(ctx, notification.clone())
</a>
} else {
@i18n!(ctx.1, notification.get_message(); notification.get_actor(ctx.0).unwrap().name(ctx.0))
@translate_notification(ctx, notification.clone())
}
</h3>
@if let Some(post) = notification.get_post(ctx.0) {

View File

@ -2,11 +2,11 @@
@use plume_models::posts::Post;
@use template_utils::*;
@(ctx: BaseContext, articles: Vec<Post>, link: &str, title: &str)
@(ctx: BaseContext, articles: Vec<Post>, link: &str, title: String)
@if articles.len() > 0 {
<div class="h-feed">
<h2><span class="p-name">@i18n!(ctx.1, title)</span> &mdash; <a href="@link">@i18n!(ctx.1, "View all")</a></h2>
<h2><span class="p-name">@title</span> &mdash; <a href="@link">@i18n!(ctx.1, "View all")</a></h2>
<div class="cards spaced">
@for article in articles {
@:post_card(ctx, article)

View File

@ -11,7 +11,7 @@
@(ctx: BaseContext, article: Post, blog: Blog, comment_form: &NewCommentForm, comment_errors: ValidationErrors, tags: Vec<Tag>, comments: Vec<CommentTree>, previous_comment: Option<Comment>, n_likes: i64, n_reshares: i64, has_liked: bool, has_reshared: bool, is_following: bool, author: User)
@:base(ctx, &article.title.clone(), {
@:base(ctx, article.title.clone(), {
<meta property="og:title" content="@article.title"/>
<meta property="og:type" content="article"/>
@if article.cover_id.is_some() {
@ -87,7 +87,7 @@
@if ctx.2.is_some() {
<div class="actions">
<form class="likes" action="@uri!(likes::create: blog = blog.get_fqn(ctx.0), slug = &article.slug)" method="POST">
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes", &n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes", n_likes)">
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)">
@n_likes
</p>
@ -98,7 +98,7 @@
}
</form>
<form class="reshares" action="@uri!(reshares::create: blog = blog.get_fqn(ctx.0), slug = &article.slug)" method="POST">
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost", &n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts", n_reshares)">
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)">
@n_reshares
</p>
@ -113,14 +113,14 @@
<p class="center">@i18n!(ctx.1, "Login or use your Fediverse account to interact with this article")</p>
<div class="actions">
<div class="likes">
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes", &n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes", n_likes)">
<p aria-label="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)" title="@i18n!(ctx.1, "One like", "{0} likes"; n_likes)">
@n_likes
</p>
<a href="@uri!(session::new: m = i18n!(ctx.1, "Login to like"))" class="action">@icon!("heart") @i18n!(ctx.1, "Add yours")</a>
</div>
<div class="reshares">
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost", &n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts", n_reshares)">
<p aria-label="@i18n!(ctx.1, "One boost", "{0} boost"; n_reshares)" title="@i18n!(ctx.1, "One boost", "{0} boosts"; n_reshares)">
@n_reshares
</p>
<a href="@uri!(session::new: m = i18n!(ctx.1, "Login to boost"))" class="action">@icon!("repeat") @i18n!(ctx.1, "Boost")</a>

View File

@ -8,16 +8,10 @@
@use routes::posts::NewPostForm;
@use routes::*;
@(ctx: BaseContext, blog: Blog, editing: bool, form: &NewPostForm, is_draft: bool, article: Option<Post>, errors: ValidationErrors, medias: Vec<Media>, content_len: u64)
@(ctx: BaseContext, title: String, blog: Blog, editing: bool, form: &NewPostForm, is_draft: bool, article: Option<Post>, errors: ValidationErrors, medias: Vec<Media>, content_len: u64)
@:base(ctx, &i18n!(ctx.1, if editing { "Edit {0}" } else { "New post" }; &form.title), {}, {}, {
<h1>
@if editing {
@i18n!(ctx.1, "Edit {0}"; &form.title)
} else {
@i18n!(ctx.1, "Create a new post")
}
</h1>
@:base(ctx, title.clone(), {}, {}, {
<h1>@title</h1>
@if let Some(article) = article {
<form id="post-form" class="new-post" method="post" action="@uri!(posts::update: blog = blog.actor_id, slug = &article.slug)" content-size="@content_len">
} else {
@ -27,7 +21,7 @@
@input!(ctx.1, subtitle (optional text), "Subtitle", form, errors.clone(), "")
@if let Some(ValidationErrorsKind::Field(errs)) = errors.clone().errors().get("content") {
@format!(r#"<p class="error">{}</p>"#, i18n!(ctx.1, &*errs[0].message.clone().unwrap_or(Cow::from("Unknown error"))))
@format!(r#"<p class="error">{}</p>"#, errs[0].message.clone().unwrap_or(Cow::from("Unknown error")))
}
<label for="plume-editor">@i18n!(ctx.1, "Content")<small>@i18n!(ctx.1, "Markdown syntax is supported")</small></label>

View File

@ -3,14 +3,14 @@
@(ctx: BaseContext, now: &str)
@:base(ctx, "Search", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Search"), {}, {}, {
<h1>@i18n!(ctx.1, "Search")</h1>
<form method="get" id="form">
<input id="q" name="q" placeholder="@i18n!(ctx.1, "Your query")" type="search">
<details>
<summary>@i18n!(ctx.1, "Advanced search")</summary>
@input!(ctx.1, title (text), "Article title matching these words", &format!("placeholder=\"{}\"", i18n!(ctx.1, "Title")))
@input!(ctx.1, subtitle (text), "Subtitle matching these words", &format!("placeholder=\"{}\"", i18n!(ctx.1, "Subtitle byline")))
@input!(ctx.1, subtitle (text), "Subtitle matching these words", &format!("placeholder=\"{}\"", i18n!(ctx.1, "Subtitle - byline")))
@input!(ctx.1, content (text), "Content matching these words", &format!("placeholder=\"{}\"", i18n!(ctx.1, "Body content")))
@input!(ctx.1, after (date), "From this date", &format!("max={}", now))
@input!(ctx.1, before (date), "To this date", &format!("max={}", now))

View File

@ -4,7 +4,7 @@
@(ctx: BaseContext, query_str: &str, articles: Vec<Post>, page: i32, n_pages: i32)
@:base(ctx, i18n!(ctx.1, "Search result for \"{0}\""; query_str).as_str(), {}, {}, {
@:base(ctx, i18n!(ctx.1, "Search result for \"{0}\""; query_str), {}, {}, {
<h1>@i18n!(ctx.1, "Search result")</h1>
<p>@query_str</p>

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, message: Option<String>, form: &LoginForm, errors: ValidationErrors)
@:base(ctx, "Login", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Login"), {}, {}, {
<h1>@i18n!(ctx.1, "Login")</h1>
@if let Some(message) = message {
<p>@message</p>

View File

@ -4,7 +4,7 @@
@(ctx: BaseContext, tag: String, articles: Vec<Post>, page: i32, n_pages: i32)
@:base(ctx, i18n!(ctx.1, "Articles tagged \"{0}\""; &tag).as_str(), {}, {}, {
@:base(ctx, i18n!(ctx.1, "Articles tagged \"{0}\""; &tag), {}, {}, {
<h1>@i18n!(ctx.1, "Articles tagged \"{0}\""; &tag)</h1>
@if !articles.is_empty() {

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, blogs: Vec<Blog>, drafts: Vec<Post>)
@:base(ctx, "Your Dashboard", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Your Dashboard"), {}, {}, {
<h1>@i18n!(ctx.1, "Your Dashboard")</h1>
<section>

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, recents: Vec<Post>, reshares: Vec<Post>)
@:base(ctx, &user.name(ctx.0), {}, {}, {
@:base(ctx, user.name(ctx.0), {}, {}, {
@:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, form: UpdateUserForm, errors: ValidationErrors)
@:base(ctx, "Edit your account", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Edit your account"), {}, {}, {
@if let Some(u) = ctx.2.clone() {
<h1>@i18n!(ctx.1, "Your Profile")</h1>
<form method="post" action="@uri!(user::update: _name = u.username.clone())">

View File

@ -5,7 +5,7 @@
@(ctx: BaseContext, user: User, follows: bool, is_remote: bool, remote_url: String, followers: Vec<User>, page: i32, n_pages: i32)
@:base(ctx, &i18n!(ctx.1, "{0}'s followers"; user.name(ctx.0)), {}, {}, {
@:base(ctx, i18n!(ctx.1, "{0}'s followers"; user.name(ctx.0)), {}, {}, {
@:header(ctx, &user, follows, is_remote, remote_url)
@tabs(&[

View File

@ -6,7 +6,7 @@
@(ctx: BaseContext, enabled: bool, form: &NewUserForm, errors: ValidationErrors)
@:base(ctx, "Create your account", {}, {}, {
@:base(ctx, i18n!(ctx.1, "Create your account"), {}, {}, {
@if enabled {
<h1>@i18n!(ctx.1, "Create an account")</h1>
<form method="post" action="@uri!(user::create)">