Internationalization (i18n) API Reference
This document covers stx's internationalization system for building multilingual applications.
Overview
stx provides a comprehensive i18n system that:
- Supports multiple translation file formats (JSON, YAML, JS)
- Provides
@translatedirective for inline translations - Supports nested translation keys with dot notation
- Includes parameter substitution
- Caches translations for performance
- Falls back to default locale when translations are missing
Configuration
Basic Setup
typescript
// stx.config.ts
export default {
i18n: {
defaultLocale: 'en',
locale: 'en',
translationsDir: 'translations',
format: 'yaml', // 'json', 'yaml', 'yml', or 'js'
fallbackToKey: true,
cache: true
}
}Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
defaultLocale | string | 'en' | Default fallback locale |
locale | string | 'en' | Current active locale |
translationsDir | string | 'translations' | Translation files directory |
format | string | 'yaml' | File format (json, yaml, yml, js) |
fallbackToKey | boolean | true | Return key if translation not found |
cache | boolean | true | Cache translation files |
Translation Files
Directory Structure
translations/
├── en.yaml # English translations
├── es.yaml # Spanish translations
├── fr.yaml # French translations
├── de.yaml # German translations
└── ja.yaml # Japanese translationsYAML Format
yaml
# translations/en.yaml
welcome: "Welcome"
hello: "Hello, :name!"
nav:
home: "Home"
about: "About"
contact: "Contact"
messages:
success: "Operation completed successfully"
error: "An error occurred"
loading: "Loading..."
user:
profile:
title: "User Profile"
edit: "Edit Profile"
settings:
title: "Settings"
language: "Language"JSON Format
json
{
"welcome": "Welcome",
"hello": "Hello, :name!",
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"messages": {
"success": "Operation completed successfully",
"error": "An error occurred",
"loading": "Loading..."
}
}JavaScript Format
javascript
// translations/en.js
export default {
welcome: "Welcome",
hello: (name) => `Hello, ${name}!`,
nav: {
home: "Home",
about: "About",
contact: "Contact"
},
messages: {
success: "Operation completed successfully",
error: "An error occurred",
loading: "Loading..."
}
}@translate Directive
Basic Usage
html
<!-- Simple translation -->
<h1>@translate('welcome')</h1>
<!-- Output: Welcome -->
<!-- Nested keys with dot notation -->
<nav>
<a href="/">@translate('nav.home')</a>
<a href="/about">@translate('nav.about')</a>
<a href="/contact">@translate('nav.contact')</a>
</nav>With Parameters
html
<!-- Parameter substitution -->
<p>@translate('hello', {name: 'Alice'})</p>
<!-- Output: Hello, Alice! -->
<p>@translate('user.welcome', {name: user.name, role: user.role})</p>
<!-- Output: Welcome back, Alice! You are logged in as Admin. -->Translation file:
yaml
# translations/en.yaml
hello: "Hello, :name!"
user:
welcome: "Welcome back, :name! You are logged in as :role."Block Syntax
html
<!-- Translation with fallback content -->
@translate('custom.message')
This is the default content if translation is not found
@endtranslateUsing Template Context
html
<script>
module.exports = {
userName: "Bob",
itemCount: 5
};
</script>
<p>@translate('greeting', {name: userName})</p>
<p>@translate('items.count', {count: itemCount})</p>Programmatic API
loadTranslation()
Load a translation file for a specific locale.
typescript
import { loadTranslation } from '@stacksjs/stx/i18n'
const translations = await loadTranslation('en', {
i18n: {
translationsDir: 'translations',
format: 'yaml',
cache: true
}
})
console.log(translations)
// { welcome: "Welcome", hello: "Hello, :name!", ... }Parameters:
locale(string): Locale code (e.g., 'en', 'es', 'fr')options(StxOptions): Configuration options
Returns: Promise<Record<string, any>> - Translation object
getTranslation()
Get a specific translation by key.
typescript
import { getTranslation } from '@stacksjs/stx/i18n'
const translations = {
hello: "Hello, :name!",
nav: {
home: "Home"
}
}
// Simple key
const hello = getTranslation('hello', translations, true, { name: 'Alice' })
console.log(hello) // "Hello, Alice!"
// Nested key
const navHome = getTranslation('nav.home', translations)
console.log(navHome) // "Home"
// Missing key with fallback
const missing = getTranslation('missing.key', translations, true)
console.log(missing) // "missing.key"
// Missing key without fallback
const noFallback = getTranslation('missing.key', translations, false)
console.log(noFallback) // ""Parameters:
key(string): Translation key (supports dot notation)translations(object): Translations objectfallbackToKey(boolean): Return key if not foundparams(object): Parameters for substitution
Returns: string - Translated string
processTranslateDirective()
Process all @translate directives in a template.
typescript
import { processTranslateDirective } from '@stacksjs/stx/i18n'
const template = `
<h1>@translate('welcome')</h1>
<p>@translate('hello', {name: 'Alice'})</p>
`
const result = await processTranslateDirective(
template,
{},
'template.stx',
{
i18n: {
locale: 'en',
translationsDir: 'translations'
}
}
)
console.log(result)Dynamic Locale Switching
Runtime Locale Change
html
<script>
module.exports = {
currentLocale: 'en'
};
</script>
<!-- Update config based on context -->
<div lang="{{ currentLocale }}">
@translate('welcome')
</div>URL-based Locale
html
<script>
// Extract locale from URL
const url = new URL(Bun.env.REQUEST_URL || 'http://localhost');
const locale = url.searchParams.get('lang') || 'en';
module.exports = {
locale
};
</script>
<!-- Configure locale from context -->
@translate('hello', {name: 'User'})Cookie-based Locale
html
<script>
// Get locale from cookie
const cookies = parseCookies(Bun.env.HTTP_COOKIE || '');
const locale = cookies.locale || 'en';
module.exports = {
locale
};
</script>Advanced Features
Pluralization
yaml
# translations/en.yaml
items:
zero: "No items"
one: ":count item"
other: ":count items"html
<script>
function pluralize(key, count, translations) {
const plural = count === 0 ? 'zero' : count === 1 ? 'one' : 'other';
const translation = translations[key][plural] || translations[key].other;
return translation.replace(':count', count);
}
module.exports = {
itemCount: 5,
pluralize
};
</script>
<p>{{ pluralize('items', itemCount, __translations) }}</p>
<!-- Output: 5 items -->Context-based Translations
yaml
# translations/en.yaml
button:
submit:
default: "Submit"
loading: "Submitting..."
success: "Submitted!"html
<script>
module.exports = {
buttonState: 'loading'
};
</script>
@translate(`button.submit.${buttonState}`)Formatted Messages
yaml
# translations/en.yaml
date:
full: ":day :month :year"
short: ":month/:day/:year"html
<script>
module.exports = {
day: 8,
month: 10,
year: 2025
};
</script>
<p>@translate('date.full', {day: day, month: month, year: year})</p>
<!-- Output: 8 10 2025 -->Best Practices
1. Organize Translations by Feature
yaml
# Good structure
auth:
login:
title: "Login"
email: "Email"
password: "Password"
register:
title: "Register"
username: "Username"
# Avoid flat structure
login_title: "Login"
login_email: "Email"2. Use Meaningful Keys
yaml
# Good
user.profile.edit.button: "Edit Profile"
# Bad
btn1: "Edit Profile"3. Keep Translations DRY
yaml
# Use references where appropriate
common:
save: "Save"
cancel: "Cancel"
form:
save: # Reference common.save
cancel: # Reference common.cancel4. Provide Context in Keys
yaml
# Good - Clear context
navigation.primary.home: "Home"
button.submit.loading: "Loading..."
# Bad - Ambiguous
home: "Home"
loading: "Loading..."Examples
Multi-language Navigation
html
<script>
module.exports = {
locale: 'en' // or 'es', 'fr', etc.
};
</script>
<nav>
<a href="/?lang=en">English</a>
<a href="/?lang=es">Español</a>
<a href="/?lang=fr">Français</a>
</nav>
<ul>
<li><a href="/">@translate('nav.home')</a></li>
<li><a href="/about">@translate('nav.about')</a></li>
<li><a href="/services">@translate('nav.services')</a></li>
<li><a href="/contact">@translate('nav.contact')</a></li>
</ul>translations/en.yaml:
yaml
nav:
home: "Home"
about: "About Us"
services: "Services"
contact: "Contact"translations/es.yaml:
yaml
nav:
home: "Inicio"
about: "Sobre Nosotros"
services: "Servicios"
contact: "Contacto"Form with Translations
html
<form>
<h2>@translate('form.contact.title')</h2>
<label>
@translate('form.fields.name')
<input type="text" name="name" placeholder="@translate('form.placeholders.name')">
</label>
<label>
@translate('form.fields.email')
<input type="email" name="email" placeholder="@translate('form.placeholders.email')">
</label>
<label>
@translate('form.fields.message')
<textarea name="message" placeholder="@translate('form.placeholders.message')"></textarea>
</label>
<button type="submit">
@translate('form.buttons.submit')
</button>
</form>Dynamic User Messages
html
<script>
module.exports = {
user: {
name: "Alice",
lastLogin: "2025-10-08",
unreadCount: 3
}
};
</script>
<div class="user-dashboard">
<h1>@translate('dashboard.welcome', {name: user.name})</h1>
<p>@translate('dashboard.lastLogin', {date: user.lastLogin})</p>
@if(user.unreadCount > 0)
<div class="notification">
@translate('dashboard.unreadMessages', {count: user.unreadCount})
</div>
@endif
</div>translations/en.yaml:
yaml
dashboard:
welcome: "Welcome back, :name!"
lastLogin: "Last login: :date"
unreadMessages: "You have :count unread messages"Cache Management
Translation Cache
typescript
import { translationsCache } from '@stacksjs/stx/i18n'
// Manually clear cache for a locale
delete translationsCache['en']
// Clear all cached translations
Object.keys(translationsCache).forEach(key => {
delete translationsCache[key]
})Cache Warming
Pre-load translations during application startup:
typescript
import { loadTranslation } from '@stacksjs/stx/i18n'
const locales = ['en', 'es', 'fr', 'de']
// Warm up cache
for (const locale of locales) {
await loadTranslation(locale, {
i18n: {
translationsDir: 'translations',
cache: true
}
})
}Error Handling
Missing Translation Files
When a translation file is not found:
- Falls back to default locale
- Returns empty object if default locale also missing
- Logs error in debug mode
Missing Translation Keys
When a translation key is not found:
- Returns the key itself if
fallbackToKey: true - Returns empty string if
fallbackToKey: false - Does not throw errors
Debug Mode
Enable debug mode to see detailed i18n logs:
typescript
// stx.config.ts
export default {
debug: true,
i18n: {
locale: 'en',
translationsDir: 'translations'
}
}See Also
- Template Syntax - Template syntax reference
- Directives - All stx directives
- Configuration - Configuration options
- Examples - More examples