Internationalization (i18n)
Overview
Forge’s i18n system loads locale strings from flat text files and provides forge_t(ctx, key) for translation in handlers and views. The locale is per-request, resolved from a cookie, query param, or Accept-Language header.
Locale file format
One key=value pair per line. Keys can use dot notation for namespacing. Lines starting with # are comments.
# locales/en.txt
greeting=Hello!
nav.home=Home
nav.posts=Posts
flash.saved=Changes saved.
flash.error=Something went wrong.
post.title_label=Post Title
post.body_label=Content
errors.blank=%s cannot be blank# locales/fr.txt
greeting=Bonjour !
nav.home=Accueil
nav.posts=Articles
flash.saved=Modifications enregistrées.
flash.error=Une erreur est survenue.
post.title_label=Titre
post.body_label=ContenuLoading locales
Call forge_i18n_load in main() before app_listen:
forge_i18n_load("en", "locales/en.txt")
forge_i18n_load("fr", "locales/fr.txt")
forge_i18n_load("es", "locales/es.txt")Or group the calls in a dedicated function in config.jda:
fn load_i18n() {
forge_i18n_load("en", "locales/en.txt")
forge_i18n_load("fr", "locales/fr.txt")
}Setting the locale per request
Use middleware to detect and set the locale before handlers run:
fn locale_middleware(ctx: i64) {
// 1. Check cookie
let locale = ctx_get_cookie(ctx, "locale")
// 2. Fall back to Accept-Language header (just check first two chars)
if locale.len == 0 {
let al = ctx_header(ctx, "Accept-Language")
if al.len >= 2 { locale = al[0..2] }
}
// 3. Default to "en"
if locale.len == 0 { locale = "en" }
forge_locale_set_ctx(ctx, locale)
}
// In main():
app_use(app, fn_addr(locale_middleware))Translating strings
fn handle_posts_index(ctx: i64) {
let heading = forge_t(ctx, "nav.posts") // "Posts" or "Articles"
ctx_html(ctx, 200, "<h1>" + heading + "</h1>")
}forge_t(ctx, key) returns the translation for the locale set on ctx. If the key is not found in the current locale, it falls back to "en". If still not found, it returns the key itself.
Setting translation values programmatically
forge_i18n_set("en", "greeting", "Hello!")
forge_i18n_set("fr", "greeting", "Bonjour!")Switching locale via URL parameter
fn set_locale_handler(ctx: i64) {
let locale = ctx_query(ctx, "locale")
// Whitelist allowed locales
let allowed = false
if forge_slice_eq(locale, "en") or forge_slice_eq(locale, "fr") { allowed = true }
if allowed {
ctx_set_cookie(ctx, "locale", locale, 31536000) // 1 year
}
ctx_redirect(ctx, ctx_query(ctx, "return_to"))
}Using translations in views
fn posts_new_page(ctx: i64) -> []i8 {
let token = forge_csrf_token(ctx)
ret layout(ctx, forge_t(ctx, "post.new_title"),
forge_form_tag_open("/posts", "POST", token) +
forge_label_tag("title", forge_t(ctx, "post.title_label")) +
forge_input_tag("text", "title", "") +
forge_submit_tag(forge_t(ctx, "post.submit")) +
forge_form_tag_close())
}API reference
| Function | Description |
|---|---|
forge_i18n_load(locale, path) | Load a locale file |
forge_i18n_set(locale, key, val) | Set a single translation |
forge_t(ctx, key) | Get translation for ctx’s locale |
forge_locale_set_ctx(ctx, locale) | Set locale on a request context |
Limits
| Limit | Value |
|---|---|
| Maximum entries per locale | 512 (FORGE_I18N_MAX_ENTRIES) |
| Maximum key length | 63 characters |
| Maximum value length | 255 characters |