Troubleshooting
Common symptoms, what causes them, and how to fix them.
On this page
A “no text for "en"” error is thrown
Cause
A message with no text for that locale got past the types and was read. It happens to a message written in JavaScript, forced through with as, or written before Register was merged.
Fix
The error names the missing locale and the ones the message has. Add the missing text. With Register merged, the same gap is a type error at the declaration.
A missing locale is not a type error
Cause
Register does not carry the locales’ type. Until it does, RegisteredLocale is string, and any set of locales passes.
Fix
Write Register with declare module '@k8ordo/i18n' in the module that defines the set, and check that the module is part of the TypeScript project.
Some text does not change with the language
Cause
The message is called at the top of a module and kept as a string. A message reads the locale at the moment it is called, so the string stays in whatever locale was current when the module loaded.
Fix
Keep the Message itself, and call it in the component that renders it.
A date differs between the server and the browser
Cause
A new Intl.DateTimeFormat without a timeZone writes in the runtime’s own zone. When the server’s zone and the browser’s differ, the date or time shifts, and hydration disagrees.
Fix
Write it with locales.dateTimeFormat, which uses the locale’s timeZone, so both sides agree. Anything that should follow the visitor’s own zone belongs in a part that renders in the browser only.
Pages render in the default locale
Cause
The [locale] layout does not export paramsSchema. The server reads only a locale a schema accepted, so even /en/… renders in the default, and /fr/…, outside the list, is no 404.
Fix
Write export const { paramsSchema } = locales in a Server Component layout. Exported from a 'use client' module, it does not reach the framework as a schema.
A “no AsyncLocalStorage to scope a locale to” error is thrown
Cause
The server runtime cannot hand out AsyncLocalStorage through process.getBuiltinModule. With nowhere to tie an accepted locale to the render, it throws rather than render in the default.
Fix
Run on a runtime with process.getBuiltinModule('node:async_hooks'). Node 24, which @k8ordo/static and @k8ordo/server require, has it.
An “in the browser the URL is the locale” error is thrown
Cause
locales.run was called in a browser. A test environment that defines document, such as jsdom or happy-dom, counts as one.
Fix
In a browser, change the locale by navigating to that locale’s URL. In a test, change the pathname with history.replaceState.
Only @k8ordo/ui’s components speak English
Cause
The module that defines the set is not loaded in the browser. Where no set is defined, @k8ordo/ui speaks English, which also disagrees with the server’s HTML.
Fix
Import locales from a Client Component, as the bindParams links or the language switcher do.
A “@k8ordo/ui: no built-in text for "fr"” error is thrown
Cause
@k8ordo/ui ships text for ja and en only, and throws when it renders in a locale nothing registered. A regional tag such as fr-CA without a registration of its own looks for its language, fr.
Fix
Call registerMessages('fr', fr) from @k8ordo/ui/i18n in the module that defines the set.
The static build stops with “static build needs pathnames for”
Cause
A pattern has a parameter besides the locale, such as /:locale/blog/:slug. locales.paths expands only the locale, so :slug is left.
Fix
In the function passed as paths, expand what is left in locales.paths’ result. “Static builds” shows how.
The switcher’s URL has two locales in it
Cause
A pathname still holding its locale segment went to localize, which does not check for one.
Fix
Take the segment off with delocalize first, and hand its pathname to localize.
The 404 page stays in the default language
Cause
The 404.html a static host serves is rendered once at build time, in the default locale, and text a Server Component rendered stays that way.
Fix
Make not-found.tsx a Client Component that renders the text. When the browser renders it afresh at the visitor’s URL, it comes out in their locale.
Passing a message to a Client Component fails
Cause
A message is a function, and React cannot send a function to a Client Component as a prop.
Fix
Pass the string you get by calling it, or import the message in the Client Component and call it there.
Form errors are not in the page’s language
Cause
zod was given the string a message returned, or formFields is called at the top of a module. Either way the string is fixed in the locale current at that moment.
Fix
Give zod the message itself, as in { error: m.talk.titleRequired }, and call formFields inside the page’s render.