Static builds
@k8ordo/static has to know, at build time, the pathnames of every page with a parameter. The locale segment takes the same values on every page, so the locale set makes that list itself. This page covers passing locales.paths, pages with another parameter, and the language of the 404 page.
On this page
Write a page per locale
Pass locales.paths as framework’s paths option. Every pattern with a /:locale segment becomes one pathname per locale.
vite.config.tsimport { framework } from '@k8ordo/static';
import { defineConfig } from 'vite';
import { locales } from './src/i18n';
export default defineConfig({
plugins: [framework({ paths: locales.paths })],
});locales.paths(['/:locale', '/:locale/about']);
// ['/ja', '/en', '/ja/about', '/en/about']The replacement works by segment, so a parameter whose name merely starts with locale, such as /:localeCode, is left alone.
Each pathname is rendered as a request of its own, so the schema accepts the locale page by page and the messages come out in it. The build renders several pages at once, and none lends its locale to another.
When there is another parameter
A pattern with another parameter, such as /:locale/blog/:slug, still holds :slug after locales.paths: /ja/blog/:slug.
The build cannot write a pathname that still holds a parameter, so it stops with static build needs pathnames for /:locale/blog/:slug — supply them with the "paths" option.
vite.config.tsimport { framework } from '@k8ordo/static';
import { defineConfig } from 'vite';
import { locales } from './src/i18n';
import { readSlugs } from './src/posts';
const expandSlugs = async (paths: string[]) => {
const slugs = await readSlugs();
return paths.flatMap((path) =>
path.includes('/:slug')
? slugs.map((slug) => path.replace('/:slug', `/${slug}`))
: [path],
);
};
export default defineConfig({
plugins: [
framework({
paths: (patterns) => expandSlugs(locales.paths(patterns)),
}),
],
});Expand the locale with locales.paths first, then the remaining parameter in the same function. paths may return a Promise, so it can read the list of posts before it answers.
The language of the 404 page
A static host answers every URL it does not know with one 404.html. That file is rendered once, under the build’s sentinel segment, so its text is in the default locale.
The schema refuses the sentinel segment, so 404.html is in the default locale whichever pages the build rendered before it. One file cannot follow each visitor’s language.
The browser does not hydrate the file: it was rendered for another URL, and a message read while hydrating would disagree with it. It renders the page afresh at the visitor’s URL instead, where Client Components come out in the visitor’s locale.
So make not-found.tsx a Client Component that renders the text. A visitor without JavaScript gets the default locale.
routes/[locale]/not-found.tsx'use client';
import * as m from '../../messages';
export default function NotFound() {
return (
<main>
<h1>{m.notFound.title()}</h1>
<p>{m.notFound.description()}</p>
</main>
);
}<html lang> keeps the default locale the root layout wrote on the server. On this site, the Client Component the [locale] layout renders sets document.documentElement.lang to the visitor’s locale from an effect.
Note
Under the dev server and @k8ordo/server, a 404 is rendered at the visitor’s URL. When its segment is a locale of the set, the page is in that locale from the server’s HTML on.