Setup
Installation and provider setup for Vite + React applications
This guide walks you through installing Better i18n and setting up the provider in your Vite + React application.
Installation
npm install @better-i18n/use-intl @better-i18n/vite use-intlyarn add @better-i18n/use-intl @better-i18n/vite use-intlpnpm add @better-i18n/use-intl @better-i18n/vite use-intlbun add @better-i18n/use-intl @better-i18n/vite use-intlConfiguration
Step 1: Add the Vite Plugin
Configure @better-i18n/vite in your Vite config. This is the single source of truth for your project — no need to repeat it in the provider.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { betterI18n } from '@better-i18n/vite'
export default defineConfig({
plugins: [
betterI18n({ project: 'your-org/your-project' }),
react(),
],
})The plugin fetches translations server-side (in the Vite dev server or at build time) and injects them into your HTML as a <script> tag. No client-side CDN requests, no FOUC.
Step 2: Setup Provider
Wrap your app with BetterI18nProvider — no props needed:
import { BetterI18nProvider, LocaleDropdown } from '@better-i18n/use-intl'
function App() {
return (
<BetterI18nProvider> // [!code ++]
<Header />
<Router />
</BetterI18nProvider>
)
}
function Header() {
return (
<header>
<LocaleDropdown /> {/* Works out of the box */}
</header>
)
}
export default AppThe provider automatically reads project, locale, and messages from the plugin-injected <script> tag. You can still pass props explicitly — they take precedence over injected data.
Without the Vite plugin, project and locale props are required. The provider will
fetch translations from CDN on mount, which causes a brief flash of untranslated content
on the first render.
BetterI18nProvider Props
| Prop | Type | Required | Description |
|---|---|---|---|
project | string | With plugin: No | Project identifier (org/project). Auto-read from plugin. |
locale | string | With plugin: No | Current locale. Auto-detected by plugin. |
messages | Messages | No | Pre-loaded translations (auto-injected by plugin) |
onLocaleChange | (locale: string) => void | No | Called when user switches language via setLocale() |
timeZone | string | No | IANA timezone |
now | Date | No | Pin current time (for testing or SSR consistency) |
storage | TranslationStorage | No | Persistent translation cache adapter |
fetchTimeout | number | No | CDN fetch timeout in ms (default: 10000) |
retryCount | number | No | Retry attempts on CDN failure (default: 1) |
Vite Plugin Options
| Option | Type | Required | Description |
|---|---|---|---|
project | string | Yes | Project identifier (org/project) |
defaultLocale | string | No | Fallback locale (default: "en") |
localeCookie | string | No | Cookie name for locale persistence (default: "locale") |
cdnBaseUrl | string | No | CDN base URL override |
Step 3: Add Loading State
With the Vite plugin, the provider reads all config from the injected <script> tag — no props needed:
import { BetterI18nProvider } from '@better-i18n/use-intl'
import { Suspense } from 'react'
function App() {
return (
<Suspense fallback={<LoadingScreen />}> // [!code ++]
<BetterI18nProvider> // [!code ++]
<Router />
</BetterI18nProvider> // [!code ++]
</Suspense>
)
}
function LoadingScreen() {
return (
<div className="flex items-center justify-center h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900" />
</div>
)
}Using Translations
Basic Usage
import { useTranslations } from '@better-i18n/use-intl'
export function HomePage() {
const t = useTranslations('home')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}With Variables
function WelcomeMessage({ user }) {
const t = useTranslations('greeting')
return (
<p>{t('hello', { name: user.name, count: user.notifications })}</p>
)
}Formatting
import { useFormatter } from '@better-i18n/use-intl'
function OrderSummary({ order }) {
const format = useFormatter()
return (
<div>
<p>Total: {format.number(order.total, { style: 'currency', currency: 'USD' })}</p>
<p>Date: {format.dateTime(order.date, { dateStyle: 'long' })}</p>
<p>Items: {format.list(order.items, { type: 'conjunction' })}</p>
</div>
)
}Performance Tips
Use the Vite Plugin
The @better-i18n/vite plugin embeds translations in the HTML at build time — zero client-side CDN requests and no FOUC.
Preload Translations
If not using the plugin, preload messages in your HTML:
<link rel="preload" href="https://cdn.better-i18n.com/org/project/en/translations.json" as="fetch" crossorigin>Cache in Service Worker
For offline support, cache translation files in a service worker.