---
title: "i18n"
description: "The player's language, and which of your world's languages to show them. i18n.pick({\"en\", \"pt\", \"es\"})…"
section: "API Reference"
slug: "api-i18n"
canonical: "https://origozero.ai/docs/api-i18n"
updated: "2026-10-16T20:06:46.844187671+00:00"
tags: ["api", "reference"]
---

# i18n

The player's language, and which of your world's languages to show them. `i18n.pick({"en", "pt", "es"})` answers which of the languages your content has text for this player reads; `i18n.preferred()` is the languages their platform says they read (OS, browser or Android settings), most preferred first; `i18n.onChange` reports every change while they play; `i18n.emulate(tags)` stands a visitor's language list in to try content under it. `i18n.locale()` is the locale the engine's own text is in, one of `i18n.offered()`; `i18n.choose(tag)` is the player's own choice, kept for their account. The same surface in every VM. The engine picks the locale from one chain: the player's choice, then the language ZeroMind shows them, then the platform's list, then `en`, each matched against the offered locales (same tag, then the tag truncated, then the same language). `i18n.sources()` shows every link and which one decided.

```lua
require("@builtin::modules.api.engine.i18n") -- i18n (also available as global 'i18n')
```

The `i18n` namespace: 33 functions.

## globals/i18n/choice {#globals-i18n-choice}

```lua
i18n.choice() -> string?
```

The locale the player chose for themselves, or nil when they chose
none and the chain decides.

**Returns** `string?` — The player's choice.

## globals/i18n/choose {#globals-i18n-choose}

```lua
i18n.choose(tag: string?) -> ChooseResult
```

Choose `tag` as the player's locale, kept on this device for their
ZeroMind account (or for the signed-out player), so it wins the chain
from now on and on every later boot. `nil` clears the choice and the
chain decides again. A tag no offered locale matches is refused, naming
the offered tags.

**Parameters**

- `tag` `string` _(optional)_ — A tag from `i18n.offered()`, or nil to clear the choice.

**Returns** `ChooseResult` — `{ ok, locale?, decidedBy?, reason?, offered? }`.

```lua
local r = i18n.choose("pt-BR")
if not r.ok then log.warn(r.reason) end
```

## globals/i18n/emulate {#globals-i18n-emulate}

```lua
i18n.emulate(tags: { string }?) -> { ok: boolean, locale: string?, platform: { string }?, reason: string? }
```

Stand `tags` in for the platform's preferred languages, the way a
visitor whose device or browser reads them would arrive, and negotiate
again: `i18n.preferred()`, `i18n.pick`, `i18n.locale()` and
`i18n.onChange` all answer as they would for that visitor. `nil` reads
the platform's own list again. A report the platform makes afterwards
(a language change, a desktop window regaining focus) replaces it. The
player's own choice (`i18n.choose`) still comes first in the chain, so
clear it with `i18n.choose(nil)` to see the list decide.

**Parameters**

- `tags` `{ string }` _(optional)_ — BCP 47 tags, most preferred first, or nil for the platform's own list.

**Returns** `{ ok: boolean, locale: string?, platform: { string }?, reason: string? }` — `{ ok, locale?, platform?, reason? }`: the locale and list now in force.

```lua
i18n.emulate({ "pt-BR", "en" })   -- try the world as a Brazilian visitor
i18n.emulate(nil)                  -- back to this machine's own languages
```

## globals/i18n/fallback {#globals-i18n-fallback}

```lua
i18n.fallback() -> { string }
```

The chosen locale's fallback chain: the locales a string missing
from its catalog is looked up in, in order.

**Returns** `{ string }` — The fallback locales, `en` last; empty for `en` itself.

## globals/i18n/locale {#globals-i18n-locale}

```lua
i18n.locale() -> string
```

The locale the engine chose for the player for its own text, a BCP
47 tag such as `en` or `pt-BR`. It is always one of `i18n.offered()`;
`en`, the engine's source language, until a negotiation states another.
To choose among your own content's languages, use `i18n.pick`.

**Returns** `string` — The chosen locale.

