Translations
Access translations with useTranslations, interpolation, and namespaces
useTranslations Hook
Access namespaced translations in any component:
import { useTranslations } from '@better-i18n/use-intl'
function HomePage() {
const t = useTranslations('home')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}API Reference
| Parameter | Type | Required | Description |
|---|---|---|---|
namespace | string | ✅ | The namespace to scope translations to |
Return Methods
| Method | Description |
|---|---|
t(key, values?) | Get translated string with optional interpolation |
t.rich(key, values) | Get translated string with React components |
t.raw(key) | Get raw translation string |
t.has(key) | Check if translation exists |
Namespaces
Namespaces help organize translations into logical groups, making large translation files manageable.
What Are Namespaces?
A namespace is a top-level key in your translation file:
{
"home": {
"title": "Welcome",
"description": "The best app ever"
},
"about": {
"title": "About Us",
"mission": "Our mission is..."
},
"common": {
"save": "Save",
"cancel": "Cancel",
"loading": "Loading..."
}
}Namespace Patterns
By Page:
// app/routes/about.tsx
const t = useTranslations('about')By Feature:
// components/LoginForm.tsx
const t = useTranslations('auth')
t('login.title')Shared Namespace:
const t = useTranslations('common')
<button>{t('actions.save')}</button>Nested Keys
Access nested keys with dot notation:
{
"settings": {
"profile": {
"title": "Profile Settings",
"fields": {
"name": "Full Name",
"email": "Email Address"
}
}
}
}const t = useTranslations('settings')
t('profile.title') // "Profile Settings"
t('profile.fields.name') // "Full Name"Multiple Namespaces
Use multiple namespaces in one component:
function CheckoutPage() {
const tCheckout = useTranslations('checkout')
const tCommon = useTranslations('common')
return (
<div>
<h1>{tCheckout('title')}</h1>
<button>{tCommon('actions.save')}</button>
<button>{tCommon('actions.cancel')}</button>
</div>
)
}Interpolation
Interpolation lets you insert dynamic values into translations using ICU Message Format.
Basic Interpolation
Insert variables with curly braces:
{
"greeting": {
"hello": "Hello, {name}!",
"welcome": "Welcome back, {firstName} {lastName}"
}
}const t = useTranslations('greeting')
t('hello', { name: 'John' })
// "Hello, John!"
t('welcome', { firstName: 'John', lastName: 'Doe' })
// "Welcome back, John Doe"Pluralization
Handle plural forms with plural:
{
"cart": {
"items": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
}t('items', { count: 0 }) // "No items"
t('items', { count: 1 }) // "1 item"
t('items', { count: 5 }) // "5 items"Plural Categories
| Category | Description | Example |
|---|---|---|
=0, =1, =N | Exact match | =0 {Empty} |
zero | Zero form (some languages) | zero {No items} |
one | Singular | one {# item} |
two | Dual (some languages) | two {# items} |
few | Few (some languages) | few {# items} |
many | Many (some languages) | many {# items} |
other | Default/plural | other {# items} |
# is replaced with the count value. Use one and other for English; other languages may need more categories.
Select (Gender/Variants)
Choose based on a string value:
{
"notifications": {
"liked": "{gender, select, male {He} female {She} other {They}} liked your post"
}
}t('liked', { gender: 'male' }) // "He liked your post"
t('liked', { gender: 'female' }) // "She liked your post"
t('liked', { gender: 'other' }) // "They liked your post"SelectOrdinal
Handle ordinal numbers (1st, 2nd, 3rd):
{
"ranking": {
"place": "You finished in {position, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place"
}
}t('place', { position: 1 }) // "You finished in 1st place"
t('place', { position: 2 }) // "You finished in 2nd place"
t('place', { position: 3 }) // "You finished in 3rd place"Rich Text
Insert React components with .rich():
{
"legal": {
"terms": "By signing up, you agree to our <link>Terms of Service</link> and <bold>Privacy Policy</bold>."
}
}const t = useTranslations('legal')
t.rich('terms', {
link: (chunks) => <a href="/terms">{chunks}</a>,
bold: (chunks) => <strong>{chunks}</strong>,
})Self-Closing Tags
For elements without children:
{
"message": "Loading<spinner/> Please wait..."
}t.rich('message', {
spinner: () => <Spinner />,
})Number & Date Formatting
Format numbers and dates inline:
{
"stats": {
"users": "{count, number} active users",
"created": "Created on {date, date, medium}"
}
}t('users', { count: 1234567 })
// "1,234,567 active users"
t('created', { date: new Date() })
// "Created on Jan 18, 2026"Escaping
Use single quotes to escape:
{
"code": "Use '{name}' to insert the user''s name"
}t('code')
// "Use {name} to insert the user's name"TypeScript Support
Enable type-safe translation keys:
import en from '../locales/en.json'
type Messages = typeof en
declare global {
interface IntlMessages extends Messages {}
}Now you get autocomplete for translation keys:
const t = useTranslations('home')
t('title') // ✅ Valid
t('typoTitle') // ❌ TypeScript errorBest Practices
Keep Namespaces Focused
// ✅ Good - focused namespaces (~20-30 keys each)
{
"home": { ... },
"dashboard": { ... },
"settings": { ... }
}
// ❌ Bad - giant namespace
{
"app": { /* 500+ nested keys */ }
}Use Consistent Naming
// ✅ Good - consistent camelCase
{
"home": {
"meta": { "title": "..." },
"hero": { "title": "...", "cta": "..." }
}
}
// ❌ Bad - inconsistent
{
"home": {
"pageTitle": "...",
"HeroTitle": "...",
"button_text": "..."
}
}Avoid Deep Nesting
// ✅ Good - reasonable depth (2-3 levels)
{
"settings": {
"profile": {
"name": "Name",
"email": "Email"
}
}
}
// ❌ Bad - too deep
{
"settings": {
"user": {
"profile": {
"personal": {
"details": {
"name": "Name"
}
}
}
}
}
}Related
- Formatting - Formatting dates and numbers
- Server Utilities - Server-side translation
- Provider - Provider configuration