Internationalized routing
Ness serves each locale from its own URL prefix. Routes are written once; the router mounts the tree under a :locale segment.
Configurationβ
import { defineNessConfig } from '@nessframework/router';
export const i18n = {
locales: ['en', 'de', 'pt-BR'],
defaultLocale: 'en',
strategy: 'prefix-except-default',
};
export default defineNessConfig({
router: { i18n },
});
import { nessRoutes } from '@nessframework/router';
import { i18n } from '../ness.config.mjs';
export default nessRoutes({ i18n });
The configuration is passed in both places on purpose. ness.config.mjs validates it and records it in the build manifest; app/routes.ts is where routes are actually generated, and injecting a route tree behind your back would silently override a hand-written one.
Each locale becomes its own static path segment β /de/..., /fr/... β not a shared :locale parameter. A parameter would outrank both your not-found route and any top-level dynamic route, so /anything would be read as a locale and your 404 would never render. There is consequently no params.locale; read the locale with useLocale or getLocale below.
Strategiesβ
| Strategy | Default locale | Other locales |
|---|---|---|
prefix-except-default (default) | /pricing | /de/pricing |
prefix | /en/pricing | /de/pricing |
prefix-except-default keeps existing URLs working when a site adds its first translation. Choose prefix when no locale should be privileged.
An unknown prefix β /xx/pricing β returns 404 rather than rendering the page under a locale that does not exist.
Reading the localeβ
import { useLocale } from '@nessframework/router/i18n';
import { i18n } from '../../ness.config.mjs';
export default function Page() {
const locale = useLocale(i18n);
return <p>{locale}</p>;
}
In a loader or action, read it from the request:
import { getLocale } from '@nessframework/router/i18n';
export async function loader({ request }) {
return getProducts(getLocale(request, i18n));
}
Both return the default locale on unprefixed routes, so nothing has to special-case it.
Linking between localesβ
import { localizePath } from '@nessframework/router';
localizePath('/pricing', 'de', i18n); // '/de/pricing'
localizePath('/de/pricing', 'en', i18n); // '/pricing'
Detecting a visitor's languageβ
import { createLocaleMiddleware } from '@nessframework/router/i18n';
import { i18n } from '../../ness.config.mjs';
export default createLocaleMiddleware(i18n);
Unprefixed requests are redirected to the locale from Accept-Language; quality values are honoured and a region falls back to its base language, so de-AT matches a configured de. The choice is remembered in a cookie and preferred over the header afterwards, so switching language manually is not undone on the next navigation.
The redirect sets Vary: accept-language, cookie so a shared cache cannot serve one visitor's language to another.