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 use-intlyarn add @better-i18n/use-intl use-intlpnpm add @better-i18n/use-intl use-intlbun add @better-i18n/use-intl use-intlConfiguration
Step 1: Create i18n Configuration
Create a configuration file to centralize your i18n settings:
export const i18nConfig = {
project: 'your-org/your-project',
defaultLocale: 'en',
} as constStep 2: Setup Provider
Wrap your app with BetterI18nProvider:
import { BetterI18nProvider } from '@better-i18n/use-intl'
import { useState } from 'react'
import { i18nConfig } from './i18n.config'
import { Router } from './Router'
function App() {
// Get initial locale from localStorage or browser
const [locale, setLocale] = useState(() => {
return localStorage.getItem('locale') || navigator.language.split('-')[0] || 'en'
})
const handleLocaleChange = (newLocale: string) => {
localStorage.setItem('locale', newLocale)
setLocale(newLocale)
}
return (
<BetterI18nProvider
project={i18nConfig.project}
locale={locale}
onLocaleChange={handleLocaleChange}
> // [!code ++]
<Router />
</BetterI18nProvider>
)
}
export default AppBetterI18nProvider renders null until translations are loaded. If no messages prop is
provided, the component tree is invisible while the CDN fetch is in progress. Wrap with
a Suspense boundary or show a loading indicator via the onLocaleChange state.
BetterI18nProvider Props
| Prop | Type | Required | Description |
|---|---|---|---|
project | string | Yes | Project identifier (org/project) |
locale | string | Yes | Current locale (from state/localStorage) |
onLocaleChange | (locale: string) => void | Yes | Called when user switches language |
messages | Messages | No | Pre-loaded translations (SSR or static) |
timeZone | string | No | IANA timezone |
now | Date | No | Pin current time (for testing or SSR consistency) |
storage | TranslationStorage | No | Persistent translation cache adapter |
staticData | Record<string, Messages> | No | Offline fallback translations |
fetchTimeout | number | No | CDN fetch timeout in ms (default: 10000) |
retryCount | number | No | Retry attempts on CDN failure (default: 1) |
Step 3: Add Loading State
For a better UX, handle the loading state with Suspense:
import { BetterI18nProvider } from '@better-i18n/use-intl'
import { Suspense } from 'react'
function App() {
return (
<Suspense fallback={<LoadingScreen />}> // [!code ++]
<BetterI18nProvider
project="your-org/your-project"
locale="en"
> // [!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>
)
}Language Switcher
Simple Select
import { useLocale, useLanguages } from '@better-i18n/use-intl'
export function LanguageSwitcher() {
const { locale, setLocale } = useLocale()
const { languages, isLoading } = useLanguages()
if (isLoading) {
return <select disabled><option>Loading...</option></select>
}
return (
<select
value={locale}
onChange={(e) => setLocale(e.target.value)}
className="px-3 py-2 border rounded"
>
{languages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.nativeName}
</option>
))}
</select>
)
}Built-in Component
Use the included LanguageSwitcher component:
import { LanguageSwitcher } from '@better-i18n/use-intl'
function Header() {
return (
<header className="flex justify-between items-center p-4">
<Logo />
<LanguageSwitcher className="border rounded px-3 py-2" />
</header>
)
}Performance Tips
Preload Default Locale
For faster initial load, preload messages in your HTML:
<link rel="preload" href="https://cdn.better-i18n.com/org/project/en.json" as="fetch" crossorigin>Use Suspense
Wrap your app in Suspense to show a loading state while translations load.
Cache in Service Worker
For offline support, cache translation files in a service worker.