Agnostic internationalization for Flight Framework. Choose your engine: i18next, Paraglide, FormatJS, Lingui, or custom.
npm install @flightdev/i18nbash
npm install @flightdev/i18n
Install your preferred adapter:
npm install i18next # Most popular, feature-rich
npm install @inlang/paraglide-js # Compile-time, smallest bundle
npm install @formatjs/intl # ICU message format
npm install @lingui/core # Compile-time extraction
`
---
Quick Start
$3
`typescript
// src/i18n.ts
import { createI18n } from '@flightdev/i18n';
import { i18next } from '@flightdev/i18n/i18next';
export const i18n = createI18n(i18next({
locales: ['en', 'es', 'fr', 'de'],
defaultLocale: 'en',
fallbackLocale: 'en',
}));
`
$3
`
src/
locales/
en/
common.json
errors.json
es/
common.json
errors.json
`
`json
// src/locales/en/common.json
{
"welcome": "Welcome to our app",
"greeting": "Hello, {{name}}!",
"items": "You have {{count}} item",
"items_plural": "You have {{count}} items"
}
`
$3
`typescript
import { i18n } from './i18n';
await i18n.init();
// Basic translation
i18n.t('welcome'); // "Welcome to our app"
// With interpolation
i18n.t('greeting', { name: 'Maria' }); // "Hello, Maria!"
// With pluralization
i18n.t('items', { count: 1 }); // "You have 1 item"
i18n.t('items', { count: 5 }); // "You have 5 items"
// Change locale
await i18n.setLocale('es');
i18n.t('welcome'); // "Bienvenido a nuestra app"
`
---
Adapters
$3
The most popular option with extensive features.
`typescript
import { i18next } from '@flightdev/i18n/i18next';
const adapter = i18next({
locales: ['en', 'es', 'fr'],
defaultLocale: 'en',
fallbackLocale: 'en',
// i18next-specific options
detection: {
order: ['cookie', 'header', 'navigator'],
caches: ['cookie'],
},
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});
`
$3
Compile-time translations for the smallest bundle size.
`typescript
import { paraglide } from '@flightdev/i18n/paraglide';
import * as messages from './paraglide/messages';
const adapter = paraglide({
messages,
locales: ['en', 'es'],
defaultLocale: 'en',
});
`
$3
ICU message format with powerful formatting.
`typescript
import { formatjs } from '@flightdev/i18n/formatjs';
const adapter = formatjs({
locales: ['en', 'es'],
defaultLocale: 'en',
messages: {
en: { greeting: 'Hello, {name}!' },
es: { greeting: 'Hola, {name}!' },
},
});
`
$3
Compile-time extraction with excellent developer experience.
`typescript
import { lingui } from '@flightdev/i18n/lingui';
import { messages as enMessages } from './locales/en/messages';
import { messages as esMessages } from './locales/es/messages';
const adapter = lingui({
locales: ['en', 'es'],
defaultLocale: 'en',
messages: {
en: enMessages,
es: esMessages,
},
});
`
---
Locale Detection
Automatic detection from multiple sources:
`typescript
import { createI18n, detectLocale } from '@flightdev/i18n';
const i18n = createI18n(adapter, {
detection: {
// Detection order (first match wins)
order: ['path', 'cookie', 'header', 'navigator'],
// URL path detection: /en/about -> locale: 'en'
pathIndex: 0,
// Cookie name for persistence
cookieName: 'locale',
// Header to check (Accept-Language)
headerName: 'accept-language',
},
});
`
$3
`typescript
import { getLocaleFromRequest } from '@flightdev/i18n';
export async function loader({ request }) {
const locale = getLocaleFromRequest(request, {
supported: ['en', 'es', 'fr'],
fallback: 'en',
});
return { locale };
}
`
$3
`typescript
// Routes: /en/about, /es/about, /fr/about
import { createLocaleRouter } from '@flightdev/i18n';
const router = createLocaleRouter({
locales: ['en', 'es', 'fr'],
defaultLocale: 'en',
strategy: 'prefix', // 'prefix' | 'prefix-except-default' | 'domain'
});
`
---
UI Framework Integration
$3
`tsx
import { I18nProvider, useTranslation, Trans } from '@flightdev/i18n/react';
function App() {
return (
);
}
function Content() {
const { t, locale, setLocale, locales } = useTranslation();
return (
{t('welcome')}
{t('greeting', { name: 'World' })}
{/ Rich text with components /}
i18nKey="terms"
components={{
link: ,
bold: ,
}}
/>
{/ Locale switcher /}
);
}
`
$3
`vue
{{ t('welcome') }}
{{ t('greeting', { name: 'World' }) }}
`
$3
`svelte
{$t('welcome')}
{$t('greeting', { name: 'World' })}
`
$3
`tsx
import { useI18n, Trans } from '@flightdev/i18n/solid';
function Content() {
const { t, locale, setLocale, locales } = useI18n();
return (
<>
{t('welcome')}
{t('greeting', { name: 'World' })}
>
);
}
`
---
Formatting Utilities
Built-in formatters using the Intl API:
`typescript
import {
formatNumber,
formatCurrency,
formatDate,
formatTime,
formatRelativeTime,
formatList,
} from '@flightdev/i18n';
// Numbers
formatNumber(1234567.89, 'en-US'); // "1,234,567.89"
formatNumber(1234567.89, 'de-DE'); // "1.234.567,89"
formatNumber(0.75, 'en-US', { style: 'percent' }); // "75%"
// Currency
formatCurrency(99.99, 'USD', 'en-US'); // "$99.99"
formatCurrency(99.99, 'EUR', 'de-DE'); // "99,99 €"
formatCurrency(99.99, 'JPY', 'ja-JP'); // "¥100"
// Dates
formatDate(new Date(), 'en-US'); // "1/15/2026"
formatDate(new Date(), 'de-DE'); // "15.1.2026"
formatDate(new Date(), 'en-US', { dateStyle: 'full' });
// "Thursday, January 15, 2026"
// Time
formatTime(new Date(), 'en-US'); // "3:45 PM"
formatTime(new Date(), 'de-DE'); // "15:45"
// Relative time
formatRelativeTime(Date.now() - 3600000, 'en-US'); // "1 hour ago"
formatRelativeTime(Date.now() + 86400000, 'en-US'); // "in 1 day"
// Lists
formatList(['Apple', 'Banana', 'Cherry'], 'en-US');
// "Apple, Banana, and Cherry"
formatList(['Apple', 'Banana', 'Cherry'], 'es-ES');
// "Apple, Banana y Cherry"
`
---
Pluralization
Automatic plural form selection based on count:
`json
// English (2 forms: one, other)
{
"apple": "{{count}} apple",
"apple_plural": "{{count}} apples"
}
// Russian (3 forms: one, few, many)
{
"apple_one": "{{count}} яблоко",
"apple_few": "{{count}} яблока",
"apple_many": "{{count}} яблок"
}
// Arabic (6 forms)
{
"apple_zero": "...",
"apple_one": "...",
"apple_two": "...",
"apple_few": "...",
"apple_many": "...",
"apple_other": "..."
}
`
`typescript
t('apple', { count: 0 }); // "0 apples"
t('apple', { count: 1 }); // "1 apple"
t('apple', { count: 5 }); // "5 apples"
`
---
Interpolation
Insert dynamic values into translations:
`json
{
"greeting": "Hello, {{name}}!",
"order": "Order #{{orderId}} placed on {{date, datetime}}",
"price": "Total: {{amount, currency(USD)}}"
}
`
`typescript
t('greeting', { name: 'John' });
// "Hello, John!"
t('order', { orderId: '12345', date: new Date() });
// "Order #12345 placed on Jan 15, 2026"
t('price', { amount: 49.99 });
// "Total: $49.99"
`
---
Namespace Support
Organize translations by feature:
`
locales/
en/
common.json # Shared translations
auth.json # Login, signup, etc.
dashboard.json # Dashboard-specific
errors.json # Error messages
`
`typescript
const i18n = createI18n(adapter, {
namespaces: ['common', 'auth', 'dashboard', 'errors'],
defaultNamespace: 'common',
});
// Use namespace prefix
t('auth:login'); // From auth.json
t('dashboard:welcome'); // From dashboard.json
t('errors:not_found'); // From errors.json
t('greeting'); // From common.json (default)
`
---
Loading Translations
$3
Include all translations in the bundle:
`typescript
import en from './locales/en/common.json';
import es from './locales/es/common.json';
const i18n = createI18n(adapter, {
resources: { en, es },
});
`
$3
Load translations on demand:
`typescript
const i18n = createI18n(adapter, {
loadPath: '/locales/{{locale}}/{{namespace}}.json',
// Preload specific locales
preload: ['en'],
// Load namespace on demand
partialBundledLanguages: true,
});
// Load additional namespace
await i18n.loadNamespace('dashboard');
`
---
SSR and Hydration
Avoid content flash by synchronizing server and client:
`typescript
// Server
export async function loader({ request }) {
const locale = getLocaleFromRequest(request);
await i18n.setLocale(locale);
return {
locale,
translations: i18n.getResourceBundle(locale, 'common'),
};
}
// Client hydration
const { locale, translations } = useLoaderData();
i18n.addResourceBundle(locale, 'common', translations);
await i18n.setLocale(locale);
`
$3
`tsx
// entry-server.tsx
const html = renderToString(
);
// entry-client.tsx
hydrateRoot(
document,
);
`
---
API Reference
$3
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| locales | string[] | required | Supported locales |
| defaultLocale | string | required | Fallback locale |
| fallbackLocale | string | defaultLocale | Missing key fallback |
| namespaces | string[] | ['translation'] | Translation namespaces |
| defaultNamespace | string | 'translation' | Default namespace |
| loadPath | string | - | Path pattern for loading |
| detection | object | - | Locale detection config |
$3
| Method | Description |
|--------|-------------|
| init() | Initialize the instance |
| t(key, options?) | Translate a key |
| setLocale(locale) | Change current locale |
| getLocale() | Get current locale |
| hasLocale(locale) | Check if locale exists |
| loadNamespace(ns) | Load a namespace |
| addResourceBundle(locale, ns, resources) | Add translations |
| getResourceBundle(locale, ns) | Get translations |
---
Creating Custom Adapters
Implement the I18nAdapter interface:
`typescript
import type { I18nAdapter } from '@flightdev/i18n';
export function myAdapter(config: MyConfig): I18nAdapter {
return {
name: 'my-adapter',
async init() {
// Initialize your library
},
t(key, options) {
// Return translated string
},
async setLocale(locale) {
// Change locale
},
getLocale() {
// Return current locale
},
hasKey(key) {
// Check if key exists
},
async loadNamespace(namespace) {
// Load namespace translations
},
};
}
``