Internationalization and Formatting APIs

1. Intl Object and Localization Services

Intl Class Purpose Browser Support
Intl.DateTimeFormat Locale-aware date and time formatting. All Browsers
Intl.NumberFormat Locale-aware number formatting (currency, percent, units). All Browsers
Intl.Collator Locale-aware string comparison and sorting. All Browsers
Intl.PluralRules Locale-aware pluralization rules. All Modern Browsers
Intl.RelativeTimeFormat Locale-aware relative time formatting ("2 days ago"). All Modern Browsers
Intl.ListFormat Locale-aware list formatting ("A, B, and C"). All Modern Browsers
Intl.DisplayNames Locale-aware display names for languages, regions, scripts. All Modern Browsers
Intl.Locale Locale identifier parsing and manipulation. All Modern Browsers
Common Method Description
Intl.getCanonicalLocales(locales) Returns canonical locale identifiers.
Intl.supportedValuesOf(key) Returns supported values for key: "calendar", "currency", "timeZone", etc.

Example: Check supported locales and values

// Get canonical locale identifiers
const locales = Intl.getCanonicalLocales(["EN-us", "en-GB", "ja-JP"]);
console.log("Canonical locales:", locales);
// Output: ["en-US", "en-GB", "ja-JP"]

// Check supported currencies
if (Intl.supportedValuesOf) {
  const currencies = Intl.supportedValuesOf("currency");
  console.log("Supported currencies:", currencies.slice(0, 10));
  // Output: ["AED", "AFN", "ALL", "AMD", "ANG", ...]
  
  // Check if specific currency is supported
  const hasEUR = currencies.includes("EUR");
  console.log("EUR supported:", hasEUR);
  
  // Get all time zones
  const timeZones = Intl.supportedValuesOf("timeZone");
  console.log("Time zones count:", timeZones.length);
  console.log("Sample time zones:", timeZones.slice(0, 5));
  // Output: ["Africa/Abidjan", "Africa/Accra", ...]
  
  // Get all calendars
  const calendars = Intl.supportedValuesOf("calendar");
  console.log("Supported calendars:", calendars);
  // Output: ["buddhist", "chinese", "gregory", "hebrew", "indian", ...]
  
  // Get all numbering systems
  const numberingSystems = Intl.supportedValuesOf("numberingSystem");
  console.log("Numbering systems:", numberingSystems.slice(0, 10));
  // Output: ["arab", "arabext", "bali", "beng", ...]
}

// Detect user's locale
const userLocale = navigator.language || navigator.userLanguage;
console.log("User locale:", userLocale);
// Output: "en-US" (or user's system locale)

// Get all preferred locales
const userLocales = navigator.languages;
console.log("User locales:", userLocales);
// Output: ["en-US", "en", "es"]
Note: Intl object provides locale-aware formatting and parsing for dates, numbers, strings, and more. Uses Unicode CLDR data. Always specify locale or use user's locale from navigator.language. All formatters accept locale and options.

2. Intl.DateTimeFormat for Date Localization

Option Values Description
dateStyle "full", "long", "medium", "short" Predefined date format style. Cannot combine with individual date/time options.
timeStyle "full", "long", "medium", "short" Predefined time format style.
year "numeric", "2-digit" Year representation.
month "numeric", "2-digit", "long", "short", "narrow" Month representation.
day "numeric", "2-digit" Day representation.
weekday "long", "short", "narrow" Weekday representation.
hour "numeric", "2-digit" Hour representation.
minute "numeric", "2-digit" Minute representation.
second "numeric", "2-digit" Second representation.
timeZone IANA time zone name Time zone: "America/New_York", "Europe/London", "UTC", etc.
hour12 true, false Use 12-hour or 24-hour time.

Example: Format dates in different locales

const date = new Date("2024-03-15T14:30:00");

// Basic formatting with dateStyle and timeStyle
const formatterUS = new Intl.DateTimeFormat("en-US", {
  "dateStyle": "full",
  "timeStyle": "long"
});
console.log("en-US:", formatterUS.format(date));
// Output: "Friday, March 15, 2024 at 2:30:00 PM"

const formatterFR = new Intl.DateTimeFormat("fr-FR", {
  "dateStyle": "full",
  "timeStyle": "long"
});
console.log("fr-FR:", formatterFR.format(date));
// Output: "vendredi 15 mars 2024 à 14:30:00"

