Localization

  1. Translation files
  2. Using it
  3. Current locale
  4. Negotiation: locale::negotiate
  5. What’s deliberately not here
  6. Next

Laravel’s __('messages.welcome')/resources/lang/, narrowed to what a translation actually needs: a flat, per-locale key -> string map, :name placeholder substitution, and a current-locale/fallback-locale chain.

Translation files

resources/lang/{locale}.json - one flat JSON object per locale:

// resources/lang/en.json
{ "messages.welcome": "Welcome to :app!" }
// resources/lang/es.json
{ "messages.welcome": "¡Bienvenido a :app!" }

Dot-namespaced keys ("messages.welcome") are just an ordinary string key here - there’s no real per-file namespacing the way Laravel’s PHP-array style has (lang/en/messages.php returning ['welcome' => ...]), which keeps the loader a single, simple format instead of two. Read once, lazily, from disk and cached for the rest of the process; an app with no resources/lang/ directory at all still works fine, it just means every call below returns its own key unchanged.

Using it

larust_support::lang::t("messages.welcome");                              // "Welcome to :app!"
larust_support::lang::t_with("messages.welcome", &[("app", "Larust")]);   // "Welcome to Larust!"

t/t_with are plain functions, not a template directive - `` already accepts any real Rust expression, so this works in a .blade.xr template as-is:

<h1></h1>

A missing key returns itself unchanged (Laravel’s own __() behavior - never a blank string), so a typo’d or not-yet-translated key is always visible rather than silently disappearing.

Current locale

t/t_with resolve against current_locale, which reads a per-request override if one’s been set, falling back to Config::app_locale (APP_LOCALE in .env, default "en") otherwise. A missing key in the current locale falls back to Config::app_fallback_locale (APP_FALLBACK_LOCALE, also "en" by default) before finally falling back to the key itself.

Negotiation: locale::negotiate

Session-backed, matching Laravel’s own common pattern (there’s no built-in Accept-Language sniffing here, same as Laravel’s own default):

route
    .with_sessions(pool, secure)
    .await?
    .middleware(axum::middleware::from_fn(larust_http::locale::negotiate))

Add this after .with_sessions(...) - the same ordering constraint csrf::verify/require_auth already have. Once wired in, any route can persist a language switch:

pub async fn update(session: Session, request: LanguageRequest) -> Result<impl IntoResponse, AppError> {
    let locale = request.validated().locale;
    session.insert(larust_http::locale::SESSION_KEY, locale).await
        .map_err(|error| AppError::Internal(Box::new(error)))?;
    Ok(larust_support::redirect().to("/")?.await)
}

negotiate reads that same session key (larust_http::locale::SESSION_KEY, "locale") on every later request and sets the current-request locale before any handler runs. It’s deliberately opt-in, unlike @push/@stack’s own always-on request scope (see Reactive Components) - current_locale() already degrades correctly with no scope established at all, so there’s nothing to lose by only paying for this where an app actually wires it in. It also doesn’t validate the stored value against any “supported locales” list - that’s app-owned data (see demo’s own LanguageController::update for a real example of rejecting an unsupported one before it ever reaches the session).

What’s deliberately not here

  • No @lang(...) Blade directive. already works through the existing expression mechanism, with zero changes needed to the template parser/AST/codegen - the same “explicit Rust expression, no new template syntax” reasoning @can/@role already established for permission checks. A dedicated directive is a reasonable later addition, not a blocker for this to be useful today.
  • No Accept-Language header negotiation. Locale comes from the session (or wherever your own app decides to set it before negotiate runs) - the same session-preference-first shape Laravel apps commonly build by hand, not automatic browser-header sniffing.
  • No pluralization rules. Laravel’s trans_choice/__('...|...') count-based plural forms aren’t ported - t_with handles named placeholders only. An app that needs real plural rules can still branch on the count itself and pick a different key.

Next

You’ve now seen every major feature area. The CLI Reference covers xr end to end, or jump to Testing / Deployment.