Skip to content
jess
api

@jess/l10n

Localized text: bundles of ICU MessageFormat messages, resolved through the locale chain.

require('@jess/l10n'): { bundle, bundles, locales, setLocales, addDirectory, addProvider, providers, reload, generation, formatPattern, new, directory, files, checkPattern }

Text for people comes from here, not from string literals. A script asks for a bundle once and calls it for each message with the values the pattern names; locale fallback, plural rules, and number formatting are the module's. It's Luau in the built-in package, over @dream/intl's CLDR plural rules and decimal formatting. The model, the file layout, and the grammar are in Localization.

--!strict
local l10n = require('@jess/l10n')
l10n.addDirectory('my-package/l10n')
l10n.reload()
l10n.setLocales { 'pl-PL', 'en' }

local common = l10n.bundle 'common'
print(common('hello', { name = 'Ada' })) -- Cześć, Ada!
print(common('file_count', { count = 5 })) -- 5 plików

l10n.bundle§

l10n.bundle(name: string, fallback: string?) -> l10n.Bundle

The bundle named name, declared now if no provider offered it. fallback (default en) is the locale tried after every preferred one and its parents; passing it again changes it, and raises the generation.

  • Parameters: name: segments of letters, digits, _, and -, joined by . (common,

jess.editor). fallback: a BCP 47 tag.

  • Errors: `'bad name' isn't a bundle name: use letters, digits, '_', and '-', in segments

separated by '.'; 'english' isn't a BCP 47 locale: the given language subtag is invalid`.

l10n.bundles§

l10n.bundles() -> { string }

Every bundle: the ones providers offered at the last reload and the ones scripts asked for.

l10n.locales§

l10n.locales() -> { string }

The preferred locales, canonically spelled, as they were set. Empty until setLocales runs: then only each bundle's fallback locale is used.

l10n.setLocales§

l10n.setLocales(locales: string | { string }) -> number

Sets the preferred locales, most wanted first, and returns the new generation. A message is looked up in each locale and then its parents (pt-BR, then pt) before the next locale, then in the bundle's fallback. Every bundle resolves again; nothing recompiles. Tags are canonicalized (pt_br is pt-BR); extensions and private-use subtags are refused.

  • Errors: 'english' isn't a BCP 47 locale: ...; `locales must be a list of strings, not

number`.

--!strict
local l10n = require('@jess/l10n')
local common = l10n.bundle 'common'
l10n.setLocales 'de-AT'
print(common('hello', { name = 'Ada' })) -- Hallo, Ada! (from de.toml)

l10n.addDirectory§

l10n.addDirectory(path: string, name: string?) -> ()

Adds l10n.directory(path, name) as a provider: a folder of <bundle>/<locale>.toml files, named name (default: the path) in diagnostics and provenance. Nothing is read until the next reload.

l10n.addProvider§

l10n.addProvider(provider: l10n.Provider) -> ()

Adds a provider. Providers added later take precedence: where two define the same message of the same bundle and locale, the later one's text wins, message by message. Nothing is read until the next reload.

l10n.providers§

l10n.providers() -> { string }

The providers' names, lowest precedence first.

l10n.reload§

l10n.reload() -> ({ l10n.Diagnostic }, l10n.Report)

Reads every provider again, compiles every pattern of every locale, and swaps in each bundle that loaded without an error. A bundle with an error keeps its last good generation, whole, so a broken edit never blanks the application; a bundle with none yet takes what did compile. A provider that can't list its files stops the reload: nothing changes. Returns what was wrong (an empty list is a clean reload) and a report.

--!strict
local l10n = require('@jess/l10n')
for _, problem in l10n.reload() do
  print(tostring(problem))
end

l10n.Diagnostic§

type l10n.Diagnostic = { severity: 'info' | 'warning' | 'error', kind: string, detail: string, generation: number, provider: string?, path: string?, line: number?, column: number?, bundle: string?, locale: string?, message: string? }

One problem. kind is a code: pattern-parse-failure, bundle-parse-failure, duplicate-message, provider-collision, invalid-locale, invalid-bundle-name, provider-failure, empty-bundle (a warning: a bundle nobody has files for), and, from tryFormat, missing-message, missing-argument, and unsupported-argument-type. line and column point at the message id, or, for a pattern that doesn't compile, at the place in the pattern. tostring gives one line a log can print:

error[pattern-parse-failure] common/en.toml:1:40 (provider inline): common/en 'broken': at offset 29: 'othr' isn't a plural case; use =n, zero, one, two, few, many, or other

l10n.Report§

type l10n.Report = { generation: number, updated: { string }, kept: { string } }

The generation after the reload, the bundles swapped in, and the bundles that kept their last good generation.

l10n.generation§

l10n.generation() -> number

The generation: it rises whenever a reload swaps a bundle in, the preferred locales change, or a bundle's fallback does, which is whenever text a script cached may be stale.

l10n.formatPattern§

l10n.formatPattern(locale: string, pattern: string, values: l10n.Values?) -> string

Compiles and formats a pattern that isn't in any bundle, with locale's rules: for tools and for text that arrives at runtime. It compiles on every call.

  • Errors: the pattern doesn't compile: at offset <n>: <why>, and the argument errors of

calling a bundle.

l10n.new§

l10n.new() -> l10n.Registry

A registry of its own, apart from the runtime's: bundle, bundles, locales, setLocales, addDirectory, addProvider, providers, reload, generation, and formatPattern, as methods.

--!strict
local l10n = require('@jess/l10n')
local polish = l10n.new()
local pattern = '{count, plural, one {# plik} few {# pliki} many {# plików} other {# pliku}}'
print(polish:formatPattern('pl', pattern, { count = 22 })) -- 22 pliki

l10n.directory§

l10n.directory(root: string, name: string?) -> l10n.Provider

A provider over a folder on the host, <root>/<bundle>/<locale>.toml, read through @dream/fs. It reads only under root.

l10n.files§

l10n.files(name: string, contents: { [string]: string }, prefix: string?) -> l10n.Provider

A provider over a table of /-separated paths to text, under prefix (l10n, say; omit it for the top): the shape of a virtual filesystem, or of bundles a package carries as strings. The table is read at every reload, so changing it and reloading takes the change.

l10n.Provider§

type l10n.Provider = { read name: string, read resources: () -> ({ l10n.Resource }?, string?, string?), read read: (resource: l10n.Resource) -> (string?, string?) }

Anything that lists and reads bundle files. resources answers the files it offers, or nil, why, and the path that failed; read answers one file's text, or nil and why. A l10n.Resource is { bundle, locale, path }, the locale as the file spells it.

l10n.checkPattern§

l10n.checkPattern(pattern: string) -> ({ { name: string, kind: 'any' | 'number' | 'select' } }?, string?)

The arguments a pattern names and how it uses each, or nil and why it doesn't compile: for a tool that checks translations before anything loads them.

l10n.Bundle§

A bundle handle. Call it, or call format on it; tostring(bundle) is L10nBundle(common).

bundle(id, values)§

bundle(id: string, values: l10n.Values?) -> string

The message id in the first locale of the chain that defines it, with values filled in. The plural and number rules are those of the locale the text came from.

  • Parameters: values: a table of the names the pattern uses to strings, numbers, or

booleans ({ count = 3, name = 'Ada' }). A plural or number argument needs a number; a select needs a string or boolean; a plain {name} takes any of the three.

  • Errors: bundle 'common' has no message 'missing' in de-AT, de, en; `bundle 'common',

