Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Language Library ( lang-lib ) is a file-based multi-language translation library for Rust. It loads TOML translation files at startup and serves lookups by key, with runtime locale switching and configurable fallback chains. Designed to be simple, fast, lightweight, concurrent, and lock-free, it stays focused on doing one thing well — translation — without the weight of a full internationalization framework.
Setup is deliberately frictionless. There's no code generation, no build script, no CLI tooling, and no compile-time macros to wrestle with, just map your language files, call the t! macro, and you're translating. Drop your TOML files in a directory, point lang-lib at it, and every key is available across your application. Add a new language by adding a file; no rebuild step, no schema regeneration, nothing to wire up.
Every part of the API is shaped to reduce the amount of code you write. The t! macro handles the common case in a single line, accepts an optional locale override, and takes an inline fallback for missing keys, covering three distinct lookup patterns with one consistent call. For web handlers that need a fixed locale per request, a lightweight Translator wraps the active locale so you call .translate("key") without repeating it. No setup objects, no lifecycle management, no plumbing, just the call you actually wanted to make.
A simple, lightweight library with a high-performance, enterprise-ready core; engineered for maximum stability and the resilience to thrive under load. Don't let the simplicity fool you: underneath is a heavily-tested, performance-tuned, and fully error-hardened engine. Simplicity, stability, and high-performance — together, with zero compromise.
FEATURES
-
Multi-Language Support — Load any number of locales from plain TOML files and switch the active language at runtime. No fixed locale set, no recompile to add one.
-
Zero-Allocation Lookups — Translation values are interned at load time into a process-wide pool, and every successful lookup returns a
Cow::Borrowed(&'static str)that points directly into that pool. The hot path costs zero heap allocations. Fallback and key-return paths also avoid allocation by borrowing from the caller's inputs. -
Lock-Free Reads — Translation state is swapped atomically via
ArcSwap. Concurrent lookups never take a lock and never contend, scaling cleanly across cores. -
Thread-Safe — Every public type is
Send + Sync. Share one translation store across your entire application; call it from any thread without external synchronization. -
Configurable Fallback Chains — When a key is missing in the active locale,
lang-libwalks an ordered fallback list before giving up, so partial translations degrade gracefully instead of breaking. -
Runtime Locale Switching — Change the active language on the fly without reloading files or rebuilding state. Ideal for per-request locale selection in web servers.
-
One-Line API — A single
t!macro covers the common case, with optional locale and inline-fallback arguments. No translator object to construct, no context to thread through your call stack. -
No Build Step — No code generation, no build script, no CLI tooling. Map your language files, call the macro, ship. Adding a language means adding a file.
-
Minimal Dependencies — A lean dependency graph and a small surface area keep compile times fast and audits simple.
-
Cross-Platform — Runs identically on Linux, macOS, and Windows.
-
Hot Reload (opt-in) — Enable the
hot-reloadfeature to subscribe to filesystem events on your locales directory; edits to<locale>.tomlare debounced and atomically reloaded in place, withregistry-io-powered change-event notifications wired into [Lang::on_change]. -
Change Notifications (opt-in) — Enable the
registryfeature to install handlers via [Lang::on_change] that fire whenever a locale is loaded, reloaded, or unloaded. Sub-microsecond dispatch overhead per handler.
Installation
[]
= "1.2.0"
Optional features:
[]
# Subscribe to translation change events via registry-io.
= { = "1.2.0", = ["registry"] }
# Watch locale files on disk and reload automatically.
= { = "1.2.0", = ["hot-reload"] }
Quick Start
use ;
// Point to your project's lang folder
set_path;
// Load the locales you need
load.unwrap;
load.unwrap;
// Set the active locale
set_locale;
// Set a fallback chain (checked when a key is missing)
set_fallbacks;
// Translate
let msg = t!; // active locale
let msg = t!; // specific locale
let msg = t!; // inline fallback
let msg = t!; // locale + fallback
Tutorial
If you are wiring this into a real application, the usual setup looks like this.
1. Create a locale directory
your-app/
|- Cargo.toml
|- src/
| \- main.rs
\- locales/
|- en.toml
\- es.toml
2. Create language files
Keep each file flat. One translation key maps to one string value.
# locales/en.toml
= "Acme Control Panel"
= "Sign in"
= "Continue"
= "Your password is incorrect."
= "We could not reach the server."
# locales/es.toml
= "Panel de Control Acme"
= "Iniciar sesion"
= "Continuar"
= "La contrasena es incorrecta."
= "No pudimos conectarnos al servidor."
Rules that matter:
- File names become locale identifiers, so
en.tomlloads asen. - Locale identifiers must be simple file stems like
en,en-US, orpt_BR. - Nested TOML tables and non-string values are ignored.
- Keep keys stable and descriptive. Treat them like public API for your UI.
3. Load locales during startup
use Lang;
4. Translate where the text is rendered
use ;
5. Run the included example
The repository includes a runnable example wired to sample locale files:
cargo run --example basic
Server-Side Locale Policy
For request-driven services, the safest pattern is simple: load locales once at startup, resolve the locale for each request, and pass that locale explicitly when translating.
That means you should usually avoid calling Lang::set_locale inside request
handlers. Lang stores its active locale as process-global state, so changing
it per request creates unnecessary cross-request coupling.
Preferred pattern:
use ;
Runnable server-oriented example:
cargo run --example server
If you want less repetition inside handlers, create a request-scoped helper:
use ;
Accept-Language Helper
If your application already receives an Accept-Language header, the crate now
includes a small helper for turning that header into one of your supported
locale identifiers.
use resolve_accept_language;
let locale = resolve_accept_language;
assert_eq!;
The helper prefers higher q values, then exact locale matches, then
primary-language matches like es-ES -> es.
If your supported locales are built at runtime, use the owned variant instead:
use resolve_accept_language_owned;
let supported = vec!;
let locale = resolve_accept_language_owned;
assert_eq!;
In plain terms: this version is for cases where your locale list is not a
hard-coded &["en", "es"], but comes from config or some other runtime data.
Translator Helper
Translator is a tiny convenience wrapper around a locale string. It keeps
request-local code readable while still using the safe server-side policy.
use ;
This helper does not change Lang::locale(). It only bundles a locale with
repeated translation calls.
Axum Example
The repository also includes a real axum example that plugs locale
resolution into an HTTP handler.
Run it with:
cargo run --example axum_server --features web-example-axum
Then request it with different Accept-Language headers:
curl http://127.0.0.1:3000/
curl -H "Accept-Language: es-ES,es;q=0.9" http://127.0.0.1:3000/
If you prefer actix-web, the repository includes a matching example:
cargo run --example actix_server --features web-example-actix
The three server-oriented examples share the same locale bootstrap and
Accept-Language parsing helper, so their behavior stays aligned as the
examples evolve.
File Format
Plain TOML, one key per string:
# locales/en.toml
= "Your password is incorrect."
= "The page you requested does not exist."
Files are resolved as {path}/{locale}.toml.
Locale identifiers must be simple file stems like en, en-US, or pt_BR.
Path separators and relative path components are rejected before file access.
API Notes
Lang::set_pathchanges the base directory used byLang::load.Lang::load_fromlets you load a locale from a one-off directory.Lang::set_localechanges the process-wide active locale.Lang::translatorcreates a request-scoped helper for repeated lookups.resolve_accept_languagemaps anAccept-Languageheader to one of your supported locales.resolve_accept_language_owneddoes the same job when your supported locales live inVec<String>or similar runtime data.Lang::set_fallbackscontrols the order used when a key is missing.Lang::loadedreturns a sorted list, which is useful for diagnostics.
Fallback Behavior
When a key is not found, lookup proceeds as follows:
- Requested locale (or active locale)
- Each locale in the fallback chain, in order
- Inline
fallback:value if provided int! - The key string itself — never returns empty
Error Handling
lang-lib keeps failure modes narrow and explicit.
use ;
match load
Tips For Production Use
- Load all required locales during startup instead of lazily during request handling.
- Keep one fallback locale with complete coverage, usually
en. - In servers, resolve a locale per request and pass it explicitly instead of mutating the global active locale.
- Treat translation keys as stable identifiers and review changes to them carefully.
Production Notes
- File lookup is cross-platform and uses the platform's native path handling.
- Locale loading rejects path traversal inputs such as
../enor nested paths. - Internal state recovers from poisoned locks instead of panicking on future reads.
Lang::loaded()returns a sorted list for deterministic diagnostics and tests.
Benchmarks
The repository includes a small Criterion benchmark that measures two hot paths:
resolve_accept_language- translation lookup through the loaded in-memory store
- fallback-chain lookup when a key is missing in the requested locale
- complete miss with inline fallback string
- complete miss that returns the key itself
Run it with:
cargo bench --bench performance
This is not a full benchmarking suite, but it gives you repeatable numbers for the operations most likely to matter in a request-driven application.
For interpretation guidance and CI benchmark policy, see BENCHMARKS.md.
Health Signals
The badges at the top of this README point to the CI and benchmark workflows. If they are blank right after adding workflows, trigger the workflows once and GitHub will start showing status immediately.
Repository Examples
examples/basic.rs: end-to-end startup and translation flow.examples/server.rs: request-scoped locale resolution for server-side code.examples/axum_server.rs: realaxumhandler using request-scoped translation.examples/actix_server.rs: realactix-webhandler using the same request-scoped policy.examples/common/mod.rs: shared example helper for locale loading and request locale resolution.examples/locales/en.toml: sample English locale file.examples/locales/es.toml: sample Spanish locale file.BENCHMARKS.md: benchmark usage notes, regression guidance, and CI benchmark policy.benches/performance.rs: Criterion benchmark for request locale resolution and translation lookup.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.