const formatterJA = new Intl.DateTimeFormat("ja-JP", {
  "dateStyle": "full",
  "timeStyle": "long"
});
console.log("ja-JP:", formatterJA.format(date));
// Output: "2024年3月15日金曜日 14:30:00"

// Custom formatting
const customFormatter = new Intl.DateTimeFormat("en-US", {
  "year": "numeric",
  "month": "long",
  "day": "numeric",
  "weekday": "long",
  "hour": "numeric",
  "minute": "2-digit",
  "hour12": true
});
console.log("Custom:", customFormatter.format(date));
// Output: "Friday, March 15, 2024, 2:30 PM"

// Different styles
const styles = ["full", "long", "medium", "short"];
styles.forEach(style => {
  const formatter = new Intl.DateTimeFormat("en-US", { "dateStyle": style });
  console.log(`${style}:`, formatter.format(date));
});
// Output:
// full: Friday, March 15, 2024
// long: March 15, 2024
// medium: Mar 15, 2024
// short: 3/15/24

Example: Time zones and formatting parts

const date = new Date("2024-03-15T14:30:00Z");

// Format in different time zones
const timeZones = [
  "America/New_York",
  "Europe/London",
  "Asia/Tokyo",
  "Australia/Sydney"
];

timeZones.forEach(timeZone => {
  const formatter = new Intl.DateTimeFormat("en-US", {
    "timeZone": timeZone,
    "dateStyle": "medium",
    "timeStyle": "long",
    "timeZoneName": "short"
  });
  console.log(`${timeZone}:`, formatter.format(date));
});
// Output:
// America/New_York: Mar 15, 2024, 10:30:00 AM EDT
// Europe/London: Mar 15, 2024, 2:30:00 PM GMT
// Asia/Tokyo: Mar 15, 2024, 11:30:00 PM JST
// Australia/Sydney: Mar 16, 2024, 1:30:00 AM AEDT

// Get formatted parts for custom rendering
const formatter = new Intl.DateTimeFormat("en-US", {
  "year": "numeric",
  "month": "long",
  "day": "numeric",
  "hour": "numeric",
  "minute": "2-digit"
});

const parts = formatter.formatToParts(date);
console.log("Parts:", parts);
// Output: [
//   { type: "month", value: "March" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2024" },
//   ...
// ]

// Build custom format from parts
const customFormat = parts.map(part => {
  if (part.type === "month") return `<strong>${part.value}</strong>`;
  if (part.type === "day") return `<span class="day">${part.value}</span>`;
  return part.value;
}).join("");
console.log("Custom HTML:", customFormat);

// Format range
const startDate = new Date("2024-03-15");
const endDate = new Date("2024-03-20");
const range = formatter.formatRange(startDate, endDate);
console.log("Range:", range);
// Output: "March 15 – 20, 2024"
Note: Intl.DateTimeFormat provides locale-aware date/time formatting. Use dateStyle/timeStyle for quick formatting or individual options for custom formats. formatToParts() returns parts for custom rendering. formatRange() formats date ranges intelligently.

3. Intl.NumberFormat for Number Localization

Option Values Description
style "decimal", "currency", "percent", "unit" Number formatting style.
currency ISO 4217 currency code Currency: "USD", "EUR", "JPY", etc. Required if style is "currency".
currencyDisplay "symbol", "code", "name", "narrowSymbol" How to display currency.
unit Unit identifier Unit: "kilometer", "celsius", "byte", etc. Required if style is "unit".
minimumFractionDigits 0-20 Minimum decimal places.
maximumFractionDigits 0-20 Maximum decimal places.
minimumSignificantDigits 1-21 Minimum significant digits.
maximumSignificantDigits 1-21 Maximum significant digits.
notation "standard", "scientific", "engineering", "compact" Number notation style.
signDisplay "auto", "always", "exceptZero", "never" When to display sign.

Example: Format numbers, currency, and percentages

const number = 1234567.89;

// Basic number formatting
const numberUS = new Intl.NumberFormat("en-US").format(number);
console.log("en-US:", numberUS);
// Output: "1,234,567.89"

const numberDE = new Intl.NumberFormat("de-DE").format(number);
console.log("de-DE:", numberDE);
// Output: "1.234.567,89"