```lua
if i18n.locale() == "pt-BR" then label.text = "Olá" end
```

## globals/i18n/offChange {#globals-i18n-offchange}

```lua
i18n.offChange(id: number) -> boolean
```

Remove an `i18n.onChange` subscriber by its watcher id. Returns true
when a live watcher carried that id (false for one already removed, or
ended with the component that made it).

**Parameters**

- `id` `number` — Watcher id returned by `i18n.onChange`.

**Returns** `boolean`

## globals/i18n/offered {#globals-i18n-offered}

```lua
i18n.offered() -> { Locale }
```

The locales the engine offers for its own text, sorted by tag. Only
an offered locale can be chosen or negotiated. Content picks among its
own languages with `i18n.pick`.

**Returns** `{ Locale }` — One `{ tag, name, nativeName, fallback }` per offered locale.

## globals/i18n/onChange {#globals-i18n-onchange}

```lua
i18n.onChange(callback: (string, { string }) -> ()) -> number
```

Run `callback` each time the player's language changes: the
platform's preferred list changes (the player changed their OS, browser
or device language while the engine runs, or `i18n.emulate` stood a list
in), the player chooses or clears a choice, the ZeroMind language moves,
or the offered locales change. Called as `(locale, preferred)`, after the
engine renegotiated, so `i18n.pick` inside it answers for the new state.
A change `i18n.emulate` or `i18n.choose` makes reaches this VM's
subscribers before that call returns; one the platform reports reaches
them within a tenth of a second.
A subscription made from a component ends with that component. Returns
a watcher id for `i18n.offChange`.

**Parameters**

- `callback` `(string, { string }) -> ()` — Function invoked as `(locale: string, preferred: {string})`.

**Returns** `number` — The watcher id.

```lua
local id = i18n.onChange(function(locale, preferred)
log.info(("locale %s, platform %s"):format(locale, table.concat(preferred, ",")))
end)
```

## globals/i18n/pick {#globals-i18n-pick}

```lua
i18n.pick(available: { string }) -> string
```

Which of `available`, the languages your content has text for,
this player reads: the tag the engine's chain picks from the player's
own choice, the language ZeroMind shows them and their platform's list,
matched by the same rules (same tag, the tag truncated, the same
language). The first of `available` when none of them matches, so list
your fallback first. Call it again (or from `i18n.onChange`) to follow a
change while they play.

**Parameters**

- `available` `{ string }` — BCP 47 tags your content has text for, fallback first.

**Returns** `string` — One of `available`, exactly as written there.

```lua
local lang = i18n.pick({ "en", "pt", "es" })
label.text = ({ en = "Welcome!", pt = "Bem-vindo!", es = "¡Bienvenido!" })[lang]
```

## globals/i18n/preferred {#globals-i18n-preferred}

```lua
i18n.preferred() -> { string }
```

The languages the player's platform says they read, as BCP 47 tags,
most preferred first: the OS language list on Linux, Windows and macOS,
`navigator.languages` in a browser, the device (or per-app) language list
on Android. Empty when the platform states none.

**Returns** `{ string }` — The platform's preferred languages.

```lua
local first = i18n.preferred()[1]
```

## globals/i18n/sources {#globals-i18n-sources}

```lua
i18n.sources() -> Sources
```

Every link of the chain the locale was chosen from: the player's
`choice`, the `zeromind` language, the `platform` list, and which link
decided (`decidedBy`: `choice`, `zeromind`, `platform` or `default`)
with the tag it matched.

**Returns** `Sources` — `{ locale, decidedBy, matched, choice?, zeromind?, platform, signedIn }`.

## modules/i18n/choice {#modules-i18n-choice}

```lua
choice(): string?
```

The locale the player chose for themselves, or nil when they chose
none and the chain decides.

## modules/i18n/choose {#modules-i18n-choose}

```lua
choose(tag: string?): ChooseResult
```

Choose `tag` as the player's locale, kept on this device for their
ZeroMind account (or for the signed-out player), so it wins the chain
from now on and on every later boot. `nil` clears the choice and the
chain decides again. A tag no offered locale matches is refused, naming
the offered tags.

