• Stars
    star
    136
  • Rank 267,670 (Top 6 %)
  • Language
    Rust
  • License
    Apache License 2.0
  • Created over 4 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Easily add Fluent to your Rust project.

Fluent Templates: A High level Fluent API.

Build & Test crates.io Help Wanted Lines Of Code Documentation

fluent-templates lets you to easily integrate Fluent localisation into your Rust application or library. It does this by providing a high level "loader" API that loads fluent strings based on simple language negotiation, and the FluentLoader struct which is a Loader agnostic container type that comes with optional trait implementations for popular templating engines such as handlebars or tera that allow you to be able to use your localisations in your templates with no boilerplate.

Loaders

Currently this crate provides two different kinds of loaders that cover two main use cases.

  • static_loader! — A procedural macro that loads your fluent resources at compile-time into your binary and creates a new StaticLoader static variable that allows you to access the localisations. static_loader! is most useful when you want to localise your application and want to ship your fluent resources with your binary.

  • ArcLoader — A struct that loads your fluent resources at run-time using Arc as its backing storage. ArcLoader is most useful for when you want to be able to change and/or update localisations at run-time, or if you're writing a developer tool that wants to provide fluent localisation in your own application such as a static site generator.

static_loader!

The easiest way to use fluent-templates is to use the static_loader! procedural macro that will create a new StaticLoader static variable.

Basic Example

fluent_templates::static_loader! {
    // Declare our `StaticLoader` named `LOCALES`.
    static LOCALES = {
        // The directory of localisations and fluent resources.
        locales: "./tests/locales",
        // The language to falback on if something is not present.
        fallback_language: "en-US",
        // Optional: A fluent resource that is shared with every locale.
        core_locales: "./tests/locales/core.ftl",
    };
}

Customise Example

You can also modify each FluentBundle on initialisation to be able to change configuration or add resources from Rust.

use fluent_bundle::FluentResource;
use fluent_templates::static_loader;
use once_cell::sync::Lazy;

static_loader! {
    // Declare our `StaticLoader` named `LOCALES`.
    static LOCALES = {
        // The directory of localisations and fluent resources.
        locales: "./tests/locales",
        // The language to falback on if something is not present.
        fallback_language: "en-US",
        // Optional: A fluent resource that is shared with every locale.
        core_locales: "./tests/locales/core.ftl",
        // Optional: A function that is run over each fluent bundle.
        customise: |bundle| {
            // Since this will be called for each locale bundle and
            // `FluentResource`s need to be either `&'static` or behind an
            // `Arc` it's recommended you use lazily initialised
            // static variables.
            static CRATE_VERSION_FTL: Lazy<FluentResource> = Lazy::new(|| {
                let ftl_string = String::from(
                    concat!("-crate-version = {}", env!("CARGO_PKG_VERSION"))
                );

                FluentResource::try_new(ftl_string).unwrap()
            });

            bundle.add_resource(&CRATE_VERSION_FTL);
        }
    };
}

Locales Directory

fluent-templates will collect all subdirectories that match a valid Unicode Language Identifier and bundle all fluent files found in those directories and map those resources to the respective identifier. fluent-templates will recurse through each language directory as needed and will respect any .gitignore or .ignore files present.

Example Layout

locales
├── core.ftl
├── en-US
│   └── main.ftl
├── fr
│   └── main.ftl
├── zh-CN
│   └── main.ftl
└── zh-TW
    └── main.ftl

Looking up fluent resources

You can use the Loader trait to lookup a given fluent resource, and provide any additional arguments as needed with lookup_with_args.

Example

 # In `locales/en-US/main.ftl`
 hello-world = Hello World!
 greeting = Hello { $name }!

 # In `locales/fr/main.ftl`
 hello-world = Bonjour le monde!
 greeting = Bonjour { $name }!

 # In `locales/de/main.ftl`
 hello-world = Hallo Welt!
 greeting = Hallo { $name }!
use std::collections::HashMap;

use unic_langid::{LanguageIdentifier, langid};
use fluent_templates::{Loader, static_loader};

const US_ENGLISH: LanguageIdentifier = langid!("en-US");
const FRENCH: LanguageIdentifier = langid!("fr");
const GERMAN: LanguageIdentifier = langid!("de");

static_loader! {
    static LOCALES = {
        locales: "./tests/locales",
        fallback_language: "en-US",
        // Removes unicode isolating marks around arguments, you typically
        // should only set to false when testing.
        customise: |bundle| bundle.set_use_isolating(false),
    };
}

fn main() {
    assert_eq!("Hello World!", LOCALES.lookup(&US_ENGLISH, "hello-world"));
    assert_eq!("Bonjour le monde!", LOCALES.lookup(&FRENCH, "hello-world"));
    assert_eq!("Hallo Welt!", LOCALES.lookup(&GERMAN, "hello-world"));

    let args = {
        let mut map = HashMap::new();
        map.insert(String::from("name"), "Alice".into());
        map
    };

    assert_eq!("Hello Alice!", LOCALES.lookup_with_args(&US_ENGLISH, "greeting", &args));
    assert_eq!("Bonjour Alice!", LOCALES.lookup_with_args(&FRENCH, "greeting", &args));
    assert_eq!("Hallo Alice!", LOCALES.lookup_with_args(&GERMAN, "greeting", &args));
}

Tera

