@shelchin/svelte-i18n
Version:
The last Svelte i18n library you'll ever need. Type-safe, AI-powered, zero-config.
236 lines (235 loc) • 6.5 kB
JavaScript
/**
* Unified formatter using native Intl API
* Supports all major locales with minimal code
*/
// Cache formatters for performance
const formatterCache = new Map();
function getCacheKey(locale, type, options) {
return `${locale}:${type}:${JSON.stringify(options || {})}`;
}
/**
* Format numbers with locale-specific rules
*/
export function formatNumber(value, locale, options) {
const cacheKey = getCacheKey(locale, 'number', options);
let formatter = formatterCache.get(cacheKey);
if (!formatter) {
formatter = new Intl.NumberFormat(locale, options);
formatterCache.set(cacheKey, formatter);
}
return formatter.format(value);
}
/**
* Format currency with locale-specific rules
*/
export function formatCurrency(value, locale, currency, options) {
// Auto-detect currency based on locale if not provided
const currencyCode = currency || getCurrencyForLocale(locale);
return formatNumber(value, locale, {
style: 'currency',
currency: currencyCode,
...options
});
}
/**
* Format dates with locale-specific rules
*/
export function formatDate(date, locale, options) {
const cacheKey = getCacheKey(locale, 'date', options);
let formatter = formatterCache.get(cacheKey);
if (!formatter) {
formatter = new Intl.DateTimeFormat(locale, options);
formatterCache.set(cacheKey, formatter);
}
const dateObj = date instanceof Date ? date : new Date(date);
return formatter.format(dateObj);
}
/**
* Format time with locale-specific rules
*/
export function formatTime(date, locale, options) {
// Don't mix timeStyle with specific time component options
const defaultOptions = options?.timeStyle
? { timeStyle: options.timeStyle }
: { hour: 'numeric', minute: 'numeric' };
return formatDate(date, locale, {
...defaultOptions,
...options
});
}
/**
* Format relative time (e.g., "2 days ago", "in 3 hours")
*/
export function formatRelativeTime(value, unit, locale, options) {
const formatter = new Intl.RelativeTimeFormat(locale, options);
return formatter.format(value, unit);
}
/**
* Format lists (e.g., "A, B, and C")
*/
export function formatList(items, locale, type = 'conjunction') {
const formatter = new Intl.ListFormat(locale, { type });
return formatter.format(items);
}
/**
* Get default currency for common locales
*/
function getCurrencyForLocale(locale) {
const currencyMap = {
// Major population countries (>10M people)
'en-US': 'USD',
'en-GB': 'GBP',
'en-CA': 'CAD',
'en-AU': 'AUD',
'en-IN': 'INR',
'zh-CN': 'CNY',
'zh-TW': 'TWD',
'zh-HK': 'HKD',
ja: 'JPY',
'ja-JP': 'JPY',
de: 'EUR',
'de-DE': 'EUR',
fr: 'EUR',
'fr-FR': 'EUR',
es: 'EUR',
'es-ES': 'EUR',
'es-MX': 'MXN',
'es-AR': 'ARS',
'es-CO': 'COP',
pt: 'EUR',
'pt-BR': 'BRL',
'pt-PT': 'EUR',
ru: 'RUB',
'ru-RU': 'RUB',
ar: 'SAR',
'ar-SA': 'SAR',
'ar-EG': 'EGP',
'ar-AE': 'AED',
'hi-IN': 'INR',
'bn-BD': 'BDT',
'pa-IN': 'INR',
'ur-PK': 'PKR',
id: 'IDR',
'id-ID': 'IDR',
vi: 'VND',
'vi-VN': 'VND',
th: 'THB',
'th-TH': 'THB',
ko: 'KRW',
'ko-KR': 'KRW',
tr: 'TRY',
'tr-TR': 'TRY',
it: 'EUR',
'it-IT': 'EUR',
pl: 'PLN',
'pl-PL': 'PLN',
uk: 'UAH',
'uk-UA': 'UAH',
nl: 'EUR',
'nl-NL': 'EUR',
'fa-IR': 'IRR',
'ms-MY': 'MYR',
'fil-PH': 'PHP',
'ta-IN': 'INR',
'te-IN': 'INR',
'mr-IN': 'INR',
'gu-IN': 'INR',
'kn-IN': 'INR',
'or-IN': 'INR',
'ml-IN': 'INR',
'as-IN': 'INR',
'ne-NP': 'NPR',
'si-LK': 'LKR',
'my-MM': 'MMK',
'km-KH': 'KHR',
'lo-LA': 'LAK',
'ka-GE': 'GEL',
'az-AZ': 'AZN',
'uz-UZ': 'UZS',
'kk-KZ': 'KZT',
'ky-KG': 'KGS',
'tg-TJ': 'TJS',
'tk-TM': 'TMT',
'mn-MN': 'MNT',
'hu-HU': 'HUF',
'cs-CZ': 'CZK',
'sk-SK': 'EUR',
'bg-BG': 'BGN',
'ro-RO': 'RON',
'sr-RS': 'RSD',
'hr-HR': 'EUR',
'sl-SI': 'EUR',
'lt-LT': 'EUR',
'lv-LV': 'EUR',
'et-EE': 'EUR',
'fi-FI': 'EUR',
'sv-SE': 'SEK',
'da-DK': 'DKK',
'nb-NO': 'NOK',
'is-IS': 'ISK',
'he-IL': 'ILS',
'am-ET': 'ETB',
'ha-NG': 'NGN',
'yo-NG': 'NGN',
'ig-NG': 'NGN',
'zu-ZA': 'ZAR',
'xh-ZA': 'ZAR',
'af-ZA': 'ZAR',
'sw-KE': 'KES',
'sw-TZ': 'TZS'
};
// Try exact match
if (currencyMap[locale]) {
return currencyMap[locale];
}
// Try language code only
const langCode = locale.split('-')[0];
if (currencyMap[langCode]) {
return currencyMap[langCode];
}
// Default to USD for unknown locales
return 'USD';
}
/**
* Preset formats for common use cases
*/
export const FORMATS = {
// Number formats
number: {
default: {},
decimal: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
percent: { style: 'percent' },
compact: { notation: 'compact' },
scientific: { notation: 'scientific' },
engineering: { notation: 'engineering' }
},
// Date formats
date: {
short: { dateStyle: 'short' },
medium: { dateStyle: 'medium' },
long: { dateStyle: 'long' },
full: { dateStyle: 'full' },
yearMonth: { year: 'numeric', month: 'long' },
monthDay: { month: 'long', day: 'numeric' },
weekday: { weekday: 'long' }
},
// Time formats
time: {
short: { timeStyle: 'short' },
medium: { timeStyle: 'medium' },
long: { timeStyle: 'long' },
hour: { hour: 'numeric' },
hourMinute: { hour: 'numeric', minute: 'numeric' },
hourMinuteSecond: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
}
},
// DateTime formats
dateTime: {
short: { dateStyle: 'short', timeStyle: 'short' },
medium: { dateStyle: 'medium', timeStyle: 'medium' },
long: { dateStyle: 'long', timeStyle: 'long' }
}
};