**Parameters**

- `tag` `string?` _(optional)_ — A tag from `i18n.offered()`, or nil to clear the choice.

```lua
local r = i18n.choose("pt-BR")
if not r.ok then log.warn(r.reason) end
```

## modules/i18n/emulate {#modules-i18n-emulate}

```lua
emulate(tags: { string }?): { ok: boolean, locale: string?, platform: { string }?, reason: string? }
```

Stand `tags` in for the platform's preferred languages, the way a
visitor whose device or browser reads them would arrive, and negotiate
again: `i18n.preferred()`, `i18n.pick`, `i18n.locale()` and
`i18n.onChange` all answer as they would for that visitor. `nil` reads
the platform's own list again. A report the platform makes afterwards
(a language change, a desktop window regaining focus) replaces it. The
player's own choice (`i18n.choose`) still comes first in the chain, so
clear it with `i18n.choose(nil)` to see the list decide.

**Parameters**

- `tags` `{ string }?` _(optional)_ — BCP 47 tags, most preferred first, or nil for the platform's own list.

```lua
i18n.emulate({ "pt-BR", "en" })   -- try the world as a Brazilian visitor
i18n.emulate(nil)                  -- back to this machine's own languages
```

## modules/i18n/fallback {#modules-i18n-fallback}

```lua
fallback(): { string }
```

The chosen locale's fallback chain: the locales a string missing
from its catalog is looked up in, in order.

## modules/i18n/locale {#modules-i18n-locale}

```lua
locale(): string
```

The locale the engine chose for the player for its own text, a BCP
47 tag such as `en` or `pt-BR`. It is always one of `i18n.offered()`;
`en`, the engine's source language, until a negotiation states another.
To choose among your own content's languages, use `i18n.pick`.

```lua
if i18n.locale() == "pt-BR" then label.text = "Olá" end
```

## modules/i18n/offChange {#modules-i18n-offchange}

```lua
offChange(id: number): boolean
```

Remove an `i18n.onChange` subscriber by its watcher id. Returns true
when a live watcher carried that id (false for one already removed, or
ended with the component that made it).

**Parameters**

- `id` `number` — Watcher id returned by `i18n.onChange`.

## modules/i18n/offered {#modules-i18n-offered}

```lua
offered(): { Locale }
```

The locales the engine offers for its own text, sorted by tag. Only
an offered locale can be chosen or negotiated. Content picks among its
own languages with `i18n.pick`.

## modules/i18n/onChange {#modules-i18n-onchange}

```lua
onChange(callback: (string, { string }) -> ()): number
```

Run `callback` each time the player's language changes: the
platform's preferred list changes (the player changed their OS, browser
or device language while the engine runs, or `i18n.emulate` stood a list
in), the player chooses or clears a choice, the ZeroMind language moves,
or the offered locales change. Called as `(locale, preferred)`, after the
engine renegotiated, so `i18n.pick` inside it answers for the new state.
A change `i18n.emulate` or `i18n.choose` makes reaches this VM's
subscribers before that call returns; one the platform reports reaches
them within a tenth of a second.
A subscription made from a component ends with that component. Returns
a watcher id for `i18n.offChange`.

**Parameters**

- `callback` `(string, { string }) -> ()` — Function invoked as `(locale: string, preferred: {string})`.

```lua
local id = i18n.onChange(function(locale, preferred)
log.info(("locale %s, platform %s"):format(locale, table.concat(preferred, ",")))
end)
```

## modules/i18n/pick {#modules-i18n-pick}

```lua
pick(available: { string }): string
```

Which of `available`, the languages your content has text for,
this player reads: the tag the engine's chain picks from the player's
own choice, the language ZeroMind shows them and their platform's list,
matched by the same rules (same tag, the tag truncated, the same
language). The first of `available` when none of them matches, so list
your fallback first. Call it again (or from `i18n.onChange`) to follow a
change while they play.

**Parameters**

- `available` `{ string }` — BCP 47 tags your content has text for, fallback first.