const numberIN = new Intl.NumberFormat("en-IN").format(number);
console.log("en-IN:", numberIN);
// Output: "12,34,567.89" (Indian grouping)

// Currency formatting
const price = 1299.99;

const usd = new Intl.NumberFormat("en-US", {
  "style": "currency",
  "currency": "USD"
}).format(price);
console.log("USD:", usd);
// Output: "$1,299.99"

const eur = new Intl.NumberFormat("de-DE", {
  "style": "currency",
  "currency": "EUR"
}).format(price);
console.log("EUR:", eur);
// Output: "1.299,99 €"

const jpy = new Intl.NumberFormat("ja-JP", {
  "style": "currency",
  "currency": "JPY"
}).format(price);
console.log("JPY:", jpy);
// Output: "¥1,300" (JPY has no decimal places)

// Currency display variations
const currencyDisplays = ["symbol", "code", "name", "narrowSymbol"];
currencyDisplays.forEach(display => {
  const formatted = new Intl.NumberFormat("en-US", {
    "style": "currency",
    "currency": "USD",
    "currencyDisplay": display
  }).format(price);
  console.log(`${display}:`, formatted);
});
// Output:
// symbol: $1,299.99
// code: USD 1,299.99
// name: 1,299.99 US dollars
// narrowSymbol: $1,299.99

// Percentage
const percent = 0.756;
const percentFormatter = new Intl.NumberFormat("en-US", {
  "style": "percent",
  "minimumFractionDigits": 1,
  "maximumFractionDigits": 1
});
console.log("Percent:", percentFormatter.format(percent));
// Output: "75.6%"

Example: Units, compact notation, and advanced formatting

// Unit formatting
const distance = 1234.5;
const distanceKm = new Intl.NumberFormat("en-US", {
  "style": "unit",
  "unit": "kilometer",
  "unitDisplay": "long"
}).format(distance);
console.log("Distance:", distanceKm);
// Output: "1,234.5 kilometers"

const temperature = 23.5;
const tempCelsius = new Intl.NumberFormat("en-US", {
  "style": "unit",
  "unit": "celsius",
  "unitDisplay": "short"
}).format(temperature);
console.log("Temperature:", tempCelsius);
// Output: "23.5°C"

// File size with bytes
const fileSize = 1234567890;
const fileSizeGB = new Intl.NumberFormat("en-US", {
  "style": "unit",
  "unit": "gigabyte",
  "unitDisplay": "narrow"
}).format(fileSize / 1e9);
console.log("File size:", fileSizeGB);
// Output: "1.23GB"

// Compact notation
const bigNumber = 1234567890;

const compact = new Intl.NumberFormat("en-US", {
  "notation": "compact",
  "compactDisplay": "short"
}).format(bigNumber);
console.log("Compact:", compact);
// Output: "1.2B"

const compactLong = new Intl.NumberFormat("en-US", {
  "notation": "compact",
  "compactDisplay": "long"
}).format(bigNumber);
console.log("Compact long:", compactLong);
// Output: "1.2 billion"

// Scientific notation
const scientific = new Intl.NumberFormat("en-US", {
  "notation": "scientific",
  "maximumFractionDigits": 2
}).format(bigNumber);
console.log("Scientific:", scientific);
// Output: "1.23E9"

// Engineering notation
const engineering = new Intl.NumberFormat("en-US", {
  "notation": "engineering"
}).format(bigNumber);
console.log("Engineering:", engineering);
// Output: "1.235E9"

// Sign display
const positiveNumber = 42;
const negativeNumber = -42;

const signAlways = new Intl.NumberFormat("en-US", {
  "signDisplay": "always"
});
console.log("Always:", signAlways.format(positiveNumber));
// Output: "+42"

const signExceptZero = new Intl.NumberFormat("en-US", {
  "signDisplay": "exceptZero"
});
console.log("ExceptZero 0:", signExceptZero.format(0));
// Output: "0"
console.log("ExceptZero +:", signExceptZero.format(positiveNumber));
// Output: "+42"
Note: Intl.NumberFormat handles numbers, currency, percentages, and units. Use style option to choose format type. Compact notation great for large numbers. formatToParts() available for custom rendering. Automatically handles locale-specific grouping and decimals.

4. Intl.Collator for String Comparison

