best-i18n
Integrations

Solid

Reactive translations and request isolation in SolidStart v2

Supports SolidStart v2, Solid 1 and Vite 8; SolidStart v2 itself requires Node.js 24 or newer. See the complete playground/solid-start.

Shared configuration

Reuse one locale configuration in Vite, the server and the client:

// src/i18n.ts
import type { UrlConfig } from 'best-i18n/locale-url'

export const i18n: UrlConfig = {
  locales: ['en', 'zh'],
  baseLocale: 'en',
}

Put i18n before solidStart() and enable solid: true. This example uses Nitro for Node server deployment.

// vite.config.ts
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { solidStart } from '@solidjs/start/config'
import { i18n } from 'best-i18n/vite'
import { nitro } from 'nitro/vite'
import { defineConfig } from 'vite'
import { i18n as localeConfig } from './src/i18n.ts'

export default defineConfig({
  define: {
    'import.meta.env.I18N_STATIC_LOCALE': JSON.stringify(
      process.env.I18N_STATIC_LOCALE ?? '',
    ),
  },
  plugins: [
    i18n({
      messagesDir: fileURLToPath(new URL('./messages', import.meta.url)),
      locales: localeConfig.locales,
      baseLocale: localeConfig.baseLocale,
      staticLocale: process.env.I18N_STATIC_LOCALE || undefined,
      solid: true,
    }),
    solidStart({ middleware: './src/middleware.ts' }),
    nitro(),
  ],
})

Server and hydration

Wrap next() in v2 H3 middleware to isolate each request’s locale. Static builds use their build locale:

// src/middleware.ts
import { createMiddleware } from '@solidjs/start/middleware'
import { withLocale, withRequestLocale } from 'best-i18n/server'
import { i18n } from './i18n.ts'

export default createMiddleware([
  (event, next) =>
    import.meta.env.I18N_STATIC_LOCALE
      ? withLocale(import.meta.env.I18N_STATIC_LOCALE, next)
      : withRequestLocale(event.req, i18n, next),
])

In the document component in entry-server.tsx, use getLocale() from best-i18n/solid for <html lang={getLocale()}>. Read that value before hydration:

// src/entry-client.tsx
import { mount, StartClient } from '@solidjs/start/client'
import { configure, setLocale } from 'best-i18n/solid'
import { i18n } from './i18n.ts'

configure(i18n)
// Use the server-rendered language so hydration also matches static builds.
setLocale(document.documentElement.lang || i18n.baseLocale)
mount(() => <StartClient />, document.getElementById('app')!)

Writing messages

import { createSignal } from 'solid-js'
import { t, plural } from 'best-i18n/macro'
import { Trans } from 'best-i18n/solid/macro'

export default function Counter() {
  const [count, setCount] = createSignal(0)
  const title = () => t`Hello from SolidStart!`

  return (
    <>
      <h1>{title()}</h1>
      <button onClick={() => setCount(count() + 1)}>
        {plural(count(), `One click`, `${count()} clicks`)}
      </button>
      <p>
        <Trans>
          Read the <a href='/docs'>documentation</a> to learn more.
        </Trans>
      </p>
    </>
  )
}

Solid component functions run once. Put text that must follow locale changes in JSX, an accessor (like title above), or createMemo; assigning a translated string at component initialization captures its initial value. Reusable translation logic can return accessors, without React’s useI18n().

best-i18n/solid exports reactive getLocale(), plus configure, getLocales and setLocale. Calling setLocale() updates tracked JSX and computations. For locale-prefixed URLs, use switchLocale(locale, i18n) from best-i18n/client; it saves a cookie and performs a full navigation, which also works with separate deployments per locale.

The extractor recognizes <Trans> from best-i18n/solid/macro by default. Run:

i18n-extract --src src --messages messages --locales en,zh

On this page