```lua
local lang = i18n.pick({ "en", "pt", "es" })
label.text = ({ en = "Welcome!", pt = "Bem-vindo!", es = "¡Bienvenido!" })[lang]
```

## modules/i18n/preferred {#modules-i18n-preferred}

```lua
preferred(): { string }
```

The languages the player's platform says they read, as BCP 47 tags,
most preferred first: the OS language list on Linux, Windows and macOS,
`navigator.languages` in a browser, the device (or per-app) language list
on Android. Empty when the platform states none.

```lua
local first = i18n.preferred()[1]
```

## modules/i18n/sources {#modules-i18n-sources}

```lua
sources(): Sources
```

Every link of the chain the locale was chosen from: the player's
`choice`, the `zeromind` language, the `platform` list, and which link
decided (`decidedBy`: `choice`, `zeromind`, `platform` or `default`)
with the tag it matched.

## typed/builtin//modules/api/engine/i18n/i18n/choice {#typed-builtin-modules-api-engine-i18n-i18n-choice}

```lua
i18n.choice() -> string?
```

The locale the player chose for themselves, or nil when they chose
none and the chain decides.

**Returns** `string?` — The player's choice.

## typed/builtin//modules/api/engine/i18n/i18n/choose {#typed-builtin-modules-api-engine-i18n-i18n-choose}

```lua
i18n.choose(tag: string?) -> ChooseResult
```

Choose `tag` as the player's locale, kept on this device for their
ZeroMind account (or for the signed-out player), so it wins the chain
from now on and on every later boot. `nil` clears the choice and the
chain decides again. A tag no offered locale matches is refused, naming
the offered tags.

**Parameters**

- `tag` `string` _(optional)_ — A tag from `i18n.offered()`, or nil to clear the choice.

**Returns** `ChooseResult` — `{ ok, locale?, decidedBy?, reason?, offered? }`.

```lua
local r = i18n.choose("pt-BR")
if not r.ok then log.warn(r.reason) end
```

## typed/builtin//modules/api/engine/i18n/i18n/emulate {#typed-builtin-modules-api-engine-i18n-i18n-emulate}

```lua
i18n.emulate(tags: { string }?) -> { ok: boolean, locale: string?, platform: { string }?, reason: string? }
```

Stand `tags` in for the platform's preferred languages, the way a
visitor whose device or browser reads them would arrive, and negotiate
again: `i18n.preferred()`, `i18n.pick`, `i18n.locale()` and
`i18n.onChange` all answer as they would for that visitor. `nil` reads
the platform's own list again. A report the platform makes afterwards
(a language change, a desktop window regaining focus) replaces it. The
player's own choice (`i18n.choose`) still comes first in the chain, so
clear it with `i18n.choose(nil)` to see the list decide.

**Parameters**

- `tags` `{ string }` _(optional)_ — BCP 47 tags, most preferred first, or nil for the platform's own list.

**Returns** `{ ok: boolean, locale: string?, platform: { string }?, reason: string? }` — `{ ok, locale?, platform?, reason? }`: the locale and list now in force.

```lua
i18n.emulate({ "pt-BR", "en" })   -- try the world as a Brazilian visitor
i18n.emulate(nil)                  -- back to this machine's own languages
```

## typed/builtin//modules/api/engine/i18n/i18n/fallback {#typed-builtin-modules-api-engine-i18n-i18n-fallback}

```lua
i18n.fallback() -> { string }
```

The chosen locale's fallback chain: the locales a string missing
from its catalog is looked up in, in order.

**Returns** `{ string }` — The fallback locales, `en` last; empty for `en` itself.

## typed/builtin//modules/api/engine/i18n/i18n/locale {#typed-builtin-modules-api-engine-i18n-i18n-locale}

```lua
i18n.locale() -> string
```

The locale the engine chose for the player for its own text, a BCP
47 tag such as `en` or `pt-BR`. It is always one of `i18n.offered()`;
`en`, the engine's source language, until a negotiation states another.
To choose among your own content's languages, use `i18n.pick`.

**Returns** `string` — The chosen locale.