Option Values Description
usage "sort", "search" Purpose: sorting or searching. Default: "sort".
sensitivity "base", "accent", "case", "variant" Comparison sensitivity level.
ignorePunctuation true, false Ignore punctuation in comparison.
numeric true, false Use numeric collation (e.g., "2" < "10").
caseFirst "upper", "lower", "false" Sort uppercase or lowercase first.

Example: Locale-aware string sorting

// Array to sort
const words = ["réservé", "Premier", "Communiqué", "café", "adieu"];

// Default JavaScript sort (incorrect for locales)
const defaultSort = [...words].sort();
console.log("Default sort:", defaultSort);
// Output: ["Communiqué", "Premier", "adieu", "café", "réservé"]

// Locale-aware sort
const collator = new Intl.Collator("fr-FR");
const localeSort = [...words].sort(collator.compare);
console.log("Locale sort:", localeSort);
// Output: ["adieu", "café", "Communiqué", "Premier", "réservé"]

// Case-insensitive sort
const caseInsensitive = new Intl.Collator("en-US", {
  "sensitivity": "base"
});
const names = ["Alice", "bob", "Charlie", "alice", "BOB"];
const sortedNames = [...names].sort(caseInsensitive.compare);
console.log("Case-insensitive:", sortedNames);
// Output: ["Alice", "alice", "bob", "BOB", "Charlie"]

// Numeric collation
const files = ["file1.txt", "file10.txt", "file2.txt", "file20.txt"];

const regularSort = [...files].sort();
console.log("Regular sort:", regularSort);
// Output: ["file1.txt", "file10.txt", "file2.txt", "file20.txt"] (wrong)

const numericCollator = new Intl.Collator("en-US", { "numeric": true });
const numericSort = [...files].sort(numericCollator.compare);
console.log("Numeric sort:", numericSort);
// Output: ["file1.txt", "file2.txt", "file10.txt", "file20.txt"] (correct)

Example: Search with different sensitivities

// Sensitivity levels
const searchTerm = "resume";
const documents = ["Resume", "résumé", "RESUME", "resumé"];

// Base: ignore case and accents
const baseCollator = new Intl.Collator("en-US", {
  "usage": "search",
  "sensitivity": "base"
});
const baseMatches = documents.filter(doc =>
  baseCollator.compare(doc, searchTerm) === 0
);
console.log("Base matches:", baseMatches);
// Output: ["Resume", "résumé", "RESUME", "resumé"]

// Accent: case-insensitive, accent-sensitive
const accentCollator = new Intl.Collator("en-US", {
  "usage": "search",
  "sensitivity": "accent"
});
const accentMatches = documents.filter(doc =>
  accentCollator.compare(doc, searchTerm) === 0
);
console.log("Accent matches:", accentMatches);
// Output: ["Resume", "RESUME"]

// Case: case-sensitive, accent-insensitive
const caseCollator = new Intl.Collator("en-US", {
  "usage": "search",
  "sensitivity": "case"
});
const caseMatches = documents.filter(doc =>
  caseCollator.compare(doc, searchTerm) === 0
);
console.log("Case matches:", caseMatches);
// Output: ["resume", "resumé"]

// Variant: exact match
const variantCollator = new Intl.Collator("en-US", {
  "usage": "search",
  "sensitivity": "variant"
});
const variantMatches = documents.filter(doc =>
  variantCollator.compare(doc, searchTerm) === 0
);
console.log("Variant matches:", variantMatches);
// Output: ["resume"]

// Ignore punctuation
const punctuationCollator = new Intl.Collator("en-US", {
  "ignorePunctuation": true
});
console.log(punctuationCollator.compare("co-op", "coop"));
// Output: 0 (equal)
Note: Intl.Collator provides locale-aware string comparison for sorting and searching. Use compare method with Array.sort(). Set numeric: true for natural sorting of numbers in strings. Different sensitivity levels for search scenarios.

5. Intl.PluralRules for Pluralization Logic

Method/Property Description
pluralRules.select(number) Returns plural category: "zero", "one", "two", "few", "many", "other".
pluralRules.resolvedOptions() Returns resolved options object.
Option Values Description
type "cardinal", "ordinal" Type: cardinal (1, 2, 3) or ordinal (1st, 2nd, 3rd).

Example: Pluralization rules for different locales

// English pluralization (simple: one vs other)
const enPluralRules = new Intl.PluralRules("en-US");

