Expand description
Generate typed Rust translation APIs from modular Fluent catalogs.
See the setup guide for installation, and the migration guide when upgrading from 0.1.4. For 0.2.1 applications, see the 0.2.2 migration.
Language directories are discovered automatically. FTL file paths become named Rust types, and message parameters become typed accessor arguments.
Built on fluent-typed, which generates the typed message accessors and provides Fluent formatting. This crate adds module discovery and a typed translation tree around that foundation. Applications choose their own resource loading, active language and presentation layer.
§Feature selection
build(default): discovery, schema validation, source generation and theExtensionAPI for custom integrations. Enable in build-dependencies.manifest: optional TOML parsing and file-manifest factory.- No default features:
translations!and std-only source contracts, with no dependencies. Use this configuration in normal dependencies. The generated code still requires the application’sfluent-typedandfluent-syntaxdependencies.
Cargo resolver 2/3 keeps the build and normal feature contexts separate. See the dependency setup for the build and runtime dependency declarations.
§Configure the consuming package
[package.metadata.localization]
catalog = "assets/localizations/localization.toml"catalog is a filesystem path relative to the package’s Cargo.toml; .. can
locate shared sources. Its filename is configurable. The generator does not
select an engine asset root. The TOML contains:
translations-directory = "translations"
source-language = "en"
default-language = "en"translations-directory is optional and relative to this TOML. Omit it to use
locale folders beside the file (default .). The legacy languages-directory
alias is accepted, but specifying both names is an error.
With the explicit path above, put matching FTL modules under
assets/localizations/translations/en/, es/
and any other locale directories. The source language defines message keys,
references and argument annotations. default-language is emitted as
DEFAULT_LANGUAGE metadata for application startup; Locale::default()
identifies the source language. Each language must provide the same contract.
§Generate and use the API
Return fluent_typed_codegen::build() from the consuming build.rs to run
validation and generation. Errors produce readable diagnostics and a failing
exit code. cargo check also regenerates for IDE indexing; no application
execution is needed. Source files are never rewritten.
For a file ui/menu.ftl containing title = Settings, the generated type is
texts::ui::Menu. A directory becomes a namespace/group, a file becomes a
leaf type, and each Fluent message has a typed accessor:
fluent_typed_codegen::translations!(pub mod texts);
let manifest = texts::embed_manifest!();
let translations = texts::Translations::from_manifest(texts::Locale::En, &manifest)?;
let ui: &texts::Ui = translations.ui();
let menu: &texts::ui::Menu = ui.menu();
println!("{}", menu.msg_title());This snippet needs consumer-owned FTL and build output; the runnable Rust example demonstrates the complete setup. There is no public module at a leaf path. Message keys in different files stay independent, including their argument types.
§Output and checked loading
Cargo entrypoints emit Rust, per-module FTL and source metadata into OUT_DIR.
Keep that output under target/ and out of source control. generate also
accepts an explicit tool-owned output directory for custom build frontends.
Failed generation can leave partial output; always propagate build errors.
Generated leaves expose checked new(locale, bytes), safe new_unchecked,
and standalone validate. Translations::from_modules validates and parses
caller-supplied FTL strings before
returning a snapshot. Compatible prose edits can be loaded without rebuilding;
changes to the schema or language/module inventory require regeneration.
These runtime methods perform no filesystem reads. File loading and replacing
snapshots are caller-controlled, not a filesystem watcher.
Sources can come from decoded archive entries or any application-owned buffers;
module keys remain paths below the locale directory, not storage URLs. The
returned snapshot retains no input borrows. Read a coherent revision before
parsing and replace application state only after validation succeeds. Archive
I/O and pack installation remain application choices; there is no implicit fallback.
Generated from_manifest constructors read files through a
LocalizationManifest contract, while
texts::embed_manifest!() explicitly includes a build-prepared raw source set.
Without that macro invocation, generated APIs contain no FTL payload.
The embedded macro is crate-local. It accepts only an empty invocation for the
complete tree or a block of named manifest constants for selected scopes.
Ordinary catalog imports and aliases remain unrestricted.
Unselected FTL is not included even in unoptimized builds without LTO or stripping.
Named embedded manifests use selectors relative to this translation tree:
texts::embed_manifest! {
pub const MENU = ui::Menu;
const COMPLETE = Translations;
}
use texts::ui::Menu as Interface;
let menu: Interface = Interface::from_manifest(texts::Locale::En, &MENU)?;These constants have type LocalizationManifest. They contain source bytes,
not parsed catalogs. A private localization module can export its constants and
catalog aliases without exposing its generated tree. Selectors are generated
schema paths, independent of application imports; strings and type aliases are
rejected as selectors. Attributes such as #[cfg(...)] apply to each declaration.
Reading bytes borrows the selected static data; LocalizationManifest::config
initializes shared metadata on first access. Bind let manifest = &MENU; before
retaining a borrow of that metadata. Fluent parsing happens in from_manifest.
See the complete loading recipes for bytes, files, embedding, all languages and explicit module lifetimes.
§Numbers, plural selection and presentation
Native Fluent numeric selectors remain available; this generator does not
replace upstream argument types or select-expression behavior. For numbers,
percentages, currencies and dates, it’s recommended to use dedicated
ICU or
ICU4X formatters. Formatting policies and component
stability belong to the application.
For decimal text, use DecimalFormatter.
If grammar must follow visible precision, pass the same prepared Decimal to
PluralRules.
Annotate the displayed text and category keyword as (String) in source FTL.
The runnable example
uses ICU directly; no special generated numeric type, generator dependency or
feature is required. Respect ICU’s operand limits for arbitrary-precision input.
A compact ICU walkthrough
shows reusable formatters and a numeric input. Catalogs and formatters can be
kept in application state; the application decides when to recompute strings
and how to use the result.
String categories match literal Fluent keys such as [one]; unmatched strings
select the starred default. Numeric exact matches such as [1] still require
a numeric selector. Keep number formatting aligned with the snapshot’s locale.
Number formatting and Fluent interpolation isolation do not implement RTL
layout, glyph shaping or fonts; those belong to the application’s renderer.
§Custom integrations
With build, Extension adds an entrypoint decorated with typed Rust syntax,
while preserving the plain generated API. It can add attributes, imports or
application-specific registration code. See the
extension guide
for its syntax hooks and consumer-compilation requirements.
Repository links follow main; this API reference describes the viewed version.
§Typed errors
The standard build.rs remains unchanged: return fluent_typed_codegen::build()
from main() -> std::process::ExitCode. It prints readable diagnostics and returns
failure without a panic. Use from_cargo() when the caller needs to handle failures.
fn main() -> Result<(), fluent_typed_codegen::BuildError> {
fluent_typed_codegen::from_cargo()
}from_cargo, from_cargo_with, generate and generate_with return BuildError.
Match variants to distinguish missing Cargo environment, configuration failures,
I/O, catalog/module mismatches, schema/reference problems and generation failures.
I/O errors retain the operation, native filesystem path and std::io::Error.
Upstream failures retain their module and original fluent_typed::BuildError.
use fluent_typed_codegen::BuildError;
fn report(error: &BuildError) {
match error {
BuildError::Io { operation, path, source } => {
eprintln!("{operation} {} failed: {:?}", path.display(), source.kind());
}
BuildError::ModuleMismatch(details) => {
eprintln!("{}: missing {:?}, extra {:?}", details.locale, details.missing, details.extra);
}
_ => eprintln!("{error}"),
}
}Settings::from_manifest and CatalogConfig::parse return ConfigError.
Its InvalidField variant identifies the field and distinguishes Missing,
WrongType and Empty; InvalidPath preserves the original PathBuf and a
PathError reason. Unknown parameters remain ignored. Invalid TOML retains the
original parser error and span. No filesystem access occurs in these parsers.
LocalizationManifest reports ManifestError::Config { source } for configuration
and logical-path failures, RequiresLoader { origin } for virtual addresses,
Io for filesystem failures, and MissingModule for absent embedded entries.
The error types implement Display and std::error::Error; source() exposes
underlying parser, configuration, I/O and upstream errors where available.
Applications using ? with a compatible error type continue to propagate failures.
Replace explicitly declared Result<_, String> with the appropriate typed error,
or convert deliberately with map_err(|error| error.to_string()) at an application
boundary. These changes concern configuration and host generation; generated
catalog constructors retain their existing LoadError API. Build-only types and
syntax dependencies remain behind build; macro-only consumers need no dependencies.
Re-exports§
pub use syn;
Macros§
- translations
- Declare a module containing this package’s generated Fluent translations.
Structs§
- Catalog
Config - Contents of
localization.toml, separate from Cargo’s catalog path. - Localization
Manifest - A source contract for readable FTL; creating one never parses FTL.
- Module
Mismatch - Per-language difference from the complete source module inventory.
- Scope
- A generated type and its path of accessor calls from the root snapshot.
- Settings
- A package-relative filesystem path locating the catalog configuration.
Enums§
- Build
Error - Configuration, catalog validation, generation or filesystem failure.
- Config
Error - Invalid TOML, a recognized setting, or a configuration path.
- Config
Field - A localization setting or requested source address.
- Field
Error - Why a recognized field cannot be used as a string.
- IoOperation
- Filesystem operation that failed.
- Manifest
Error - A manifest or requested module could not be obtained.
- Name
Error - Why a source component cannot identify a Rust module or type.
- Name
Owner - The generated declaration owning a conflicting Rust name.
- Path
Error - Why a filesystem input path is invalid.
- Schema
Error - Fluent syntax, duplicate key or contract mismatch.
- Syntax
Node - Opaque Rust syntax category rejected before source printing.
- Upstream
Shape Error - Unsupported structure in upstream generated source.
Traits§
- Extension
- Syntax-level extension for a consuming framework’s generated entrypoint.
Functions§
- build
- Generate catalogs from a Cargo build script with readable failure output.
- build_
with - As
build, with an opt-in framework source extension. - from_
cargo - Generate using the consuming package’s Cargo.toml metadata and Cargo OUT_DIR.
- from_
cargo_ with - As
from_cargo, also emitting the extension’s entrypoint. - generate
- Generate from explicit paths, for custom build frontends and tests.
- generate_
with - As
generate, with an additional framework entrypoint.