```lua
if i18n.locale() == "pt-BR" then label.text = "Olá" end
```

## typed/builtin//modules/api/engine/i18n/i18n/offChange {#typed-builtin-modules-api-engine-i18n-i18n-offchange}

```lua
i18n.offChange(id: number) -> boolean
```

Remove an `i18n.onChange` subscriber by its watcher id. Returns true
when a live watcher carried that id (false for one already removed, or
ended with the component that made it).

**Parameters**

- `id` `number` — Watcher id returned by `i18n.onChange`.

**Returns** `boolean`

## typed/builtin//modules/api/engine/i18n/i18n/offered {#typed-builtin-modules-api-engine-i18n-i18n-offered}

```lua
i18n.offered() -> { Locale }
```

The locales the engine offers for its own text, sorted by tag. Only
an offered locale can be chosen or negotiated. Content picks among its
own languages with `i18n.pick`.

**Returns** `{ Locale }` — One `{ tag, name, nativeName, fallback }` per offered locale.

## typed/builtin//modules/api/engine/i18n/i18n/onChange {#typed-builtin-modules-api-engine-i18n-i18n-onchange}

```lua
i18n.onChange(callback: (string, { string }) -> ()) -> number
```

Run `callback` each time the player's language changes: the
platform's preferred list changes (the player changed their OS, browser
or device language while the engine runs, or `i18n.emulate` stood a list
in), the player chooses or clears a choice, the ZeroMind language moves,
or the offered locales change. Called as `(locale, preferred)`, after the
engine renegotiated, so `i18n.pick` inside it answers for the new state.
A change `i18n.emulate` or `i18n.choose` makes reaches this VM's
subscribers before that call returns; one the platform reports reaches
them within a tenth of a second.
A subscription made from a component ends with that component. Returns
a watcher id for `i18n.offChange`.

**Parameters**

- `callback` `(string, { string }) -> ()` — Function invoked as `(locale: string, preferred: {string})`.

**Returns** `number` — The watcher id.

```lua
local id = i18n.onChange(function(locale, preferred)
log.info(("locale %s, platform %s"):format(locale, table.concat(preferred, ",")))
end)
```

## typed/builtin//modules/api/engine/i18n/i18n/pick {#typed-builtin-modules-api-engine-i18n-i18n-pick}

```lua
i18n.pick(available: { string }) -> string
```

Which of `available`, the languages your content has text for,
this player reads: the tag the engine's chain picks from the player's
own choice, the language ZeroMind shows them and their platform's list,
matched by the same rules (same tag, the tag truncated, the same
language). The first of `available` when none of them matches, so list
your fallback first. Call it again (or from `i18n.onChange`) to follow a
change while they play.

**Parameters**

- `available` `{ string }` — BCP 47 tags your content has text for, fallback first.

**Returns** `string` — One of `available`, exactly as written there.

```lua
local lang = i18n.pick({ "en", "pt", "es" })
label.text = ({ en = "Welcome!", pt = "Bem-vindo!", es = "¡Bienvenido!" })[lang]
```

## typed/builtin//modules/api/engine/i18n/i18n/preferred {#typed-builtin-modules-api-engine-i18n-i18n-preferred}

```lua
i18n.preferred() -> { string }
```

The languages the player's platform says they read, as BCP 47 tags,
most preferred first: the OS language list on Linux, Windows and macOS,
`navigator.languages` in a browser, the device (or per-app) language list
on Android. Empty when the platform states none.

**Returns** `{ string }` — The platform's preferred languages.

```lua
local first = i18n.preferred()[1]
```

## typed/builtin//modules/api/engine/i18n/i18n/sources {#typed-builtin-modules-api-engine-i18n-i18n-sources}

```lua
i18n.sources() -> Sources
```

Every link of the chain the locale was chosen from: the player's
`choice`, the `zeromind` language, the `platform` list, and which link
decided (`decidedBy`: `choice`, `zeromind`, `platform` or `default`)
with the tag it matched.

**Returns** `Sources` — `{ locale, decidedBy, matched, choice?, zeromind?, platform, signedIn }`.