console.log("English:");
[0, 1, 2, 5].forEach(n => {
  console.log(`${n}: ${enPluralRules.select(n)}`);
});
// Output:
// 0: other
// 1: one
// 2: other
// 5: other

// Russian pluralization (complex: one, few, many, other)
const ruPluralRules = new Intl.PluralRules("ru-RU");

console.log("\nRussian:");
[0, 1, 2, 3, 5, 21, 22, 25, 100].forEach(n => {
  console.log(`${n}: ${ruPluralRules.select(n)}`);
});
// Output:
// 0: many
// 1: one
// 2: few
// 3: few
// 5: many
// 21: one
// 22: few
// 25: many
// 100: many

// Arabic pluralization (has zero, one, two, few, many, other)
const arPluralRules = new Intl.PluralRules("ar-EG");

console.log("\nArabic:");
[0, 1, 2, 3, 11, 100].forEach(n => {
  console.log(`${n}: ${arPluralRules.select(n)}`);
});
// Output:
// 0: zero
// 1: one
// 2: two
// 3: few
// 11: many
// 100: other

Example: Build pluralized messages

// Pluralization helper
function pluralize(locale, count, translations) {
  const pluralRules = new Intl.PluralRules(locale);
  const category = pluralRules.select(count);
  return translations[category] || translations.other;
}

// English
const enMessages = {
  "one": "You have 1 message",
  "other": "You have {count} messages"
};

[0, 1, 5].forEach(count => {
  const message = pluralize("en-US", count, enMessages)
    .replace("{count}", count);
  console.log(message);
});
// Output:
// You have 0 messages
// You have 1 message
// You have 5 messages

// Russian
const ruMessages = {
  "one": "{count} сообщение",
  "few": "{count} сообщения",
  "many": "{count} сообщений",
  "other": "{count} сообщений"
};

[1, 2, 5, 21, 22, 25].forEach(count => {
  const message = pluralize("ru-RU", count, ruMessages)
    .replace("{count}", count);
  console.log(message);
});
// Output:
// 1 сообщение
// 2 сообщения
// 5 сообщений
// 21 сообщение
// 22 сообщения
// 25 сообщений

// Ordinal numbers (1st, 2nd, 3rd)
const ordinalRules = new Intl.PluralRules("en-US", { "type": "ordinal" });

const ordinalSuffixes = {
  "one": "st",
  "two": "nd",
  "few": "rd",
  "other": "th"
};

[1, 2, 3, 4, 11, 21, 22, 23, 101, 102].forEach(n => {
  const category = ordinalRules.select(n);
  const suffix = ordinalSuffixes[category];
  console.log(`${n}${suffix}`);
});
// Output: 1st, 2nd, 3rd, 4th, 11th, 21st, 22nd, 23rd, 101st, 102nd
Note: Intl.PluralRules provides locale-specific plural categories. Different languages have different plural rules (English has 2, Russian has 4, Arabic has 6). Use select() to get category, then map to appropriate message. Use "ordinal" type for 1st, 2nd, 3rd formatting.

6. Intl.Locale for Language Tag Parsing

Property Description
locale.language Language subtag (e.g., "en", "fr", "zh").
locale.region Region subtag (e.g., "US", "GB", "CN").
locale.script Script subtag (e.g., "Latn", "Cyrl", "Hans").
locale.baseName Base locale without extensions (e.g., "en-US").
locale.calendar Calendar system (e.g., "gregory", "buddhist", "chinese").
locale.numberingSystem Numbering system (e.g., "latn", "arab", "hanidec").
locale.hourCycle Hour cycle: "h11", "h12", "h23", "h24".
locale.caseFirst Case ordering: "upper", "lower", "false".
locale.numeric Numeric collation boolean.

Example: Parse and manipulate locale identifiers

// Parse locale identifier
const locale1 = new Intl.Locale("en-US");
console.log("Language:", locale1.language);  // "en"
console.log("Region:", locale1.region);      // "US"
console.log("BaseName:", locale1.baseName);  // "en-US"

// Complex locale with extensions
const locale2 = new Intl.Locale("zh-Hans-CN-u-ca-chinese-nu-hanidec");
console.log("\nComplex locale:");
console.log("Language:", locale2.language);          // "zh"
console.log("Script:", locale2.script);              // "Hans"
console.log("Region:", locale2.region);              // "CN"
console.log("Calendar:", locale2.calendar);          // "chinese"
console.log("Numbering:", locale2.numberingSystem);  // "hanidec"
console.log("BaseName:", locale2.baseName);          // "zh-Hans-CN"
console.log("Full:", locale2.toString());            // Full identifier

