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.
Installation
Section titled “Installation”@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-webyarn add @nano_kit/platform-webnpm install @nano_kit/platform-webVite Plugin
Section titled “Vite Plugin”Enable request-bound locale containers with the SSR plugin option:
// vite.config.tsimport { 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.tsimport { 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.
Production Server
Section titled “Production Server”Pass the incoming Accept-Language header to renderer.render(url, { acceptLanguage }).
// server.jsimport { 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.
Low-Level Usage
Section titled “Low-Level Usage”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'] }.