JavaScript Intl API: Internationalization Made Easy
Master the JavaScript Intl API for formatting dates, numbers, currencies, and text according to different locales. Learn all Intl objects with practical examples.
The Intl API provides language-sensitive string comparison, number formatting, and date and time formatting. It enables JavaScript applications to display content appropriately for different locales and regions without manual formatting logic.
Understanding the Intl API
The Intl object is the namespace for the ECMAScript Internationalization API, providing access to several constructors for language-sensitive operations.
Basic Locale Concepts
// Locale identifiers
const locales = [
'en-US', // English (United States)
'en-GB', // English (United Kingdom)
'es-ES', // Spanish (Spain)
'es-MX', // Spanish (Mexico)
'fr-FR', // French (France)
'de-DE', // German (Germany)
'ja-JP', // Japanese (Japan)
'zh-CN', // Chinese (China)
'ar-SA', // Arabic (Saudi Arabia)
'hi-IN', // Hindi (India)
];
// Browser's default locale
const defaultLocale = navigator.language || 'en-US';
console.log(`Default locale: ${defaultLocale}`);
// Get all user's preferred languages
const userLanguages = navigator.languages;
console.log('User languages:', userLanguages);
// Checking locale support
function getSupportedLocale(requested, supported) {
const match = Intl.getCanonicalLocales(requested).find((locale) =>
supported.includes(locale)
);
return match || supported[0];
}
// Locale negotiation
const requestedLocales = ['fr-CA', 'fr', 'en'];
const supportedLocales = ['en-US', 'fr-FR', 'de-DE'];
const bestMatch = Intl.getCanonicalLocales(requestedLocales).find((locale) =>
supportedLocales.some((supported) =>
supported.startsWith(locale.split('-')[0])
)
);
Intl.NumberFormat
Format numbers according to locale-specific conventions.
Basic Number Formatting
// Basic usage
const number = 123456.789;
// Different locales
console.log(new Intl.NumberFormat('en-US').format(number)); // 123,456.789
console.log(new Intl.NumberFormat('de-DE').format(number)); // 123.456,789
console.log(new Intl.NumberFormat('fr-FR').format(number)); // 123 456,789
console.log(new Intl.NumberFormat('en-IN').format(number)); // 1,23,456.789
// With options
const options = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
};
const formatter = new Intl.NumberFormat('en-US', options);
console.log(formatter.format(123.4)); // 123.40
console.log(formatter.format(123.456)); // 123.46
// Scientific notation
const scientific = new Intl.NumberFormat('en-US', {
notation: 'scientific',
});
console.log(scientific.format(123456.789)); // 1.235E5
// Engineering notation
const engineering = new Intl.NumberFormat('en-US', {
notation: 'engineering',
});
console.log(engineering.format(123456.789)); // 123.457E3
// Compact notation
const compact = new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short',
});
console.log(compact.format(1234)); // 1.2K
console.log(compact.format(1234567)); // 1.2M
console.log(compact.format(1234567890)); // 1.2B
Currency Formatting
// Currency formatting
const amount = 1234.56;
// Different currencies and locales
const usd = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
console.log(usd.format(amount)); // $1,234.56
const eur = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
});
console.log(eur.format(amount)); // 1.234,56 €
const jpy = new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'JPY',
});
console.log(jpy.format(amount)); // ¥1,235
// Currency display options
const currencyOptions = {
style: 'currency',
currency: 'USD',
currencyDisplay: 'name', // 'symbol' | 'narrowSymbol' | 'code' | 'name'
};
console.log(
new Intl.NumberFormat('en-US', {
...currencyOptions,
currencyDisplay: 'symbol',
}).format(amount)
); // $1,234.56
console.log(
new Intl.NumberFormat('en-US', {
...currencyOptions,
currencyDisplay: 'code',
}).format(amount)
); // USD 1,234.56
console.log(
new Intl.NumberFormat('en-US', {
...currencyOptions,
currencyDisplay: 'name',
}).format(amount)
); // 1,234.56 US dollars
// Accounting format (negatives in parentheses)
const accounting = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'accounting',
});
console.log(accounting.format(1234.56)); // $1,234.56
console.log(accounting.format(-1234.56)); // ($1,234.56)
Percentage and Unit Formatting
// Percentage formatting
const percent = 0.75;
const percentFormatter = new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 1,
});
console.log(percentFormatter.format(percent)); // 75.0%
// Different percentage conventions
console.log(
new Intl.NumberFormat('tr-TR', {
style: 'percent',
}).format(0.75)
); // %75
// Unit formatting
const unitFormatter = new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer-per-hour',
unitDisplay: 'long',
});
console.log(unitFormatter.format(80)); // 80 kilometers per hour
// Different unit displays
const speed = 100;
console.log(
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'mile-per-hour',
unitDisplay: 'short',
}).format(speed)
); // 100 mph
console.log(
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'mile-per-hour',
unitDisplay: 'narrow',
}).format(speed)
); // 100mph
// Temperature units
const temp = 25;
console.log(
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'celsius',
}).format(temp)
); // 25°C
console.log(
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'fahrenheit',
}).format(77)
); // 77°F
// Compound units
console.log(
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'liter-per-kilometer',
}).format(5.5)
); // 5.5 L/km
Advanced Number Formatting
// Sign display options
const signOptions = {
signDisplay: 'always', // 'auto' | 'never' | 'always' | 'exceptZero'
};
const signFormatter = new Intl.NumberFormat('en-US', signOptions);
console.log(signFormatter.format(123)); // +123
console.log(signFormatter.format(-123)); // -123
console.log(signFormatter.format(0)); // +0
// Grouping options
console.log(
new Intl.NumberFormat('en-US', {
useGrouping: false,
}).format(1234567)
); // 1234567
console.log(
new Intl.NumberFormat('en-IN', {
maximumSignificantDigits: 3,
}).format(1234567)
); // 12,30,000
// Range formatting
const range = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
// Format range (if supported)
if (range.formatRange) {
console.log(range.formatRange(100, 200)); // $100 – $200
}
// Rounding options
const rounder = new Intl.NumberFormat('en-US', {
maximumFractionDigits: 2,
roundingMode: 'halfExpand', // 'ceil' | 'floor' | 'halfExpand' | 'halfTrunc'
});
console.log(rounder.format(1.235)); // 1.24
console.log(rounder.format(1.245)); // 1.25
Intl.DateTimeFormat
Format dates and times according to locale-specific conventions.
Basic Date Formatting
const date = new Date('2024-01-15T14:30:00');
// Different locales
console.log(new Intl.DateTimeFormat('en-US').format(date)); // 1/15/2024
console.log(new Intl.DateTimeFormat('en-GB').format(date)); // 15/01/2024
console.log(new Intl.DateTimeFormat('de-DE').format(date)); // 15.1.2024
console.log(new Intl.DateTimeFormat('ja-JP').format(date)); // 2024/1/15
// Date style options
const dateStyles = ['full', 'long', 'medium', 'short'];
dateStyles.forEach((style) => {
const formatter = new Intl.DateTimeFormat('en-US', { dateStyle: style });
console.log(`${style}: ${formatter.format(date)}`);
});
// full: Monday, January 15, 2024
// long: January 15, 2024
// medium: Jan 15, 2024
// short: 1/15/24
// Time style options
const timeStyles = ['full', 'long', 'medium', 'short'];
timeStyles.forEach((style) => {
const formatter = new Intl.DateTimeFormat('en-US', {
timeStyle: style,
timeZone: 'America/New_York',
});
console.log(`${style}: ${formatter.format(date)}`);
});
Custom Date Components
const date = new Date('2024-01-15T14:30:00');
// Specific components
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short',
};
const formatter = new Intl.DateTimeFormat('en-US', options);
console.log(formatter.format(date));
// Monday, January 15, 2024 at 2:30:00 PM EST
// Different month formats
['numeric', '2-digit', 'long', 'short', 'narrow'].forEach((style) => {
const f = new Intl.DateTimeFormat('en-US', { month: style });
console.log(`${style}: ${f.format(date)}`);
});
// numeric: 1
// 2-digit: 01
// long: January
// short: Jan
// narrow: J
// Era display
const ancientDate = new Date(-500, 0, 1);
console.log(
new Intl.DateTimeFormat('en-US', {
era: 'long',
year: 'numeric',
}).format(ancientDate)
); // 501 Before Christ
// Week formatting
const weekFormatter = new Intl.DateTimeFormat('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
console.log(weekFormatter.format(date)); // Monday, January 15, 2024
Time Zones and Calendars
const date = new Date('2024-01-15T14:30:00Z');
// Different time zones
const timeZones = [
'America/New_York',
'Europe/London',
'Asia/Tokyo',
'Australia/Sydney',
];
timeZones.forEach((timeZone) => {
const formatter = new Intl.DateTimeFormat('en-US', {
timeStyle: 'long',
timeZone: timeZone,
});
console.log(`${timeZone}: ${formatter.format(date)}`);
});
// Calendar systems
const calendars = [
{ locale: 'en-US-u-ca-gregory', name: 'Gregorian' },
{ locale: 'en-US-u-ca-islamic', name: 'Islamic' },
{ locale: 'en-US-u-ca-hebrew', name: 'Hebrew' },
{ locale: 'en-US-u-ca-chinese', name: 'Chinese' },
{ locale: 'en-US-u-ca-buddhist', name: 'Buddhist' },
];
calendars.forEach(({ locale, name }) => {
const formatter = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
console.log(`${name}: ${formatter.format(date)}`);
});
// Hour cycle (12-hour vs 24-hour)
const time = new Date('2024-01-15T14:30:00');
console.log(
new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: true,
}).format(time)
); // 2:30 PM
console.log(
new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: false,
}).format(time)
); // 14:30
Relative Time Formatting
// Intl.RelativeTimeFormat
const rtf = new Intl.RelativeTimeFormat('en-US', {
numeric: 'auto',
style: 'long',
});
// Past
console.log(rtf.format(-1, 'day')); // yesterday
console.log(rtf.format(-2, 'day')); // 2 days ago
console.log(rtf.format(-1, 'week')); // last week
console.log(rtf.format(-3, 'month')); // 3 months ago
console.log(rtf.format(-1, 'year')); // last year
// Future
console.log(rtf.format(1, 'day')); // tomorrow
console.log(rtf.format(2, 'day')); // in 2 days
console.log(rtf.format(1, 'week')); // next week
console.log(rtf.format(3, 'month')); // in 3 months
console.log(rtf.format(1, 'year')); // next year
// Different styles
const styles = ['long', 'short', 'narrow'];
styles.forEach((style) => {
const formatter = new Intl.RelativeTimeFormat('en-US', { style });
console.log(`${style}: ${formatter.format(5, 'day')}`);
});
// long: in 5 days
// short: in 5 days
// narrow: in 5d
// Numeric vs auto
const numeric = new Intl.RelativeTimeFormat('en-US', { numeric: 'always' });
const auto = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
console.log(numeric.format(-1, 'day')); // 1 day ago
console.log(auto.format(-1, 'day')); // yesterday
// Helper function for relative time
function getRelativeTime(date, baseDate = new Date()) {
const diff = date - baseDate;
const absDiff = Math.abs(diff);
const units = [
{ unit: 'year', ms: 365 * 24 * 60 * 60 * 1000 },
{ unit: 'month', ms: 30 * 24 * 60 * 60 * 1000 },
{ unit: 'week', ms: 7 * 24 * 60 * 60 * 1000 },
{ unit: 'day', ms: 24 * 60 * 60 * 1000 },
{ unit: 'hour', ms: 60 * 60 * 1000 },
{ unit: 'minute', ms: 60 * 1000 },
{ unit: 'second', ms: 1000 },
];
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
for (const { unit, ms } of units) {
if (absDiff >= ms) {
const value = Math.round(diff / ms);
return rtf.format(value, unit);
}
}
return 'just now';
}
Intl.Collator
Compare and sort strings according to language-specific rules.
String Comparison
// Basic collation
const collator = new Intl.Collator('en-US');
// Sorting arrays
const names = ['Müller', 'Mueller', 'Miller'];
console.log(names.sort(collator.compare));
// ['Miller', 'Mueller', 'Müller']
// Case-insensitive comparison
const caseInsensitive = new Intl.Collator('en-US', {
sensitivity: 'base',
});
console.log(caseInsensitive.compare('a', 'A')); // 0 (equal)
console.log(caseInsensitive.compare('ä', 'a')); // 0 (equal)
// Accent-sensitive comparison
const accentSensitive = new Intl.Collator('en-US', {
sensitivity: 'accent',
});
console.log(accentSensitive.compare('a', 'A')); // 0 (equal)
console.log(accentSensitive.compare('ä', 'a')); // 1 (different)
// Numeric sorting
const numeric = new Intl.Collator('en-US', {
numeric: true,
});
const items = ['item2', 'item10', 'item1', 'item20'];
console.log(items.sort()); // ['item1', 'item10', 'item2', 'item20']
console.log(items.sort(numeric.compare)); // ['item1', 'item2', 'item10', 'item20']
// Different locales have different sort orders
const swedish = new Intl.Collator('sv-SE');
const german = new Intl.Collator('de-DE');
const letters = ['ä', 'a', 'ö', 'o', 'ü', 'u'];
console.log('Swedish:', letters.sort(swedish.compare));
console.log('German:', letters.sort(german.compare));
Advanced Collation Options
// Usage types
const search = new Intl.Collator('en-US', {
usage: 'search',
sensitivity: 'base',
});
// Search example
function searchArray(array, query) {
return array.filter(
(item) => search.compare(item.substring(0, query.length), query) === 0
);
}
const cities = ['New York', 'Newark', 'New Orleans', 'Newcastle'];
console.log(searchArray(cities, 'new')); // All cities (case-insensitive)
// Sorting with multiple criteria
function createComparator(...criteria) {
return (a, b) => {
for (const { key, collator } of criteria) {
const result = collator.compare(a[key], b[key]);
if (result !== 0) return result;
}
return 0;
};
}
const people = [
{ firstName: 'John', lastName: 'Smith' },
{ firstName: 'Jane', lastName: 'Smith' },
{ firstName: 'John', lastName: 'Brown' },
];
const comparator = createComparator(
{ key: 'lastName', collator: new Intl.Collator('en-US') },
{ key: 'firstName', collator: new Intl.Collator('en-US') }
);
console.log(people.sort(comparator));
Intl.PluralRules
Determine plural rule categories for numbers.
Plural Categories
// English plural rules
const pr = new Intl.PluralRules('en-US');
console.log(pr.select(0)); // 'other'
console.log(pr.select(1)); // 'one'
console.log(pr.select(2)); // 'other'
console.log(pr.select(100)); // 'other'
// Polish has more complex rules
const prPL = new Intl.PluralRules('pl');
console.log(prPL.select(1)); // 'one'
console.log(prPL.select(2)); // 'few'
console.log(prPL.select(5)); // 'many'
// Ordinal numbers
const ordinal = new Intl.PluralRules('en-US', { type: 'ordinal' });
console.log(ordinal.select(1)); // 'one' (1st)
console.log(ordinal.select(2)); // 'two' (2nd)
console.log(ordinal.select(3)); // 'few' (3rd)
console.log(ordinal.select(4)); // 'other' (4th)
console.log(ordinal.select(11)); // 'other' (11th)
console.log(ordinal.select(21)); // 'one' (21st)
// Practical usage: pluralization
function pluralize(count, locale, forms) {
const pr = new Intl.PluralRules(locale);
const rule = pr.select(count);
return forms[rule] || forms.other;
}
// English pluralization
const enForms = {
one: 'You have 1 message',
other: `You have ${count} messages`,
};
console.log(pluralize(0, 'en-US', enForms)); // You have 0 messages
console.log(pluralize(1, 'en-US', enForms)); // You have 1 message
console.log(pluralize(5, 'en-US', enForms)); // You have 5 messages
// Polish pluralization
const plForms = {
one: 'Masz 1 wiadomość',
few: `Masz ${count} wiadomości`,
many: `Masz ${count} wiadomości`,
other: `Masz ${count} wiadomości`,
};
Intl.ListFormat
Format lists of items in a locale-appropriate way.
List Formatting
// Basic list formatting
const list = ['Apple', 'Orange', 'Banana'];
// Conjunction (and)
const andFormatter = new Intl.ListFormat('en-US', {
style: 'long',
type: 'conjunction',
});
console.log(andFormatter.format(list)); // Apple, Orange, and Banana
// Disjunction (or)
const orFormatter = new Intl.ListFormat('en-US', {
style: 'long',
type: 'disjunction',
});
console.log(orFormatter.format(list)); // Apple, Orange, or Banana
// Unit (neither and nor or)
const unitFormatter = new Intl.ListFormat('en-US', {
style: 'long',
type: 'unit',
});
console.log(unitFormatter.format(list)); // Apple, Orange, Banana
// Different styles
const styles = ['long', 'short', 'narrow'];
styles.forEach((style) => {
const formatter = new Intl.ListFormat('en-US', {
style,
type: 'conjunction',
});
console.log(`${style}: ${formatter.format(['A', 'B', 'C'])}`);
});
// long: A, B, and C
// short: A, B, & C
// narrow: A, B, C
// Different locales
const locales = ['en-US', 'es-ES', 'fr-FR', 'de-DE'];
locales.forEach((locale) => {
const formatter = new Intl.ListFormat(locale, { type: 'conjunction' });
console.log(`${locale}: ${formatter.format(list)}`);
});
Intl.Locale
Represents a Unicode locale identifier.
Working with Locales
// Create locale objects
const locale = new Intl.Locale('en-US', {
calendar: 'gregory',
collation: 'emoji',
hourCycle: 'h12',
numberingSystem: 'latn',
});
console.log(locale.baseName); // en-US
console.log(locale.calendar); // gregory
console.log(locale.collation); // emoji
console.log(locale.hourCycle); // h12
console.log(locale.numberingSystem); // latn
// Language and region info
console.log(locale.language); // en
console.log(locale.region); // US
console.log(locale.script); // undefined
// Complex locale
const complexLocale = new Intl.Locale('zh-Hans-CN-u-nu-hanidec-ca-chinese');
console.log(complexLocale.language); // zh
console.log(complexLocale.script); // Hans
console.log(complexLocale.region); // CN
console.log(complexLocale.numberingSystem); // hanidec
console.log(complexLocale.calendar); // chinese
// Maximize and minimize locales
const minimal = new Intl.Locale('en');
const maximal = minimal.maximize();
console.log(maximal.toString()); // en-Latn-US
const maximized = new Intl.Locale('zh-CN');
const minimized = maximized.minimize();
console.log(minimized.toString()); // zh
Practical Examples
Multi-locale Formatter Class
class I18nFormatter {
constructor(locale = 'en-US') {
this.locale = locale;
this.initFormatters();
}
initFormatters() {
this.number = new Intl.NumberFormat(this.locale);
this.currency = new Intl.NumberFormat(this.locale, {
style: 'currency',
currency: this.getCurrency(),
});
this.percent = new Intl.NumberFormat(this.locale, {
style: 'percent',
});
this.date = new Intl.DateTimeFormat(this.locale);
this.time = new Intl.DateTimeFormat(this.locale, {
timeStyle: 'medium',
});
this.list = new Intl.ListFormat(this.locale);
this.relative = new Intl.RelativeTimeFormat(this.locale, {
numeric: 'auto',
});
}
getCurrency() {
const currencyMap = {
'en-US': 'USD',
'en-GB': 'GBP',
'de-DE': 'EUR',
'ja-JP': 'JPY',
'zh-CN': 'CNY',
};
return currencyMap[this.locale] || 'USD';
}
formatNumber(value) {
return this.number.format(value);
}
formatCurrency(value) {
return this.currency.format(value);
}
formatPercent(value) {
return this.percent.format(value);
}
formatDate(date) {
return this.date.format(date);
}
formatTime(date) {
return this.time.format(date);
}
formatList(items) {
return this.list.format(items);
}
formatRelativeTime(value, unit) {
return this.relative.format(value, unit);
}
formatCompactNumber(value) {
return new Intl.NumberFormat(this.locale, {
notation: 'compact',
compactDisplay: 'short',
}).format(value);
}
}
// Usage
const formatter = new I18nFormatter('en-US');
console.log(formatter.formatCurrency(1234.56)); // $1,234.56
console.log(formatter.formatPercent(0.75)); // 75%
console.log(formatter.formatCompactNumber(1500)); // 1.5K
const deFormatter = new I18nFormatter('de-DE');
console.log(deFormatter.formatCurrency(1234.56)); // 1.234,56 €
console.log(deFormatter.formatDate(new Date())); // 15.1.2024
Locale-Aware Data Display
class LocalizedDataTable {
constructor(data, locale = 'en-US') {
this.data = data;
this.locale = locale;
this.setupFormatters();
}
setupFormatters() {
this.formatters = {
number: new Intl.NumberFormat(this.locale),
currency: new Intl.NumberFormat(this.locale, {
style: 'currency',
currency: 'USD',
}),
date: new Intl.DateTimeFormat(this.locale, {
dateStyle: 'short',
}),
percent: new Intl.NumberFormat(this.locale, {
style: 'percent',
minimumFractionDigits: 1,
}),
};
}
formatCell(value, type) {
switch (type) {
case 'number':
return this.formatters.number.format(value);
case 'currency':
return this.formatters.currency.format(value);
case 'date':
return this.formatters.date.format(new Date(value));
case 'percent':
return this.formatters.percent.format(value);
default:
return value;
}
}
render() {
return this.data.map((row) => {
const formatted = {};
for (const [key, value] of Object.entries(row)) {
const type = this.getColumnType(key);
formatted[key] = this.formatCell(value, type);
}
return formatted;
});
}
getColumnType(column) {
const typeMap = {
price: 'currency',
amount: 'currency',
date: 'date',
created: 'date',
percentage: 'percent',
rate: 'percent',
count: 'number',
quantity: 'number',
};
return typeMap[column] || 'string';
}
}
// Usage
const salesData = [
{ date: '2024-01-15', amount: 1234.56, quantity: 42, rate: 0.15 },
{ date: '2024-01-16', amount: 2345.67, quantity: 58, rate: 0.22 },
];
const table = new LocalizedDataTable(salesData, 'de-DE');
console.log(table.render());
Language Switcher Implementation
class I18nManager {
constructor(defaultLocale = 'en-US') {
this.currentLocale = defaultLocale;
this.translations = {};
this.formatters = {};
this.loadTranslations();
this.initFormatters();
}
loadTranslations() {
// Simulated translations
this.translations = {
'en-US': {
welcome: 'Welcome',
items: (count) => {
const pr = new Intl.PluralRules('en-US');
const forms = {
one: `${count} item`,
other: `${count} items`,
};
return forms[pr.select(count)] || forms.other;
},
},
'es-ES': {
welcome: 'Bienvenido',
items: (count) => {
const pr = new Intl.PluralRules('es-ES');
const forms = {
one: `${count} artículo`,
other: `${count} artículos`,
};
return forms[pr.select(count)] || forms.other;
},
},
};
}
initFormatters() {
this.formatters = {
number: new Intl.NumberFormat(this.currentLocale),
currency: new Intl.NumberFormat(this.currentLocale, {
style: 'currency',
currency: this.getCurrencyForLocale(),
}),
date: new Intl.DateTimeFormat(this.currentLocale, {
dateStyle: 'long',
}),
relativeTime: new Intl.RelativeTimeFormat(this.currentLocale, {
numeric: 'auto',
}),
};
}
getCurrencyForLocale() {
const currencyMap = {
'en-US': 'USD',
'en-GB': 'GBP',
'es-ES': 'EUR',
'es-MX': 'MXN',
};
return currencyMap[this.currentLocale] || 'USD';
}
setLocale(locale) {
this.currentLocale = locale;
this.initFormatters();
}
t(key, ...args) {
const translation = this.translations[this.currentLocale]?.[key];
if (typeof translation === 'function') {
return translation(...args);
}
return translation || key;
}
format(type, value, options = {}) {
const formatter = this.formatters[type];
if (!formatter) return value;
if (type === 'relativeTime') {
return formatter.format(value, options.unit || 'day');
}
return formatter.format(value);
}
}
// Usage
const i18n = new I18nManager('en-US');
console.log(i18n.t('welcome')); // Welcome
console.log(i18n.t('items', 1)); // 1 item
console.log(i18n.t('items', 5)); // 5 items
console.log(i18n.format('currency', 1234)); // $1,234.00
i18n.setLocale('es-ES');
console.log(i18n.t('welcome')); // Bienvenido
console.log(i18n.format('currency', 1234)); // 1.234,00 €
Best Practices
- Always specify locale explicitly
// Good - explicit locale
const formatter = new Intl.NumberFormat('en-US');
// Avoid - relies on system locale
const formatter = new Intl.NumberFormat();
- Cache formatter instances
// Good - reuse formatter
const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
values.forEach((value) => {
console.log(currencyFormatter.format(value));
});
// Avoid - creating formatter in loop
values.forEach((value) => {
console.log(
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(value)
);
});
- Handle locale negotiation
function getBestLocale(requested, available) {
const negotiated = Intl.getCanonicalLocales(requested);
for (const locale of negotiated) {
if (available.includes(locale)) {
return locale;
}
// Try language-only fallback
const language = locale.split('-')[0];
const fallback = available.find((a) => a.startsWith(language));
if (fallback) return fallback;
}
return available[0]; // Default fallback
}
- Provide fallbacks for older browsers
// Check for Intl support
if (typeof Intl === 'undefined') {
// Load polyfill
// import 'intl-polyfill';
}
// Feature detection for specific APIs
const supportsListFormat = typeof Intl.ListFormat !== 'undefined';
const supportsRelativeTimeFormat =
typeof Intl.RelativeTimeFormat !== 'undefined';
// Provide fallbacks
function formatList(items, locale) {
if (supportsListFormat) {
return new Intl.ListFormat(locale).format(items);
}
// Simple fallback
return items.join(', ');
}
Conclusion
The Intl API provides powerful, standardized tools for internationalizing JavaScript applications. By leveraging these built-in formatters, you can create applications that respect users' language and regional preferences without implementing complex formatting logic yourself. From number and date formatting to pluralization and list formatting, the Intl API covers most internationalization needs, making it easier than ever to build truly global applications.