Expand description
Translations: locales/*.yml, %{name} interpolation, plurals, locale per request.
Works like Rails’ I18n.t: YAML files in locales/ compiled into the
Worker, %{name} interpolation, CLDR plurals, fallback to the default
locale and locale selection per request.
# locales/fr.yml (a YAML subset: nested keys and strings)
fr:
posts:
created: "Article créé."
greeting: "Bonjour %{name} !"
count:
one: "%{count} article" # CLDR categories: zero, one, two, few, many, other
other: "%{count} articles"An app declares its locales once in src/lib.rs (ocre g locale en fr
writes it) and adds layer at the end of
routes(); handlers then take the I18n extractor:
// Not compiled here: `locales!` includes `locales/en.yml` and `locales/fr.yml` from the app's root.
static LOCALES: ocre::i18n::Locales = ocre::locales!("en", "fr");use std::sync::LazyLock;
use axum::{Router, routing::get};
use ocre::{Ctx, i18n::{Catalog, I18n, Locales}};
// `ocre::locales!("en", "fr")` expands to this, with the files' contents.
static LOCALES: Locales = LazyLock::new(|| {
Catalog::load(&[
("en", "en:\n posts:\n count:\n one: \"%{count} post\"\n other: \"%{count} posts\"\n"),
("fr", "fr:\n posts:\n count:\n one: \"%{count} article\"\n other: \"%{count} articles\"\n"),
])
});
async fn home(i18n: I18n) -> String {
i18n.t("posts.count").count(3).to_string() // "3 articles" for French visitors
}
fn routes() -> Router<Ctx> {
Router::new()
.route("/", get(home))
// ocre:routes
.layer(ocre::i18n::layer(&LOCALES))
}
// Outside requests (mailers, jobs):
assert_eq!(LOCALES.locale("fr").t("posts.count").count(3).to_string(), "3 articles");In templates, translations are Display values that
askama writes (escaped) straight into the page:
<p>{{ i18n.t("posts.greeting").arg("name", user.name) }}</p>. Never mark
them |safe; for a translation containing markup, end with
.html(), which escapes the values only.
Beyond t: I18n::l formats dates and times, I18n::number and
I18n::currency numbers, I18n::model_name and I18n::attribute
name models and fields, I18n::error_message translates validation
errors, and I18n::time_ago_in_words durations. Their texts come from
the app’s files first, then from built-in translations (compiled in, no
parsing) for English, French, German, Spanish, Italian, Portuguese and
Dutch, under the rails-i18n keys (date.formats.short,
errors.messages.blank…).
The files are parsed once per Worker instance, on the first translation,
by a small parser for this YAML subset; lookups are a BTreeMap search.
No D1, KV or other billed resource is used. I18n picks the locale from
a {locale} path segment, then the locale cookie, then
Accept-Language, then the default locale. A key missing from the
request’s locale falls back to the default locale in release builds
(ocre deploy); debug builds (ocre dev) show
translation missing: fr.posts.created instead, so gaps are visible.
ocre i18n missing lists them (see check).
Structs§
- Catalog
- All locales of an app, parsed: one table of dotted keys per locale code, the first being the default.
- Html
Translation - A
Translationwritten as HTML, fromTranslation::html: the text as is, every value escaped. - I18n
- Axum extractor giving translations in the request’s locale, like Rails’
I18n.twith a per-request locale. - Translation
- A translation being built by
I18n::t, displayed (looked up and interpolated) when written.
Enums§
Constants§
- LOCALE_
COOKIE - Name of the cookie that remembers a visitor’s chosen locale.
- LOCALE_
PARAM - Name of the path parameter
I18nreads the locale from.
Traits§
- Count
- A number
Translation::countaccepts: every integer type, and references to them.
Functions§
- check
- Checks locale files for syntax errors and keys missing from non-default locales.
- layer
- Makes the catalog available to the
I18nextractor, as an axum layer.