Skip to content

Locale

@nano_kit/platform-web provides browser-like locale helpers for SSR and tests.

Use them when server-rendered code needs to resolve the user’s preferred locale from the incoming Accept-Language header, or provide locale state through Nano Kit dependency injection. You can use browserLocale directly with navigator, or combine Locales$ with parseLocales when the same store should work in both the browser and SSR.

@nano_kit/platform-web is an optional peer dependency of @nano_kit/ssr. Install it only when your SSR app uses request-bound browser helpers such as Locales$.

pnpm add @nano_kit/platform-web

Enable request-bound locale containers with the SSR plugin option:

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import ssr from '@nano_kit/react-ssr/vite-plugin'
export default defineConfig({
plugins: [
react(),
ssr({
index: 'src/index.tsx',
browserLocale: true
})
]
})

The option makes the renderer parse the incoming Accept-Language header and provide the result through Locales$.

Read Locales$ inside a store factory when you want to resolve the current locale from the context.

// src/stores/locale.ts
import { inject } from '@nano_kit/store'
import {
Locales$,
browserLocale
} from '@nano_kit/platform-web'
const SUPPORTED_LOCALES = ['en', 'ru'] as const
export function Locale$() {
const locales = inject(Locales$)
const locale = browserLocale(locales, SUPPORTED_LOCALES, 'en')
return {
locale
}
}

In the browser, Locales$ resolves to navigator. During SSR, it resolves to a parsed Accept-Language container for the current request.

Pass the incoming Accept-Language header to renderer.render(url, { acceptLanguage }).

// server.js
import { renderer } from './dist/renderer/index.js'
app.get('*', async (req, res) => {
const result = await renderer.render(req.url, {
acceptLanguage: req.headers['accept-language']
})
if (result.redirect) {
return res.redirect(result.statusCode, result.redirect)
}
if (result.html !== null) {
return res.status(result.statusCode).send(result.html)
}
res.status(result.statusCode).send('Not Found')
})

The Vite dev server passes this header automatically when browserLocale: true is enabled.

You can also use parseLocales directly in tests or custom render pipelines:

import { InjectionContext, provide } from '@nano_kit/store'
import {
Locales$,
browserLocale,
parseLocales
} from '@nano_kit/platform-web'
const locales = parseLocales('ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7')
const context = new InjectionContext([
provide(Locales$, locales)
])
browserLocale(context.get(Locales$), ['en', 'ru-RU'], 'en')
// 'ru-RU'

parseLocales sorts languages by q quality and keeps declaration order when qualities are equal. Empty headers fall back to { language: 'en', languages: ['en'] }.