message 'hello' (de): argument 'name' is missing; ... argument 'count' must be a number, not a string; variable 'name' of message 'hello' in bundle 'common' must be a string, number, or boolean, not table`. A missing message is never an empty string and never the id passed off as text.

--!strict
local l10n = require('@jess/l10n')
local common = l10n.bundle 'common'
print(common('file_count', { count = 0 })) -- No files
print(common('file_count', { count = 1 })) -- 1 file
print(common('file_count', { count = 1234 })) -- 1,234 files
print(common('mode_label', { mode = 'edit' })) -- Editing

bundle:format§

bundle:format(id: string, values: l10n.Values?) -> string

The same as calling the bundle, for code that prefers a method.

bundle:tryFormat§

bundle:tryFormat(id: string, values: l10n.Values?) -> (string?, l10n.Diagnostic?)

As format, answering nil and the diagnostic instead of raising.

bundle:has§

bundle:has(id: string) -> boolean

Whether some locale in the chain defines id. Formatting a message that isn't there is an error; this is the question to ask first when a message is optional.

bundle:literal§

bundle:literal(id: string) -> string?

The text of id when the chain resolves it to a pattern with no arguments, which needs no formatting; nil otherwise.

bundle:placeholder§

bundle:placeholder(id: string) -> string

[[common:missing]]: what to show in place of a message that can't be formatted, loud on purpose. The error is still the caller's to report.

bundle:lookup§

bundle:lookup(id: string, locale: string?) -> l10n.MessageInfo?

Where id's text comes from: through the chain, or, with locale, in exactly that locale with no fallback. Nil when nothing defines it there. An l10n.MessageInfo is { locale, fallback, provider, path, line, column, arguments }; fallback says a locale other than the first preferred one supplied the text, and arguments are what checkPattern gives.

bundle:locales, bundle:messages, bundle:chain, bundle:fallback, bundle:generation§

The locales the bundle has files for; every message name it knows (defined or asked for), sorted; the locales a message is looked up in, in order; its fallback locale; and the generation it was last swapped in at.

bundle.name§

bundle.name: string

The bundle's name.

l10n.Values§

type l10n.Values = { [string]: string | number | boolean }

The values a message takes, by the names its pattern uses. Tables, functions, and userdata are errors naming the variable, the message, and the bundle: localization formats semantic values, so a script reduces an object to one first.