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

Fluent integration #211

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e081bfa
Starting work on fluent integration
kazimuth Feb 17, 2019
fa3c2d0
Fix compilation errors
kazimuth Feb 18, 2019
ef1eb6c
Basic parser test
kazimuth Feb 18, 2019
eb62a3e
Switch to looking in local i18n module, small doc changes
kazimuth Feb 18, 2019
c548914
Initial codegen for baking .ftl files into executable
kazimuth Feb 18, 2019
08c0331
Refactor parser to use separated_list macro where possible
kazimuth Feb 21, 2019
a53a11b
Add `localize(message.attribute, arg1: v1, argn: vn)` expression to
kazimuth Feb 21, 2019
463912d
Remove debug output from test
kazimuth Feb 21, 2019
e37b5f4
Add some compile-time checks on input .ftl files
kazimuth Feb 21, 2019
6212a9a
Whoops, use correct fluent syntax
kazimuth Feb 21, 2019
dd26927
Modify API, add `Localize` trait
kazimuth Feb 26, 2019
a5de4d1
Rewire codegen to use new syntax, switch to quote!
kazimuth Mar 5, 2019
93c9de3
Basic localization works!
kazimuth Mar 5, 2019
f696e68
Fix coverage reporting, remove debug prints
kazimuth Mar 5, 2019
138dd27
Test all askama_shared features on travis
kazimuth Mar 9, 2019
5080875
Fix i18n tests in askama_shared, add Accept-Language header support
kazimuth Mar 9, 2019
3b9a8e7
Simplify fallback chain management for now
kazimuth Mar 9, 2019
c699ea0
Doc-tests
kazimuth Mar 9, 2019
96fb8a0
Fix missed FALLBACK_CHAINS
kazimuth Mar 9, 2019
979b29a
Locales use underscores, lmao
kazimuth Mar 9, 2019
9ed5428
Fix up various leftover TODOs
kazimuth Mar 9, 2019
50aa47f
A few TODOs
kazimuth Mar 20, 2019
d09728e
More cleanup
kazimuth Mar 20, 2019
6708e73
Refactor to use locale chians instead of single locales
kazimuth Mar 20, 2019
ae082bf
Update codegen with locale chains
kazimuth Mar 20, 2019
24ac4b0
Add default-tests
kazimuth Mar 20, 2019
677a48f
More TODO cleanup
kazimuth Mar 20, 2019
5577e39
Merge remote-tracking branch 'upstream/master' into fluent
kazimuth Mar 20, 2019
b95ccbc
Small doc fixes
kazimuth Mar 20, 2019
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ before_script:

script:
- cargo test --all
- cd askama_shared && cargo test --features full && cd ..
- if [[ "${TRAVIS_RUST_VERSION}" == stable ]]; then
cd testing && cargo test --features full && cargo fmt -- --check;
fi
Expand Down
1 change: 1 addition & 0 deletions askama/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ with-iron = ["iron", "askama_derive/iron"]
with-rocket = ["rocket", "askama_derive/rocket"]
with-actix-web = ["actix-web", "askama_derive/actix-web", "mime", "mime_guess", "bytes"]
with-gotham = ["gotham", "askama_derive/gotham", "hyper", "mime", "mime_guess"]
with-i18n = ["askama_shared/with-i18n", "askama_derive/with-i18n"]

[dependencies]
askama_derive = { version = "0.8.0", path = "../askama_derive" }
Expand Down
1 change: 1 addition & 0 deletions askama/i18n/en_US/dummy.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
basic = "hello"
180 changes: 180 additions & 0 deletions askama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,133 @@
//! Enabling the `serde-json` filter will enable the use of the `json` filter.
//! This will output formatted JSON for any value that implements the required
//! `Serialize` trait.
//!
//! ## Internationalization with Fluent
//! Enabling the `with-i18n` feature enables askama's internationalization / localization (i18n / l10n)
//! functionality, which you can use to provide translations of your templates into other languages.
//!
//! Askama's i18n relies on [Fluent](https://projectfluent.org) to parse and apply translations.
//!
//! Using i18n tries to be as simple as possible, but it's still a little involved.
//!
//! Create a folder structure at your project root, organized like so:
//! - `i18n`
//! - `en_US`
//! - `greeting.ftl`
//! - `es_MX`
//! - `greeting.ftl`
//!
//! Add some localizations in [Fluent's `FTL` Syntax](https://projectfluent.org/fluent/guide/):
//!
//! `i18n/en_US/greeting.ftl`:
//! ```txt
//! hello = Hello, $name!
//! age.tracker = You are $age_hours hours old.
//! ```
//! `i18n/es_MX/greeting.ftl`:
//! ```txt
//! hello = ¡Hola, $name!
//! age.tracker = Tiene $age_hours horas.
//! ```
//!
//! Call the `impl_localize!()` macro at your crate root:
//! ```
//! # #[cfg(feature = "with-i18n")]
//! # mod lmao_rustc {
//! extern crate askama;
//! use askama::{Localize, impl_localize};
//!
//! impl_localize! {
//! #[localize(path = "i18n", default_locale = "en_US")]
//! pub struct AppLocalizer(_);
//! }
//! # }
//! ```
//!
//! This creates a struct called `AppLocalizer` which implements the askama `Localize` trait.
//!
//! This will bake translations you provide into the output executable, to ease
//! deployment; all you need is one binary.
//!
//! (Note: tests will be autogenerated to ensure that `default_locale` has full coverage of messages
//! used in the application. For this reason, you should probably use a language everyone on your dev team
//! speaks for the default locale.)
//!
//! Now, on templates you want to localize, add a member of type `AppLocalizer`
//! annotated with the `#[localizer]` attribute:
//! ```
//! # #[cfg(feature = "with-i18n")]
//! # mod lmao_rustc {
//! # extern crate askama;
//! # use askama::{Localize, impl_localize, Template};
//! # impl_localize! {
//! # #[localize(path = "i18n", default_locale = "en_US")]
//! # struct AppLocalizer(_);
//! # }
//! #[derive(Template)]
//! #[template(path = "hello.html")]
//! struct HelloTemplate<'a> {
//! #[localizer]
//! localizer: AppLocalizer,
//! name: &'a str,
//! age_hours: f32,
//! }
//! # }
//! ```
//!
//! And now you can use the `localize` filter in your templates:
//! ```html
//! <h1>{{ localize(hello, name: name) }}</h1>
//! <h3>{{ localize(age.tracker, age_hours: age_hours) }}</h1>
//! ```
//!
//! Now, your template will be automatically translated when rendered:
//! ```no_run
//! # #[cfg(feature = "with-i18n")]
//! # {
//! # mod lmao_rustc {
//! # extern crate askama;
//! # use askama::{Localize, impl_localize, Template};
//! # impl_localize! {
//! # #[localize(path = "i18n", default_locale = "en_US")]
//! # struct AppLocalizer(_);
//! # }
//! # #[derive(Template)]
//! # #[template(path = "hello.html")]
//! # pub struct HelloTemplate<'a> {
//! # #[localizer]
//! # pub localizer: AppLocalizer,
//! # pub name: &'a str,
//! # pub age_hours: f32,
//! # }
//! # }
//! # use lmao_rustc::*;
//! # use askama::Localize;
//! println!("{}", HelloTemplate {
//! localizer: AppLocalizer::new(Some("en_US"), None),
//! name: "Jamie",
//! age_hours: 195_481.8
//! });
//! /*
//! <h1>Hello, Jamie!</h1>
//! <h3>You are 195,481.8 hours old.</h1>
//! */
//! println!("{}", HelloTemplate {
//! localizer: AppLocalizer::new(Some("es_MX"), None),
//! name: "Jamie",
//! age_hours: 195_481.8
//! });
//! /*
//! <h1>¡Hola, Jamie!</h1>
//! <h3>Tienes 195.481,8 horas.</h1>
//! */
//! # }
//! ```
//!
//! To generate a coverage report, run:
//! `cargo test i18n_coverage -- --nocapture`
//!
//! This will report on the percent of messages translated in each locale.

