fluent-zero
Zero-allocation, high-performance Fluent localization for Rust.
fluent-zero is a specialized localization loader designed for high-performance applications, such as GUI clients (egui, iced, winit) and Game Development (Bevy, Fyrox).
Unlike other loaders that prioritize template engine integration or hot-reloading, fluent-zero prioritizes runtime speed and memory efficiency. It generates static code at build time to allow for O(1) lookups that return &'static str whenever possible, eliminating the heap allocation overhead typical of localization libraries.
β‘ Why fluent-zero?
Most Fluent implementations (like fluent-templates) wrap the standard fluent-bundle. When you request a translation, they look it up in a HashMap, parse the pattern, and allocate a new String on the heap to return the resultβeven if the text is static.
In an immediate-mode GUI (like egui) running at 60 FPS, looking up 50 strings per frame results in 3,000 allocations per second. This causes allocator contention and Garbage Collection-like micro-stutters.
fluent-zero solves this by pre-computing the cache at compile time.
| Feature | fluent-templates |
fluent-zero |
|---|---|---|
| Static Text Lookup | Heap Allocation (String) |
Zero Allocation (&'static str) |
| Lookup Speed | HashMap + AST traversal | Perfect Hash Function (PHF) |
| Memory Usage | Full AST loaded on start | Lazy / Zero-Cost Abstraction |
| Best For | Web Servers (Tera/Askama) | Desktop GUIs & Games |
π Usage
1. Installation
You need both the runtime library and the build-time code generator.
[]
= "0.1"
= "0.9"
[]
= "0.1"
2. File Structure
Organize your Fluent files using standard locale directories:
assets/
βββ locales/
βββ en-US/
β βββ main.ftl
βββ fr-FR/
β βββ main.ftl
βββ de/
βββ main.ftl
3. Build Script (build.rs)
Configure the code generator to read your locales directory. This will generate the static PHF maps and Rust code required for the zero-allocation cache inside your OUT_DIR.
// build.rs
4. Application Code
In your lib.rs (or main.rs), you must include the generated file. This brings the CACHE and LOCALES statics into scope, which the t! macro relies on.
use ;
// 1. Include the generated code from build.rs
include!;
π¦ Library Support & Nested Translations
fluent-zero supports a modular architecture where libraries and dependencies manage their own translations independently, but share their end results with the caller.
If you are writing a library (e.g., a widget crate or a game engine plugin), you can include fluent-zero-build in your library's build.rs. Your library will generate its own private CACHE and LOCALES maps based on its own .ftl files, and your callers will automatically receive those translations via your normal APIs based on the set global language.
Global Synchronization
While the translation data is isolated per crate (compile-time), the language selection is global (runtime).
fluent_zero::set_lang(...): Updates the language for the entire application stack.t!(...): Uses the library-specific translation files but respects the globally set language.
This allows for a cohesive ecosystem where the end-user application sets the language once, and all dependencies (UI widgets, logging, error messages) switch context instantly without manual propagation.
my-app/
βββ Cargo.toml
βββ build.rs // Generates app-specific cache
βββ assets/locales/ // Contains "welcome-screen.ftl"
βββ src/main.rs // Calls set_lang("fr-FR")
my-ui-library/ (Dependency)
βββ Cargo.toml
βββ build.rs // Generates library-specific cache
βββ assets/locales/ // Contains "ok-button.ftl", "cancel.ftl"
βββ src/lib.rs // Calls t!("ok-button")
In this example, when my-app calls set_lang("fr-FR"), the my-ui-library automatically begins serving French strings for its internal components.
π§ How it Works
- Build Time:
fluent-zero-buildscans your.ftlfiles. It identifies which messages are purely static (no variables) and which are dynamic. - Code Gen: It generates a Rust module containing Perfect Hash Maps (via
phf) for every locale.
- Static messages are compiled directly into the binary's read-only data section (
.rodata). - Dynamic messages are stored as raw FTL strings, wrapped in
LazyLock.
- Run Time:
- When you call
t!("hello"),fluent-zerochecks the PHF map. - If it finds a static entry, it returns a reference to the binary data instantly. No parsing. No allocation.
- If it finds a dynamic entry, it initializes the heavy
FluentBundle(only once) and performs the variable substitution.
π οΈ Example: using with egui
This crate shines in immediate mode GUIs. Because t! returns Cow<'static, str>, you can pass the result directly to widgets without .to_string() clones.
β οΈ Trade-offs
While fluent-zero is faster at runtime, it comes with trade-offs compared to fluent-templates:
- Compile Times: Because it generates Rust code for every string in your FTL files, heavily localized applications may see increased compile times.
- Binary Size: Static strings are embedded into the binary executable code.
- Flexibility: You cannot easily load new FTL files from the filesystem at runtime without restarting the application (the cache is baked in).
License
This project is licensed under the MIT license.