GioJSdocs
On this page

[i18n]

Locale routing: the locales a site serves, the default one, and the order the URL prefix, Accept-Language and the gio_locale cookie are read in.

gio.toml
[i18n]
locales = ["en", "de", "fr"]
default_locale = "en"

With no locales, i18n is off and costs nothing. In components, useLocale() returns the request's locale and <LocaleLink> prefixes links with it. See Internationalization.

Reference

KeyDefaultDescription
localesstring[][]The locales the site serves, as they appear in URLs (/de/about). An empty entry, or one listed twice (ignoring case), stops startup.0 / false / empty: Empty: i18n is off
default_localestring"en"The locale when nothing else decides. Pages in it are served without a prefix, and <html lang> is left as the root layout renders it. With locales set it must be one of them, spelled the same way, or startup stops.
detect_fromstring[]["path", "accept-language", "cookie"]Where the locale comes from, tried in this order: "path" (the first URL segment), "accept-language" (the header), "cookie" (gio_locale). The first that names a configured locale wins. Any other value stops startup, with the closest valid one.

Behavior

  • A leading locale segment is always removed before routing, whether or not "path" is in detect_from: /de/about renders app/about/page.tsx and /de renders the home page. Guards, redirects, rewrites, header rules and rate limits match that path too, so a guard on /admin/*rest also protects /de/admin.
  • Accept-Language entries are tried by quality value, highest first (the browser's order breaks ties); q=0 or a malformed q rules an entry out. An entry matches a locale exactly, ignoring case (pt-br picks pt-BR), or by its language (de-AT picks de). The result is always spelled as in locales.
  • The gio_locale cookie must hold one of the configured locales. GioJS reads it and never sets it; your app sets it, for example from a language switcher.
  • When the locale is not the default, the server writes it into <html lang>, replacing the root layout's lang. HTML files in public/ are served as written, in every locale.
  • <LocaleLink> leaves default_locale unprefixed. The server passes this section to the worker (GIO_I18N_CONFIG), so changing it changes the deployment id.
  • Cached pages are stored per locale. While detect_from reads a header or the cookie, a page requested without a locale prefix is sent Cache-Control: private, no-cache without an ETag: one URL serves several languages there, and shared caches key by URL. Prefixed URLs stay shareable while "path" comes first; when detect_from lists a header or the cookie before it, the header can override a prefix, so prefixed URLs are private too.

No key in this section logs a warning. Startup logs the locales when i18n is on.

Startup errors

Startup and --check-config refuse a section that cannot work as written:

text
gio.toml:3: invalid `i18n.detect_from`: unknown variant `acept-language`, expected one of `path`, `accept-language`, `cookie` - did you mean "accept-language"?
gio.toml:2: invalid `i18n.default_locale`: "en" is not one of locales ("de", "fr")
gio.toml:2: invalid `i18n.locales`: "EN" is listed twice (as "en" before)

A default_locale left at its default ("en") is reported on the locales line: set it to one of your locales.

Examples

URL prefixes only

Every language has its own URLs, which CDNs can cache:

gio.toml
[i18n]
locales = ["en", "de"]
default_locale = "en"
detect_from = ["path"]

A remembered choice wins over the browser

gio.toml
[i18n]
locales = ["en", "de", "fr"]
detect_from = ["path", "cookie", "accept-language"]

Good to know

  • The URL prefix and the cookie match locales exactly: /pt-br/ is not the pt-BR locale. Only Accept-Language ignores case.
  • revalidatePath and POST /_gio/revalidate drop a leading locale from a path, so a purge reaches every language of the page.
  • The section is part of the deployment id: changing it drops persisted pages.

Version history

VersionChanges
v0.1.0-beta.8An unknown detect_from value, a default_locale outside a non-empty locales, and an empty or duplicate locale stop startup (they were ignored). Pages whose locale was negotiated from request headers are never public and get no ETag. useLocale() returns the request locale during server rendering too. Accept-Language is read by quality value and gives the locale as spelled in locales (an exact match was lowercased). <html lang> replaces the root layout's lang instead of adding a second one. <LocaleLink> defaults to default_locale. A prefixed URL is private when detect_from tries a header before path. public/ HTML files keep their own lang (it was rewritten under the file's old length, cutting the body short).
v0.1.0-beta.1Introduced with locales, default_locale and detect_from.