// Create locale with options
const locale3 = new Intl.Locale("en", {
  "region": "GB",
  "calendar": "gregory",
  "hourCycle": "h23",
  "numberingSystem": "latn"
});
console.log("\nConstructed locale:", locale3.toString());
// Output: "en-GB-u-ca-gregory-hc-h23-nu-latn"

// Get maximized locale (add likely subtags)
const simpleLocale = new Intl.Locale("en");
if (simpleLocale.maximize) {
  const maximized = simpleLocale.maximize();
  console.log("\nMaximized:");
  console.log("Original:", simpleLocale.toString());  // "en"
  console.log("Maximized:", maximized.toString());    // "en-Latn-US"
}

// Get minimized locale (remove redundant subtags)
const complexLocale = new Intl.Locale("en-Latn-US");
if (complexLocale.minimize) {
  const minimized = complexLocale.minimize();
  console.log("\nMinimized:");
  console.log("Original:", complexLocale.toString());  // "en-Latn-US"
  console.log("Minimized:", minimized.toString());     // "en"
}

Example: Locale-specific calendar and numbering systems

// Use locale with different calendar
const gregorianLocale = new Intl.Locale("en-US", {
  "calendar": "gregory"
});

const buddhistLocale = new Intl.Locale("th-TH", {
  "calendar": "buddhist"
});

const date = new Date("2024-03-15");

const gregorianFormatter = new Intl.DateTimeFormat(gregorianLocale, {
  "year": "numeric",
  "month": "long",
  "day": "numeric"
});
console.log("Gregorian:", gregorianFormatter.format(date));
// Output: "March 15, 2024"

const buddhistFormatter = new Intl.DateTimeFormat(buddhistLocale, {
  "year": "numeric",
  "month": "long",
  "day": "numeric"
});
console.log("Buddhist:", buddhistFormatter.format(date));
// Output: "15 มีนาคม 2567" (year 2567 in Buddhist calendar)

// Different numbering systems
const arabicLocale = new Intl.Locale("ar-EG", {
  "numberingSystem": "arab"
});

const westernArabicLocale = new Intl.Locale("ar-EG", {
  "numberingSystem": "latn"
});

const number = 12345;

const arabicFormatter = new Intl.NumberFormat(arabicLocale);
console.log("Arabic digits:", arabicFormatter.format(number));
// Output: "١٢٬٣٤٥"

const latinFormatter = new Intl.NumberFormat(westernArabicLocale);
console.log("Latin digits:", latinFormatter.format(number));
// Output: "12,345"

// Get locale info
console.log("\nLocale info:");
console.log("Calendars:", Intl.supportedValuesOf("calendar"));
console.log("Numbering systems:", Intl.supportedValuesOf("numberingSystem"));
Note: Intl.Locale parses and manipulates BCP 47 language tags. Use to extract language, region, script, and extensions. maximize() adds likely subtags, minimize() removes redundant ones. Can specify calendar, numbering system, hour cycle in locale.
Warning: Locale strings must be valid BCP 47 tags - invalid tags throw errors. Not all calendar/numbering systems supported in all browsers. Use Intl.supportedValuesOf() to check available values. maximize() and minimize() have limited browser support - check before using.

Internationalization and Formatting Best Practices

  • Always use Intl APIs for locale-sensitive formatting - don't build your own
  • Get user's locale from navigator.language or navigator.languages
  • Use Intl.supportedValuesOf() to check available currencies, time zones, etc.
  • DateTimeFormat: Use dateStyle/timeStyle for quick formatting
  • NumberFormat: Always specify currency with style: "currency"
  • Use compact notation for large numbers in UI (1.2B instead of 1,234,567,890)
  • Collator: Enable numeric: true for natural sorting of filenames
  • PluralRules: Build locale-aware pluralization - languages have different rules
  • Cache Intl formatters - creating formatters is expensive
  • Use formatToParts() when you need fine-grained control over rendering
  • Test with multiple locales - especially RTL languages (ar, he)
  • Consider time zones - use IANA time zone identifiers
  • Provide fallback locale if user's locale not supported