Locale detector
With an i18n instance that has two or more locales, as on the Quick start,
read the user’s locale from the browser on startup and persist the choice with
@comvi/plugin-locale-detector. Every source falls through instead of throwing when window
or document is missing, so the same code runs on the server and does nothing there.
Install
Section titled “Install”npm install @comvi/plugin-locale-detectorpnpm add @comvi/plugin-locale-detectoryarn add @comvi/plugin-locale-detectorbun add @comvi/plugin-locale-detectorDetect and persist
Section titled “Detect and persist”import { createI18n } from '@comvi/core';import { localeDetector } from '@comvi/plugin-locale-detector';
export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en' }).use( localeDetector({ supportedLocales: ['en', 'uk', 'de'] }),);
await i18n.init();.use(localeDetector(…)) composes the plugin capability and registers the detector. Every
option has a default. The installer receives the i18n instance and returns it widened with the
plugin members; the plugin it queues runs inside init(), and the detector it registers is
consulted after every plugin has run, so a loader composed after it is already in place. The
detector’s result is applied through setLocale(), which loads the new locale’s namespaces
before the switch takes effect. A detected locale equal to the current one switches nothing. The locale detected
during init() is written to every configured cache once init() has succeeded, never before.
Every later setLocale(), including the one your switcher makes, is written at once.
Always list the locales you ship in supportedLocales. Without it, a visitor whose
browser language you do not ship, ja-JP on an en and uk app, gets ja as the active
locale. With a loader composed, init() then rejects because no ja catalog loads, and it
rejects again on every later visit, since the browser keeps reporting ja; nothing is cached
from a failed init(), and a cached value whose locale fails to load is removed from the caches
that hold it. A development build warns when the plugin starts with neither supportedLocales
nor convertDetectedLocale.
Detection order
Section titled “Detection order”import { localeDetectorPlugin } from '@comvi/plugin-locale-detector';
export const detector = localeDetectorPlugin({ supportedLocales: ['en', 'uk', 'de'], caches: ['localStorage'], cacheFirst: true, order: ['querystring', 'localStorage', 'sessionStorage', 'cookie', 'navigator'],});Detection has two phases. With cacheFirst: true, the first entry in caches is read first
and a valid tag there ends detection; the other cache entries are write-only. When it is
empty, each source in order is tried left to right and the first valid tag wins. Set
cacheFirst: false to let order alone decide priority, for example so a query parameter can
override a persisted choice; caches are still written. A tag is used only if it is at most 35
characters and matches ^[a-z]{2,8}([_-][a-z\d]{1,8})*$ case-insensitively, so a hand-edited
cookie cannot push an arbitrary string into locale.
With supportedLocales, a detected tag is matched with BCP 47 lookup: case-insensitive exact
match, then shorter prefixes (zh-Hant-TW, zh-Hant, zh), then a regional variant of the
base language (de to de-DE). A tag that matches nothing is skipped and the scan continues.
Without supportedLocales or convertDetectedLocale, every detected and cached tag is shortened
to its base language; every visit resolves pt-BR to pt. When
nothing matches, the locale is fallbackLocale, and that result is not cached, so an
unsupported browser language never overwrites a real choice.
Options
Section titled “Options”| Option | Default | Behaviour |
|---|---|---|
order |
['querystring', 'localStorage', 'sessionStorage', 'cookie', 'navigator'] |
Sources, tried left to right. |
caches |
['localStorage'] |
Where a detected or selected locale is written; the first is read on startup. |
cacheFirst |
true |
Read the first cache before scanning order. |
lookupQuerystring |
'lng' |
Query parameter name. |
lookupCookie |
'i18n_lang' |
Cookie name. |
lookupLocalStorage |
'i18n_locale' |
localStorage key. |
lookupSessionStorage |
'i18n_locale' |
sessionStorage key. |
cookieOptions |
{ path: '/', sameSite: 'lax' } |
Also domain and secure. sameSite: 'none' adds Secure automatically. |
cookieMaxAge |
31536000 |
Cookie lifetime in seconds, one year. |
fallbackLocale |
i18n.locale at plugin init |
Used when no source yields a supported tag. |
supportedLocales |
none | Restricts detected tags with BCP 47 lookup. |
convertDetectedLocale |
none | (tag) => string mapping applied to every raw tag. |
Storage access is wrapped, so private mode, blocked cookies and quota errors are ignored and
detection moves to the next source. Only one detector is active on an instance; a later
registration replaces it. The plugin returns a cleanup function, so destroy() unsubscribes
its localeChanged listener.
Common setups
Section titled “Common setups”import { createI18n } from '@comvi/core';import { localeDetector } from '@comvi/plugin-locale-detector';
export const i18n = createI18n({ locale: 'en' }).use( localeDetector({ supportedLocales: ['en', 'uk', 'de'], order: ['querystring', 'localStorage', 'navigator'], caches: ['localStorage'], cacheFirst: false, lookupQuerystring: 'language', }),);
await i18n.init();With ?language=de the query value wins and is persisted to localStorage; without it,
detection continues to localStorage and then navigator.
import { createI18n } from '@comvi/core';import { localeDetector } from '@comvi/plugin-locale-detector';
export const i18n = createI18n({ locale: 'en' }).use( localeDetector({ supportedLocales: ['en', 'uk', 'de'], order: ['cookie', 'navigator'], caches: ['cookie'], lookupCookie: 'comvi_locale', cookieOptions: { sameSite: 'lax' }, }),);
await i18n.init();Persist to a cookie when your server or middleware also needs to read the selected locale. On
the server every source returns nothing and the detector yields fallbackLocale, so make the
server-side decision in your framework’s middleware and share it through the cookie;
@comvi/next and @comvi/nuxt ship their own. Compose the detector on the client only when
the instance lives at module scope on the server: a detector there runs once, at the first
init(), and never again for a later request. See
Render on the server.
Read next
Section titled “Read next”- Switch the locale:
setLocale, persistence and direction. - Render on the server: detection on the server, cookie names per framework.
- Overview: installers, plugin factories and registration options.