Skip to content

I18n

Animated GIF showing Glyph localized text, labels, placeholders, and cache-aware values.

Glyph does not own translation files, plural rules, locale fallback, number formatting, or date formatting. Bring a library such as smiti18n, or any callable translator, and connect it through ui.i18n.

local smiti18n = require("smiti18n")

ui.i18n.configure({
  translate = function(key, params, opts)
    local nextParams = {}
    for name, value in pairs(params or {}) do
      nextParams[name] = value
    end
    if opts and opts.fallback then
      nextParams.default = opts.fallback
    end
    return smiti18n(key, nextParams)
  end,
  setLocale = function(locale)
    smiti18n.setLocale(locale)
  end,
  getLocale = function()
    return smiti18n.getLocale()
  end,
})

Translate

Use ui.t or ui.i18n.t when you want the translated string directly:

ui.text(ui.t("menu.title"))
ui.text(ui.t("messages", { count = count }, { cacheKey = "messages:" .. count }))

Missing translations fall back to the key. Pass fallback, or configure missing, to customize that behavior:

ui.i18n.configure({
  translate = translate,
  missing = function(key, params, opts)
    return opts and opts.fallback or key
  end,
})

Keyed Props

Most text-bearing primitives accept translation keys:

ui.textKey("hud.ready")
ui.button({ labelKey = "actions.confirm" })
ui.input({ value = query, placeholderKey = "search.placeholder" })
ui.panel({ titleKey = "inventory.title" }, children)
ui.meter({ value = hp, max = maxHp, labelKey = "stats.hp" })
ui.tabs({}, {
  { labelKey = "tabs.map", content = MapPanel() },
  { labelKey = "tabs.log", content = LogPanel() },
})

Each keyed prop also accepts params, fallback, and cache key fields:

ui.textKey("messages", {
  textParams = { count = count },
  textFallback = "No messages",
  textCacheKey = "messages:" .. tostring(count),
})

Accessibility semantics use the same pattern, so labels and spoken values stay localized without hard-coding strings in adapters:

ui.button({
  labelKey = "actions.launch",
  accessibilityLabelKey = "a11y.actions.launch",
  accessibilityDescriptionKey = "a11y.actions.launch_help",
})

Available semantic prefixes are accessibilityLabel, accessibilityDescription, and accessibilityValueText, each with Params, Fallback, and CacheKey fields.

Locale Changes

Call ui.i18n.setLocale(locale) when the active locale changes. Glyph calls the configured backend setLocale, clears its translation cache, bumps ui.i18n.version(), and marks the runtime dirty.

ui.button({
  label = "ES",
  onClick = function()
    ui.i18n.setLocale("es")
  end,
})

Use the version in memo dependencies for translated static subtrees:

local rows = ui.memo(buildRows, { ui.i18n.version(), dataVersion })

examples/i18n is a responsive game-console style demo with localized command buttons, status panels, meters, tabs, and a side-by-side comparison of memoized and unmemoized translated rows. Press the Ping UI button: the large App renders and Live builds counters keep rising, Memo builds stays stable, and Saved rebuilds shows the avoided subtree work until the locale or message count changes.

Cache Rules

Glyph caches only translations that are safe to reuse:

  • no params: cached by locale, key, fallback, and i18n version
  • params with cacheKey: cached by locale, key, fallback, cache key, and i18n version
  • params without cacheKey: translated fresh each time

This keeps mutable param tables from producing stale strings while still making stable labels cheap.