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.
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.
While the translation data is isolated per crate (compile-time), the language selection is global (runtime). When your application calls fluent_zero::set_lang("fr-FR"), all UI plugins, logging dependencies, and nested widgets will instantly switch contexts without manual propagation.
π Enterprise Font Subsetting (DAG IPC)
When deploying to environments (like WASM or games) that bundle custom fonts, you must reliably compute the unique characters used across all your dependencies to prevent missing glyphs at runtime.
fluent-zero achieves this with a hermetic, build-system-agnostic pipeline (100% compatible with sccache, Bazel, and Nix). It utilizes Cargo's native IPC (Inter-Package Communication) via the DAG to safely bubble up characters from dependencies without requiring brittle cargo_metadata JSON scraping or workspace directory-walking.
Step 1: Opt-in your Dependencies
For Cargo to authorize data bubbling up to your main application, any UI dependency or plugin using fluent-zero must declare a globally unique links key in its Cargo.toml.
[]
= "my-ui-library"
= "0.1.0"
= "my_ui_library" # <-- **REQUIRED FOR IPC BUBBLING**
Step 2: Configure the Application build.rs
In your top-level application, configure fluent-zero-build to export the charset. The builder will automatically read the injected IPC variables from Cargo (DEP_<LINKS>_FLUENT_CHARSET_PATH) and merge all dependency characters into a single master file.
use ;
use Context as _;
Step 3: The Python Subsetter Script
You will need the fonttools library (pip install fonttools) to strip out unneeded glyphs. Place this production-ready script in scripts/subset_fonts.py.
#!/usr/bin/env python3
=
=
=
# Read the unified character set
=
# Enterprise Safety: Always include basic ASCII (32-126) for debug text and fallbacks
=
= +
# Write out a temporary file for pyftsubset to consume safely
= /
# Iterate and subset all fonts in the directory
= /
# pyftsubset CLI arguments for safe GUI subsetting
=
Your optimized .ttf files are now securely located in OUT_DIR and can be seamlessly embedded into your binary using include_bytes!(concat!(env!("OUT_DIR"), "/my_font.ttf")).
π§ 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.
Notice
This crate is not related to Mozilla Project Fluent in any official capacity. All usage is at your own risk.