Internationalization (i18n)
On this page 41
stx provides comprehensive internationalization support with translation loading, pluralization, ICU MessageFormat, and lazy loading for optimal performance.
Quick Start
// 1. Create translation files
// translations/en.json
{
"greeting": "Hello, :name!",
"items": "One item|:count items"
}
// translations/es.json
{
"greeting": "¡Hola, :name!",
"items": "Un artículo|:count artículos"
}
// 2. Configure i18n in stx.config.ts
export default {
i18n: {
defaultLocale: 'en',
locale: 'en',
translationsDir: 'translations',
format: 'json'
}
}
// 3. Use in templates
@translate('greeting', { name: 'John' })
Configuration
Basic Configuration
// stx.config.ts
export default {
i18n: {
// Default locale (fallback)
defaultLocale: 'en',
// Current locale
locale: 'en',
// Directory containing translation files
translationsDir: 'translations',
// File format: 'json', 'yaml', 'yml', or 'js'
format: 'json',
// Return key if translation not found
fallbackToKey: true,
// Enable caching
cache: true,
// Cache max age in milliseconds (optional)
cacheMaxAge: 3600000 // 1 hour
}
}
Translation File Formats
JSON
// translations/en.json
{
"welcome": "Welcome to our app",
"messages": {
"success": "Operation completed successfully",
"error": "An error occurred"
}
}
YAML
# translations/en.yaml
welcome: Welcome to our app
messages:
success: Operation completed successfully
error: An error occurred
JavaScript
// translations/en.js
export default {
welcome: 'Welcome to our app',
messages: {
success: 'Operation completed successfully',
error: 'An error occurred'
}
}
Template Directives
@translate
The main directive for translations:
<!-- Simple translation -->
<h1>@translate('')</h1>
<!-- With parameters -->
<p>@translate('' '' })</p>
<!-- Nested keys -->
<p>@translate('')</p>
<!-- Block syntax with fallback -->
@translate('')
Default content if key not found
Expression Syntax
Use translations in expressions:
<p>{{ __('greeting', { name: user.name }) }}</p>
<!-- With filters -->
<p>{{ 'welcome' | translate }}</p>
Parameter Replacement
Use :parameter syntax for dynamic values:
{
"greeting": "Hello, :name!",
"order_status": "Your order #:orderId is :status",
"welcome_back": "Welcome back, :user! You have :count unread messages."
}
@translate('' '' })
<!-- Output: Hello, Alice! -->
@translate('' '' '' })
<!-- Output: Your order #12345 is shipped -->
@translate('' ''5 })
<!-- Output: Welcome back, Bob! You have 5 unread messages. -->
Pluralization
Simple Pluralization
Use the | separator for singular/plural forms:
{
"apples": "One apple|:count apples",
"items": "No items|One item|:count items"
}
@translate(''1 })
<!-- Output: One apple -->
@translate(''5 })
<!-- Output: 5 apples -->
@translate(''0 })
<!-- Output: No items -->
Advanced Pluralization
Use exact matches and ranges:
{
"progress": "{0} Not started|{1} Just begun|[2,5] Making progress|[6,*] Almost there!"
}
{n}- Exact match for countn[min,max]- Range (inclusive)[n,*]- Fromnto infinity
@translate(''0 }) <!-- Not started -->
@translate(''1 }) <!-- Just begun -->
@translate(''3 }) <!-- Making progress -->
@translate(''10 }) <!-- Almost there! -->
ICU MessageFormat
stx supports ICU MessageFormat for complex translations:
Plural
{
"cart": "{count, plural, =0{Your cart is empty} one{# item in cart} other{# items in cart}}"
}
@translate(''0 }) <!-- Your cart is empty -->
@translate(''1 }) <!-- 1 item in cart -->
@translate(''5 }) <!-- 5 items in cart -->
Select
{
"pronoun": "{gender, select, male{He} female{She} other{They}} liked your post"
}
@translate('' '' })
<!-- Output: She liked your post -->
Select Ordinal
{
"ranking": "You finished {pos, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}"
}
@translate(''1 }) <!-- You finished 1st -->
@translate(''2 }) <!-- You finished 2nd -->
@translate(''3 }) <!-- You finished 3rd -->
@translate(''4 }) <!-- You finished 4th -->
@translate(''21 }) <!-- You finished 21st -->
Number Formatting
{
"price": "Total: {amount, number, currency}",
"percentage": "{value, number, percent} complete"
}
Date/Time Formatting
{
"posted": "Posted on {date, date, long}",
"event": "Event starts at {time, time, short}"
}
Lazy Loading
stx supports lazy loading translations for better performance, especially with many locales.
Preloading
Load translations before they're needed:
import { preloadTranslations, preloadTranslationsBackground } from 'stx'
// Preload specific locales (blocking)
await preloadTranslations({
locales: ['en', 'es', 'fr', 'de'],
parallel: true, // Load all at once
onLocaleLoaded: (locale) => console.log(`${locale} ready`),
onComplete: () => console.log('All translations loaded'),
onError: (locale, error) => console.error(`Failed to load ${locale}`)
}, options)
// Preload in background (non-blocking)
preloadTranslationsBackground({
locales: ['en', 'es', 'fr'],
parallel: true
}, options)
// Execution continues immediately
Priority Loading
Load important locales first:
await preloadTranslations({
locales: ['en', 'es', 'fr', 'de', 'ja', 'zh'],
parallel: false, // Sequential loading
priority: {
'en': 100, // Highest priority
'es': 90,
'fr': 80
// Others load after
}
}, options)
Loading State
Check and wait for translations:
import {
isLoaded,
isLoading,
getLoadingState,
waitForLocale,
getLoadingLocales
} from 'stx'
// Check if locale is ready
if (isLoaded('es')) {
// Use Spanish translations
}
// Check loading state
const state = getLoadingState('fr') // 'idle' | 'loading' | 'loaded' | 'error'
// Wait for a locale with timeout
try {
await waitForLocale('de', 5000) // Wait up to 5 seconds
} catch (error) {
// Handle timeout or loading error
}
// Get all currently loading locales
const loading = getLoadingLocales() // ['fr', 'ja']
Synchronous Access
Get translations without waiting (returns undefined if not loaded):
import { getTranslationSync, getTranslationAsync } from 'stx'
// Non-blocking - returns undefined if not cached
const translations = getTranslationSync('en')
if (translations) {
// Use translations
} else {
// Show loading state
}
// Async with automatic loading
const translations = await getTranslationAsync('en', options)
// Always returns translations (loads if needed)
Request Deduplication
Multiple simultaneous requests for the same locale share one load:
import { loadTranslationLazy } from 'stx'
// These share the same underlying request
const [t1, t2, t3] = await Promise.all([
loadTranslationLazy('en', options),
loadTranslationLazy('en', options),
loadTranslationLazy('en', options)
])
// t1 = t2 = t3 (same object)
Cache Management
import {
clearTranslationCache,
getTranslationCacheStats
} from 'stx'
// Clear all cached translations
clearTranslationCache()
// Clear specific locale
clearTranslationCache('en')
// Get cache statistics
const stats = getTranslationCacheStats()
console.log(`Cached locales: ${stats.locales.join(', ')}`)
console.log(`Total entries: ${stats.size}`)
stats.entries.forEach(entry => {
console.log(`${entry.locale}: ${entry.keyCount} keys, loaded at ${entry.loadedAt}`)
})
Programmatic Usage
Loading Translations
import { loadTranslation, getTranslation } from 'stx'
// Load translations for a locale
const translations = await loadTranslation('en', options)
// Get a specific translation
const greeting = getTranslation('greeting', translations, true, { name: 'World' })
Creating Translation Filters
import { createTranslateFilter } from 'stx'
const translations = await loadTranslation('en', options)
const t = createTranslateFilter(translations)
const greeting = t('greeting', { name: 'World' })
ICU Format Detection
import { isICUFormat, formatICU } from 'stx'
const message = '{count, plural, one{# item} other{# items}}'
if (isICUFormat(message)) {
const result = formatICU(message, { count: 5 })
// "5 items"
}
Best Practices
1. Organize Translation Keys
Use dot notation for nested structure:
{
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"auth": {
"login": "Log In",
"logout": "Log Out",
"register": "Sign Up"
},
"errors": {
"404": "Page not found",
"500": "Server error"
}
}
2. Preload Common Locales
// In your app initialization
preloadTranslationsBackground({
locales: ['en', 'es'], // Most common locales
parallel: true
}, options)
3. Handle Missing Translations
<!-- Use fallback content -->
@translate('')
This exciting new feature helps you...
4. Use ICU for Complex Logic
ICU MessageFormat handles edge cases better:
{
"results": "{count, plural, =0{No results found} =1{1 result found} other{# results found}}"
}
5. Cache in Production
configureDatabase({
i18n: {
cache: true,
cacheMaxAge: 3600000 // 1 hour
}
})
Locale Switching
import { loadTranslation } from 'stx'
async function setLocale(locale: string) {
// Update config
options.i18n.locale = locale
// Preload new locale
await loadTranslation(locale, options)
// Re-render your app
}
Examples
Complete Translation File
{
"app": {
"name": "My Application",
"tagline": "Making things easier"
},
"nav": {
"home": "Home",
"dashboard": "Dashboard",
"settings": "Settings"
},
"auth": {
"welcome": "Welcome, :name!",
"login": "Log In",
"logout": "Log Out"
},
"notifications": "{count, plural, =0{No notifications} one{# notification} other{# notifications}}",
"cart": {
"empty": "Your cart is empty",
"items": "{count, plural, one{# item} other{# items}} in cart",
"total": "Total: :amount"
},
"errors": {
"required": "This field is required",
"email": "Please enter a valid email",
"min": "Must be at least :min characters"
}
}
Template Usage
<header>
<h1>@translate('')</h1>
<nav>
<ahref="">@translate('')</a>
<ahref="">@translate('')</a>
</nav>
@if(user)
<span>@translate(''username })</span>
<span>@translate(''usernotificationCount })</span>
</header>
<sectionclass="">
@if(cartisEmpty)
<p>@translate('')</p>
<p>@translate(''cartitemCount })</p>
<p>@translate(''cartformattedTotal })</p>
</section>