Internationalization
Build multilingual applications with ease. Soli provides built-in i18n support for translations, number formatting, currency, dates, and pluralization.
Global by Design
The I18n class provides everything you need to localize your application: string translations with interpolation, pluralization rules, locale-aware number and currency formatting, and date localization.
Setting the Locale
Use I18n.locale() to get the current locale and I18n.set_locale() to change it.
# Get the current locale (defaults to "en")
current = I18n.locale()
print(current); # "en"
# Set locale to French
I18n.set_locale("fr")
print(I18n.locale()); # "fr"
# Set locale to German
I18n.set_locale("de");
Basic Translation
Translate strings using I18n.translate(key, locale?, translations?). The function looks up the key in your translations hash.
# Define translations
translations = {
"en.greeting" => "Hello",
"fr.greeting" => "Bonjour",
"de.greeting" => "Hallo",
"es.greeting" => "Hola",
"en.welcome" => "Welcome to our app!",
"fr.welcome" => "Bienvenue dans notre application!"
}
# Translate using current locale
I18n.set_locale("fr")
msg = I18n.translate("greeting", null, translations)
print(msg); # "Bonjour"
# Translate with explicit locale
hello = I18n.translate("greeting", "de", translations)
print(hello); # "Hallo"
# Falls back to key if not found
missing = I18n.translate("unknown_key", null, translations)
print(missing); # "unknown_key"
Pluralization
Handle singular and plural forms with I18n.plural(key, n, locale?, translations?). Use _zero suffix for zero, _one for singular, and _other for plural.
translations = {
"en.item_zero" => "No items",
"en.item_one" => "1 item",
"en.item_other" => "{count} items",
"fr.item_zero" => "Aucun article",
"fr.item_one" => "1 article",
"fr.item_other" => "{count} articles"
}
I18n.set_locale("en")
# Zero form
none = I18n.plural("item", 0, null, translations)
print(none); # "No items"
# Singular form
single = I18n.plural("item", 1, null, translations)
print(single); # "1 item"
# Plural form
multiple = I18n.plural("item", 5, null, translations)
print(multiple); # "{count} items"
# French plural
fr_plural = I18n.plural("item", 3, "fr", translations)
print(fr_plural); # "{count} articles"
Number Formatting
Format numbers according to locale conventions using I18n.format_number(n, locale?).
value = 1234.56
# English format (dot as decimal separator)
en_num = I18n.format_number(value, "en")
print(en_num); # "1234.56"
# French format (comma as decimal separator)
fr_num = I18n.format_number(value, "fr")
print(fr_num); # "1234,56"
# German format
de_num = I18n.format_number(value, "de")
print(de_num); # "1234,56"
Currency Formatting
Format monetary amounts with I18n.format_currency(amount, currency, locale?). Supports common currency codes.
price = 1999.99
# US Dollars
usd = I18n.format_currency(price, "USD", "en")
print(usd); # "$1,999.99"
# Euros with French locale
eur = I18n.format_currency(price, "EUR", "fr")
print(eur); # "€1.999,99"
# British Pounds
gbp = I18n.format_currency(price, "GBP", "en")
print(gbp); # "£1,999.99"
# Japanese Yen
jpy = I18n.format_currency(2500, "JPY", "en")
print(jpy); # "¥2,500"
Date Formatting
Format dates using I18n.format_date(timestamp, locale?). Different locales use different date formats.
# Unix timestamp for January 15, 2026
timestamp = 1768521600
# US format: MM/DD/YYYY
us_date = I18n.format_date(timestamp, "en")
print(us_date); # "01/15/2026"
# French format: DD/MM/YYYY
fr_date = I18n.format_date(timestamp, "fr")
print(fr_date); # "15/01/2026"
# German format: DD.MM.YYYY
de_date = I18n.format_date(timestamp, "de")
print(de_date); # "15.01.2026"
# ISO format (default for other locales)
iso_date = I18n.format_date(timestamp, "ja")
print(iso_date); # "2026-01-15"
DateTime.format() with Locale
The DateTime.format() method accepts an optional locale parameter to produce localized month and day names. This is useful when you need full control over the format pattern while still getting translated names.
dt = DateTime.parse("2024-03-06T14:30:00Z")
# English (default)
dt.format("%A %d %B %Y") # "Wednesday 06 March 2024"
# French
dt.format("%A %d %B %Y", "fr") # "mercredi 06 mars 2024"
dt.format("%d %b %Y", "fr") # "06 mars 2024"
# Spanish
dt.format("%A %d %B %Y", "es") # "miércoles 06 marzo 2024"
# Supported locales: "en", "fr", "es", "de", "it", "pt"
# Numeric specifiers (%Y, %m, %d, %H, %M, %S) are unaffected by locale
Translation Files
Organize translations in JSON files and load them at startup.
{
"en.app_name": "My Application",
"en.nav.home": "Home",
"en.nav.about": "About Us",
"en.nav.contact": "Contact",
"en.messages.success": "Operation completed successfully!",
"en.messages.error": "An error occurred. Please try again.",
"en.user.greeting": "Hello, {name}!",
"en.items_one": "You have 1 item",
"en.items_other": "You have {count} items"
}
# Load translations from JSON file
en_json = file_read("config/locales/en.json")
en_translations = json_decode(en_json)
fr_json = file_read("config/locales/fr.json")
fr_translations = json_decode(fr_json)
# Merge translations
all_translations = en_translations.merge(fr_translations)
# Use in your application
greeting = I18n.translate("nav.home", "en", all_translations)
print(greeting); # "Home"
Supported Locales
The I18n class provides built-in formatting support for these locales:
| Locale Code | Language | Number Format | Date Format |
|---|---|---|---|
en |
English | 1,234.56 | MM/DD/YYYY |
fr |
French | 1.234,56 | DD/MM/YYYY |
de |
German | 1.234,56 | DD.MM.YYYY |
es |
Spanish | 1.234,56 | YYYY-MM-DD |
it |
Italian | 1.234,56 | YYYY-MM-DD |
Method Reference
I18n.locale()
Returns the current locale string. Defaults to "en" if not set.
I18n.set_locale(locale)
Sets the current locale. Returns the locale string.
I18n.translate(key, locale?, translations?)
Translates a key. Uses current locale if not specified. Falls back to the key itself if no translation found.
I18n.plural(key, n, locale?, translations?)
Returns zero (_zero), singular (_one), or plural (_other)
form based on count.
I18n.format_number(n, locale?)
Formats a number according to locale conventions (decimal separator).
I18n.format_currency(amount, currency, locale?)
Formats a monetary amount with currency symbol. Supports USD, EUR, GBP, JPY.
I18n.format_date(timestamp, locale?)
Formats a Unix timestamp as a date string according to locale conventions.
Best Practice
Set the locale early in your request lifecycle, typically in a before filter or middleware.
You can detect the user's preferred language from the Accept-Language header or from user preferences stored in your database.