With the tera feature you can use FluentLoader as a Tera function. It accepts a key parameter pointing to a fluent resource and lang for what language to get that key for. Optionally you can pass extra arguments to the function as arguments to the resource. fluent-templates will automatically convert argument keys from Tera's snake_case to the fluent's preferred kebab-case arguments.

fluent-templates = { version = "*", features = ["tera"] }
use fluent_templates::{FluentLoader, static_loader};

static_loader! {
    static LOCALES = {
        locales: "./tests/locales",
        fallback_language: "en-US",
        // Removes unicode isolating marks around arguments, you typically
        // should only set to false when testing.
        customise: |bundle| bundle.set_use_isolating(false),
    };
}

fn main() {
    let mut tera = tera::Tera::default();
    let ctx = tera::Context::default();
    tera.register_function("fluent", FluentLoader::new(&*LOCALES));
    assert_eq!(
        "Hello World!",
        tera.render_str(r#"{{ fluent(key="hello-world", lang="en-US") }}"#, &ctx).unwrap()
    );
    assert_eq!(
        "Hello Alice!",
        tera.render_str(r#"{{ fluent(key="greeting", lang="en-US", name="Alice") }}"#, &ctx).unwrap()
    );
}

Handlebars

In handlebars, fluent-templates will read the lang field in your handlebars::Context while rendering.

fluent-templates = { version = "*", features = ["handlebars"] }
use fluent_templates::{FluentLoader, static_loader};

static_loader! {
    static LOCALES = {
        locales: "./tests/locales",
        fallback_language: "en-US",
        // Removes unicode isolating marks around arguments, you typically
        // should only set to false when testing.
        customise: |bundle| bundle.set_use_isolating(false),
    };
}

fn main() {
    let mut handlebars = handlebars::Handlebars::new();
    handlebars.register_helper("fluent", Box::new(FluentLoader::new(&*LOCALES)));
    let data = serde_json::json!({"lang": "zh-CN"});
    assert_eq!("Hello World!", handlebars.render_template(r#"{{fluent "hello-world"}}"#, &data).unwrap());
    assert_eq!("Hello Alice!", handlebars.render_template(r#"{{fluent "greeting" name="Alice"}}"#, &data).unwrap());
}

Handlebars helper syntax.

The main helper provided is the {{fluent}} helper. If you have the following Fluent file:

foo-bar = "foo bar"
placeholder = this has a placeholder { $variable }
placeholder2 = this has { $variable1 } { $variable2 }

You can include the strings in your template with

<!-- will render "foo bar" -->
{{fluent "foo-bar"}}
<!-- will render "this has a placeholder baz" -->
{{fluent "placeholder" variable="baz"}}

You may also use the {{fluentparam}} helper to specify variables, especially if you need them to be multiline.

{{#fluent "placeholder2"}}
    {{#fluentparam "variable1"}}
        first line
        second line
    {{/fluentparam}}
    {{#fluentparam "variable2"}}
        first line
        second line
    {{/fluentparam}}
{{/fluent}}

FAQ

Why is there extra characters around the values of arguments?

These are called "Unicode Isolating Marks" that used to allow the text to be bidirectional. You can disable this with FluentBundle::set_isolating_marks being set to false.

static_loader! {
    static LOCALES = {
        locales: "./tests/locales",
        fallback_language: "en-US",
        // Removes unicode isolating marks around arguments.
        customise: |bundle| bundle.set_use_isolating(false),
    };
}

More Repositories

1

tokei

Count your code, quickly.
Rust
10,875
star
2

octocrab

A modern, extensible GitHub API Client for Rust.
Rust
1,058
star
3

rasn

A Safe #[no_std] ASN.1 Codec Framework
Rust
153
star
4

mean-bean-ci-template

Build and Test Your Rust Projects with Zero Configuration
Shell
100
star
5

Mammut

A wrapper for the Mastodon API in Rust.
Rust
69
star
6

remove_dir_all

Reliable remove_dir_all implementation for Windows
Rust
59
star
7

tokei_rs

The tokei.rs server code.
Rust
55
star
8

eve

A utility to easily search and replace with environment variables.
Rust
34
star
9

deploy-mdbook

A GitHub Action to automatically build and deploy your mdbook project.
TypeScript
29
star
10

i8080

An intel 8080 emulator
Rust
28
star
11

timetill.rs

A community website for highlighting Rust conferences.
Vue
23
star
12

region_buffer

A growable array allowing for multiple mutable non-overlapping regions.
Rust
16
star
13

hera

A program for checking if there were code changes between git commits.
Rust
9
star
14

unwrap_to

A Rust utility macro to unwrap enums.
Rust
8
star
15

errln

Convenience macros for writing to stderr.
Rust
6
star
16

get-github-release

A GitHub Action for downloading any binary available through GitHub Releases.
JavaScript
6
star
17

Polly

A truly logic-less templating language for Rust servers.
Rust
5
star
18

gh-auditor

Rust
3
star
19

event_feedback

A proof of concept for an event feedback system.
Rust
2
star
20

csv-to-md

A small tool to convert a csv to markdown.
Rust
2
star
21

numcount

A small utility that counts up the numbers in a file.
Rust
2
star
22

tonic-ice

Starlark
1
star
23

keywords

Rust
1
star
24

cargo-raze-bug

Rust
1
star