Add actix-web support #10

Merged
asonix merged 1 commits from plume/rocket_i18n:asonix/add-actix into master 6 years ago

@ -11,7 +11,15 @@ version = "0.2.0"
[dependencies]
gettext = "0.3"
rocket = "0.4.0-rc.1"
[dependencies.rocket]
version = "0.4.0-rc.1"
optional = true
[dependencies.actix-web]
version = "0.7"
optional = true
[features]
default = ["rocket"]
build = []

@ -85,3 +85,46 @@ msgid_plural "You have {{ count }} new notifications" # The plural one
msgstr[0] ""
msgstr[1] ""
```
### Using with Actix Web
First, disable the default features so it doesn't pull in all of Rocket.
```toml
[dependencies.rocket_i18n]
version = "0.2"
default-features = false
features = ["actix-web"]
```
Then add it to your application.
```rust
#[macro_use]
extern crate rocket_i18n;
use rocket_i18n::{I18n, Internationalized, Translations};
fn route_handler(i18n: I18n) -> &str {
i18n!(i18n.catalog, "Hello, world!")
}
#[derive(Clone)]
struct MyState {
translations: Translations,
}
impl Internationalized for MyState {
fn get(&self) -> Translations {
self.translations.clone()
}
}
fn main() {
let state = MyState {
translations: rocket_i18n::i18n(vec![ "en", "fr", "de", "ja" ]);
};
App::with_state(state)
.resource("", |r| r.with(route_handler))
.finish();
}
```

@ -87,17 +87,24 @@
//! ```
//!
#[cfg(feature = "actix-web")]
extern crate actix_web;
extern crate gettext;
#[cfg(feature = "rocket")]
extern crate rocket;
pub use gettext::*;
use rocket::{
http::Status,
request::{self, FromRequest},
Outcome, Request, State,
};
use std::fs;
#[cfg(feature = "rocket")]
mod with_rocket;
#[cfg(feature = "actix-web")]
mod with_actix;
#[cfg(feature = "actix-web")]
pub use with_actix::Internationalized;
const ACCEPT_LANG: &'static str = "Accept-Language";
/// A request guard to get the right translation catalog for the current request
@ -117,36 +124,6 @@ pub fn i18n(lang: Vec<&'static str>) -> Translations {
})
}
impl<'a, 'r> FromRequest<'a, 'r> for I18n{
type Error = ();
fn from_request(req: &'a Request) -> request::Outcome<I18n, ()> {
let langs = &*req
.guard::<State<Translations>>()
.expect("Couldn't retrieve translations because they are not managed by Rocket.");
let lang = req
.headers()
.get_one(ACCEPT_LANG)
.unwrap_or("en")
.split(",")
.filter_map(|lang| lang
// Get the locale, not the country code
.split(|c| c == '-' || c == ';')
.nth(0))
// Get the first requested locale we support
.find(|lang| langs.iter().any(|l| l.0 == &lang.to_string()))
.unwrap_or("en");
match langs.iter().find(|l| l.0 == lang) {
Some(catalog) => Outcome::Success(I18n {
catalog: catalog.1.clone(),
}),
None => Outcome::Failure((Status::InternalServerError, ())),
}
}
}
#[cfg(feature = "build")]
pub fn update_po(domain: &str) {
let pot_path = Path::new("po").join(format!("{}.pot", domain));
@ -161,7 +138,11 @@ pub fn update_po(domain: &str) {
.arg(po_path.to_str().unwrap())
.arg(pot_path.to_str().unwrap())
.status()
.map(|s| if !s.success() { panic!("Couldn't update PO file") })
.map(|s| {
if !s.success() {
panic!("Couldn't update PO file")
}
})
.expect("Couldn't update PO file");
} else {
println!("Creating {}", lang.clone());
@ -173,7 +154,11 @@ pub fn update_po(domain: &str) {
.arg(lang)
.arg("--no-translator")
.status()
.map(|s| if !s.success() { panic!("Couldn't init PO file") })
.map(|s| {
if !s.success() {
panic!("Couldn't init PO file")
}
})
.expect("Couldn't init PO file");
}
}
@ -194,7 +179,11 @@ fn compile_po() {
.arg(format!("--output-file={}", mo_path.to_str().unwrap()))
.arg(po_path)
.status()
.map(|s| if !s.success() { panic!("Couldn't compile translations") })
.map(|s| {
if !s.success() {
panic!("Couldn't compile translations")
}
})
.expect("Couldn't compile translations");
}
}
@ -240,7 +229,10 @@ pub enum FormatError {
}
#[doc(hidden)]
pub fn try_format<'a>(str_pattern: &'a str, argv: &[Box<dyn std::fmt::Display + 'a>]) -> Result<String, FormatError> {
pub fn try_format<'a>(
str_pattern: &'a str,
argv: &[Box<dyn std::fmt::Display + 'a>],
) -> Result<String, FormatError> {
use std::fmt::Write;
//first we parse the pattern
@ -265,7 +257,8 @@ pub fn try_format<'a>(str_pattern: &'a str, argv: &[Box<dyn std::fmt::Display +
.map_err(|_| FormatError::InvalidPositionalArgument)?
} else {
i
}).ok_or(FormatError::InvalidPositionalArgument)?,
})
.ok_or(FormatError::InvalidPositionalArgument)?,
);
} else {
finish_or_fail = true;

@ -0,0 +1,64 @@
use std::{error::Error, fmt};
use crate::{I18n, Translations, ACCEPT_LANG};
use actix_web::{FromRequest, HttpRequest, ResponseError};
#[derive(Debug)]
pub struct MissingTranslationsError(String);
impl fmt::Display for MissingTranslationsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Could not find translations for {}", self.0)
}
}
impl Error for MissingTranslationsError {
fn description(&self) -> &str {
"Could not find translations"
}
}
impl ResponseError for MissingTranslationsError {
// this defaults to an empty InternalServerError response
}
pub trait Internationalized {
fn get(&self) -> Translations;
}
impl<S> FromRequest<S> for I18n
where
S: Internationalized,
{
type Config = ();
type Result = Result<Self, actix_web::Error>;
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let state = req.state();
let langs = state.get();
let lang = req
.headers()
.get(ACCEPT_LANG)
.and_then(|v| v.to_str().ok())
.unwrap_or("en")
.split(",")
.filter_map(|lang| {
lang
// Get the locale, not the country code
.split(|c| c == '-' || c == ';')
.nth(0)
})
// Get the first requested locale we support
.find(|lang| langs.iter().any(|l| l.0 == &lang.to_string()))
.unwrap_or("en");
match langs.iter().find(|l| l.0 == lang) {
Some(catalog) => Ok(I18n {
catalog: catalog.1.clone(),
}),
None => Err(MissingTranslationsError(lang.to_owned()).into()),
}
}
}

@ -0,0 +1,37 @@
use crate::{ACCEPT_LANG, I18n, Translations};
use rocket::{
http::Status,
request::{self, FromRequest},
Outcome, Request, State,
};
impl<'a, 'r> FromRequest<'a, 'r> for I18n{
type Error = ();
fn from_request(req: &'a Request) -> request::Outcome<I18n, ()> {
let langs = &*req
.guard::<State<Translations>>()
.expect("Couldn't retrieve translations because they are not managed by Rocket.");
let lang = req
.headers()
.get_one(ACCEPT_LANG)
.unwrap_or("en")
.split(",")
.filter_map(|lang| lang
// Get the locale, not the country code
.split(|c| c == '-' || c == ';')
.nth(0))
// Get the first requested locale we support
.find(|lang| langs.iter().any(|l| l.0 == &lang.to_string()))
.unwrap_or("en");
match langs.iter().find(|l| l.0 == lang) {
Some(catalog) => Outcome::Success(I18n {
catalog: catalog.1.clone(),
}),
None => Outcome::Failure((Status::InternalServerError, ())),
}
}
}
Loading…
Cancel
Save