#localization #axum

axum_l10n

A crate with localization utilities for Axum

11 releases

0.2.8 Mar 28, 2024
0.2.7 Mar 18, 2024
0.2.6 Feb 22, 2024
0.2.4 Jan 26, 2024
0.1.1 Jan 2, 2024

#45 in Internationalization (i18n)

Download history 6/week @ 2023-12-29 61/week @ 2024-01-19 96/week @ 2024-01-26 54/week @ 2024-02-02 72/week @ 2024-02-09 335/week @ 2024-02-16 185/week @ 2024-02-23 68/week @ 2024-03-01 34/week @ 2024-03-08 156/week @ 2024-03-15 175/week @ 2024-03-22 76/week @ 2024-03-29 62/week @ 2024-04-05 22/week @ 2024-04-12

348 downloads per month

MIT/Apache

33KB
630 lines

About this Crate

This crate offers some localization tools to be used with Axum.

Basic usage

You can use this crate to extract a language identifier (ex en-US) from a request.

If the mode is set to RedirectMode::NoRedirect, the Accept-Language header is used to find the users preferred language. If the mode is set to either RedirectMode::RedirectToFullLocaleSubPath or RedirectMode::RedirectToLanguageSubPath, the user will be redirected to a sub-path based on their Accept-Language headers, to to the default language if not supported.

use unic_langid::{langid, LanguageIdentifier};
use axum::Extension;

pub const ENGLISH: LanguageIdentifier = langid!("en");
pub const JAPANESE: LanguageIdentifier = langid!("ja");

let router = axum::Router::new()
      .route("/lists", get(|Extension(lang): Extension<LanguageIdentifier>|
        async move {
          Html(format!("Your language is: {}", lang.to_string()))
        }))
      .layer(axum_l10n::LanguageIdentifierExtractorLayer::new(
          ENGLISH,
          vec![ENGLISH, JAPANESE],
          axum_l10n::RedirectMode::NoRedirect,
      ));

For RedirectMode::RedirectToFullLocaleSubPath or RedirectMode::RedirectToLanguageSubPath, you must wrap this service/middleware around the entire axum app, as explained here.

When using the subpath redirect modes, you may want to exclude some folders from the redirect as below:

let l10n_middleware = axum_l10n::LanguageIdentifierExtractorLayer::new(
        JAPANESE,
        vec![JAPANESE, ENGLISH],
        axum_l10n::RedirectMode::RedirectToLanguageSubPath,
    )
    .excluded_paths(&["/api", "/assets", "/auth"]);

Features

fluent

Enabling fluent allows you to use the fluent Localizer to add bundles for translation.

See fluent-rs for details about fluent and rust.

Usage

use unic_langid::{langid, LanguageIdentifier};
use axum_l10n::Localizer;

pub const ENGLISH: LanguageIdentifier = langid!("en");
pub const JAPANESE: LanguageIdentifier = langid!("ja");
let mut localizer = Localizer::new();

localizer
    .add_bundle(JAPANESE, &["locales/ja/main.ftl", "locales/ja/login.ftl"])
    .unwrap();
localizer
    .add_bundle(ENGLISH, &["locales/en/main.ftl", "locales/en/login.ftl"])
    .unwrap();

let message = localizer.format_message(&ENGLISH, "test-key-a", None);

assert_eq!(Some(String::from("Hello World")), message);

tera

Enabling the tera feature allows you to use the fluent translations inside tera templates.

See tera for more information on tera.

Usage

Initialization:

use tera::Tera;

let mut tera = Tera::new("src/views/templates/**/*").expect("tera parsing error");

let mut localizer = Localizer::new();

localizer
    .add_bundle(ENGLISH, &["locales/en/main.ftl", "locales/en/login.ftl"])
    .unwrap();

tera.register_function("fluent", localizer);

Axum handler:

#[derive(Clone)]
struct ViewRouterState {
    pool: mysql_async::Pool,
    tera: Arc<tera::Tera>,
}

async fn lists_view(
    State(state): State<ViewRouterState>,
    Extension(lang): Extension<LanguageIdentifier>,
) -> axum::response::Response {
    let lists: Vec<String> = List::paginate(&state.pool, claim.sub)
        .await.unwrap();

    let mut ctx = Context::new();
    ctx.insert("lists", &lists);
    ctx.insert("lang", &lang);

    let html = state.tera.render("lists.html", &ctx).unwrap();

    Html(html).into_response()
}

In tera template:

<label for="family-id">{{ fluent(key="list-family", lang=lang) }}</label>
<select name="family-id" id="family-id">
  {% for family in families %}
  <option value="{{ family.family_id }}">{{ family.family_name }}</option>
  {% endfor %}
</select>

Dependencies

~4–13MB
~155K SLoC