#![allow(unused_imports)]
#[macro_use]
Expand All @@ -462,6 +589,12 @@ use std::path::Path;

pub use askama_escape::{Html, Text};

#[cfg(feature = "with-i18n")]
use std::collections::HashMap;

#[cfg(feature = "with-i18n")]
pub use crate::shared::i18n;

/// Main `Template` trait; implementations are generally derived
pub trait Template {
/// Helper method which allocates a new `String` and renders into it
Expand All @@ -480,6 +613,50 @@ pub trait Template {
fn size_hint() -> usize;
}

/// `Localize` trait; can be included in templates to allow using the `localize` filter.
/// Implementations are generally derived.
#[cfg(feature = "with-i18n")]
pub trait Localize: Sized {
// Implementation notes:
// All of the code that actually talks to fluent is in the `askama_shared::i18n::macro_impl` module.
// Codegen for `impl_localize!` is in `askama_derive::gen_impl_localize`.
// Codegen for the `localize` filter is in `askama_derive::generator`.

/// Create a localizer.
///
/// Every localizer contains a chain of locales to look up messages in. If it can't find a message in the
/// first locale, it will move on to the second, and so on.
///
/// This chain prefers the locale `user_locale`, if provided. It will also try short-code variants;
/// for example, if `user_locale` is `en_AU` and only `en_UK` is available, `en_UK` will be selected.
///
/// Then, it falls back to locales from `accept_language`, if provided.
/// `accept_language` should be an `Accept-Language` HTTP header like: `"de_DE, de, en_UK, en; q=0.5"`.
///
/// Finally, it will fall back to the default language provided to `impl_localize!()`.
///
/// Tests will be autogenerated to ensure that the default language has full coverage of messages
/// used in the application. This helps catch mis-typed message-IDs, and ensures your application will always
/// have a message in *some* language.
fn new(user_locale: Option<&str>, accept_language: Option<&str>) -> Self;

/// Localize a particular message.
/// Used by templates; users shouldn't need to call this function directly.
fn localize(
&self,
message_id: &str,
args: &[(&str, &shared::i18n::I18nValue)],
) -> Result<String>;

/// Whether a localizer has a particular message available.
/// Used by automated tests; users shouldn't need to call this function directly.
fn has_message(&self, message_id: &str) -> bool;

/// Default locale of this localizer.
/// Used by automated tests.
fn default_locale() -> &'static str;
}

pub use crate::shared::filters;
pub use crate::shared::helpers;
pub use crate::shared::{read_config_file, Error, MarkupDisplay, Result};
Expand Down Expand Up @@ -633,3 +810,6 @@ pub mod gotham {
note = "file-level dependency tracking is handled automatically without build script"
)]
pub fn rerun_if_templates_changed() {}

#[cfg(feature = "with-i18n")]
pub use askama_derive::impl_localize;
Empty file added askama/templates/hello.html
Empty file.
3 changes: 3 additions & 0 deletions askama_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ iron = []
rocket = []
actix-web = []
gotham = []
with-i18n = ["fluent-bundle", "fluent-syntax"]

[dependencies]
askama_shared = { version = "0.8", path = "../askama_shared" }
nom = "4"
proc-macro2 = "0.4"
quote = "0.6"
syn = "0.15"
fluent-bundle = { version = "0.5", optional = true }
fluent-syntax = { version = "0.8", optional = true }
Loading