phrasebook
Fluent localization for Rust web frontends: message catalogs per language, formatting with arguments and plurals, a fallback language, and the user's chosen language remembered in the browser.
An app declares its languages and catalogs once, in a Phrasebook, and
hands its components a Phrases handle: the phrasebook open at the
current language. Components look messages up with Phrases::text and
Phrases::msg. A library ships its own Catalog under a prefix,
which an app adds to its phrasebook and can override or translate, and
which the library falls back to when the app provides nothing.
Lang, Catalog and Source are defined in their own crate,
phrasebook-catalog, and re-exported here. A library's catalog names
that crate's Catalog, which changes far more rarely than phrasebook, so
it survives phrasebook's breaking releases: an app moves to a new
phrasebook without waiting for every library it uses to move with it.
The design is in docs/design.md, including which dependencies appear
in the API and what is deliberately left breaking-prone.
Features
std is on by default and is what everything but Lang, Catalog
and Source needs; the other features enable it. On docs.rs, items that
need a feature are marked with it.
| Feature | Module | What it provides |
|---|---|---|
| (none) | Lang, Catalog and Source |
|
std (default) |
check |
Phrasebook, Phrases, Args, Value, args!, and the check test helpers |
alloc |
nothing yet; enabled by std |
|
browser |
browser |
the language remembered in localStorage, the browser's preferred languages, Phrases::store, and numbers formatted in the handle's language |
yew023 |
yew023 |
yew 0.23: ImplicitClone and IntoPropValue impls, and the or_default and or_catalog helpers for components rendered without a provided handle; turns on phrasebook-catalog's yew023 for the impls on Lang |
koine03 |
koine03 |
conversions between Lang and koine 0.3's Language, Country and Locale, koine's English names of languages (koine03::name), and a warning for declared languages koine does not know; turns on phrasebook-catalog's koine03 for the conversions |
Fluent and unic-langid are used underneath but appear nowhere in the API, so upgrading them is never a breaking change. A dependency that is not yet 1.0 and does appear in the API is named after its version in its feature and module, so a later version can be added beside it.
yew023 does not enable browser; an app in the browser enables both.
yew023 and koine03 forward to phrasebook-catalog's features of the
same names, because only the crate that defines Lang may implement
yew's and koine's traits for it.
Example
use ;
const SV_SE: Lang = new;
static APP: Catalog = new;
// The first declared language is the fallback.
let book = builder
.language
.language
.catalog
.build;
let phrases = book.phrases;
assert_eq!;
// Not translated yet: the fallback language, logged once.
assert_eq!;
// Not defined anywhere: the id, so the hole is visible.
assert_eq!;
let phrases = phrases.with_lang;
assert_eq!;
Pass numbers as numbers, not as strings formatted beforehand. With
browser, in the browser, a number is formatted in the handle's language
(12,345 in English, 12 345 in Swedish), honouring NUMBER()'s options
in the message; a string is shown as written. A number that is not a
quantity, such as a year, is passed as a string, or formatted with
NUMBER($year, useGrouping: "false"). Phrases::number formats a number
shown outside a message the same way.
In an app's tests, check turns a catalog's problems (syntax errors,
duplicate ids, ids outside the prefix, messages that do not format, and
untranslated or extra messages) into failures. check::all runs every
check of a catalog in every language at once, naming the library catalogs
the app translates, and Phrasebook::has_catalog asserts that a
library's catalog was added. check::coverage compares the ids the code
asks for with the ones a catalog defines, both ways.
With yew
The context type is Phrases itself. The app's root owns the handle as
app state and provides it with yew's own ContextProvider<Phrases>; on a
language change it provides phrases.with_lang(lang) (and, with browser,
calls browser::set_document_lang and store). Components read it with
the use_phrases hook, or keep a PhrasesContext in a struct component.
// The app's root, in `view`.
html!
// An app's component.
let phrases = use_phrases;
// A library's component, with its own catalog `L10N`.
let phrases = use_phrases_with;
// A struct component, in `create`; each later handle arrives as a
// message, for `self.l10n.set(phrases)` in `update`.
let l10n = new;
A library's component falls back to its own catalog for messages the app
lacks, or to the catalog alone when nothing is provided. The yew023
module documents both forms in full, struct components included.
To put markup where a translation places an argument, such as a user's
name in <strong>, yew023::html_around renders the message around an
element (Phrases::msg_around gives the text on either side).
Examples
examples/tour.rs prints what an app's catalog, a library's catalog
with a backstop, plurals and the fallback language produce, in English
and Swedish:
examples/app is a small Yew app: the root owns and provides the
handle, a picker switches and remembers the language, and a library-style
component falls back to its own catalog. It is its own cargo workspace,
so it adds nothing to the crate's builds, and is built and served with
trunk:
&&