Internationalize JS apps with APIs to format dates, numbers, and strings, including pluralization and handling translations.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Cache management, configuration utilities, and performance optimization helpers. Provides infrastructure functions for managing formatter instances, configuration processing, and memory optimization.
Creates a cache object for storing formatter instances to prevent memory leaks and improve performance.
/**
* Create cache for formatter instances to prevent memory leaks
* @returns Empty cache object with all formatter storage initialized
*/
function createIntlCache(): IntlCache;
interface IntlCache {
/** DateTimeFormat instances keyed by serialized options */
dateTime: Record<string, DateTimeFormat>;
/** NumberFormat instances keyed by serialized options */
number: Record<string, Intl.NumberFormat>;
/** IntlMessageFormat instances keyed by serialized options */
message: Record<string, IntlMessageFormat>;
/** RelativeTimeFormat instances keyed by serialized options */
relativeTime: Record<string, Intl.RelativeTimeFormat>;
/** PluralRules instances keyed by serialized options */
pluralRules: Record<string, Intl.PluralRules>;
/** ListFormat instances keyed by serialized options */
list: Record<string, IntlListFormat>;
/** DisplayNames instances keyed by serialized options */
displayNames: Record<string, DisplayNames>;
}Usage Examples:
import { createIntlCache, createIntl } from "@formatjs/intl";
// Create shared cache for multiple intl instances
const cache = createIntlCache();
const intlEn = createIntl({
locale: 'en-US',
messages: { /* ... */ }
}, cache);
const intlEs = createIntl({
locale: 'es-ES',
messages: { /* ... */ }
}, cache);
// Both instances share formatter caches for memory efficiency
// Formatters with same options are reused across instances
// Cache inspection (for debugging)
console.log('Cached formatters:', {
dateTime: Object.keys(cache.dateTime).length,
number: Object.keys(cache.number).length,
message: Object.keys(cache.message).length
});
// Manual cache clearing (rarely needed)
const clearCache = (cache: IntlCache) => {
cache.dateTime = {};
cache.number = {};
cache.message = {};
cache.relativeTime = {};
cache.pluralRules = {};
cache.list = {};
cache.displayNames = {};
};Creates memoized formatter instances with cache integration for optimal performance.
/**
* Create intl formatters and populate cache
* @param cache - Optional explicit cache to prevent leaking memory
* @returns Formatters object with memoized factory functions
*/
function createFormatters(cache?: IntlCache): Formatters;
interface Formatters {
/** Memoized DateTimeFormat factory */
getDateTimeFormat(locale?: string | string[], options?: Intl.DateTimeFormatOptions): DateTimeFormat;
/** Memoized NumberFormat factory */
getNumberFormat(locales?: string | string[], opts?: NumberFormatOptions): Intl.NumberFormat;
/** Memoized MessageFormat factory */
getMessageFormat(message: string, locales?: string | string[], overrideFormats?: Partial<Formats>, opts?: IntlMessageFormatOptions): IntlMessageFormat;
/** Memoized RelativeTimeFormat factory */
getRelativeTimeFormat(locales?: string | string[], options?: Intl.RelativeTimeFormatOptions): Intl.RelativeTimeFormat;
/** Memoized PluralRules factory */
getPluralRules(locales?: string | string[], options?: Intl.PluralRulesOptions): Intl.PluralRules;
/** Memoized ListFormat factory */
getListFormat(locales?: string | string[], options?: IntlListFormatOptions): IntlListFormat;
/** Memoized DisplayNames factory */
getDisplayNames(locales?: string | string[], options?: DisplayNamesOptions): DisplayNames;
}Usage Examples:
import { createFormatters, createIntlCache } from "@formatjs/intl";
// Create formatters with default cache
const formatters = createFormatters();
// Create formatters with explicit cache
const explicitCache = createIntlCache();
const cachedFormatters = createFormatters(explicitCache);
// Use formatters directly
const numberFormatter = formatters.getNumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
const formatted = numberFormatter.format(1234.56);
// Result: "$1,234.56"
// Subsequent calls with same parameters return cached instance
const sameFormatter = formatters.getNumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
// sameFormatter === numberFormatter (same instance)
// Different parameters create new cached instance
const euroFormatter = formatters.getNumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
});
// Different instance, also cached
// Date formatter usage
const dateFormatter = formatters.getDateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
const formattedDate = dateFormatter.format(new Date());
// Result: "March 15, 2024"Filters object properties based on an allowlist with optional defaults.
/**
* Filter properties from objects with allowlist and defaults
* @param props - Source object to filter properties from
* @param allowlist - Array of allowed property names
* @param defaults - Optional default values for missing properties
* @returns New object with only allowed properties
*/
function filterProps<T extends Record<string, any>, K extends string>(
props: T,
allowlist: Array<K>,
defaults?: Partial<T>
): Pick<T, K>;Usage Examples:
import { filterProps } from "@formatjs/intl";
// Basic property filtering
const sourceObject = {
locale: 'en-US',
currency: 'USD',
style: 'currency',
unwantedProp: 'remove me',
anotherBadProp: 123
};
const allowedProps = ['locale', 'currency', 'style'] as const;
const filtered = filterProps(sourceObject, allowedProps);
// Result: { locale: 'en-US', currency: 'USD', style: 'currency' }
// With defaults for missing properties
const incompleteObject = {
locale: 'en-US',
currency: 'USD'
// missing 'style'
};
const withDefaults = filterProps(
incompleteObject,
allowedProps,
{ style: 'decimal' }
);
// Result: { locale: 'en-US', currency: 'USD', style: 'decimal' }
// Real-world usage in number formatting options
const NUMBER_FORMAT_OPTIONS = [
'style', 'currency', 'unit', 'useGrouping',
'minimumIntegerDigits', 'minimumFractionDigits',
'maximumFractionDigits', 'minimumSignificantDigits',
'maximumSignificantDigits'
] as const;
const userOptions = {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
localeMatcher: 'best fit', // Not in allowlist
invalidOption: 'remove' // Not in allowlist
};
const cleanOptions = filterProps(userOptions, NUMBER_FORMAT_OPTIONS);
// Result: { style: 'currency', currency: 'USD', minimumFractionDigits: 2 }
// TypeScript provides type safety
const typedFiltered: Pick<typeof sourceObject, 'locale' | 'currency' | 'style'> =
filterProps(sourceObject, allowedProps);Retrieves named format configurations from custom formats with error handling.
/**
* Get named format from configuration with error handling
* @param formats - Custom formats object
* @param type - Format type (number, date, time, relative)
* @param name - Named format identifier
* @param onError - Error handler for missing formats
* @returns Format options or undefined if not found
*/
function getNamedFormat<T extends keyof CustomFormats>(
formats: CustomFormats,
type: T,
name: string,
onError: OnErrorFn
): NumberFormatOptions | Intl.DateTimeFormatOptions | Intl.RelativeTimeFormatOptions | undefined;Usage Examples:
import { getNamedFormat } from "@formatjs/intl";
// Custom formats configuration
const customFormats = {
number: {
currency: { style: 'currency', currency: 'USD' },
percentage: { style: 'percent', minimumFractionDigits: 1 },
compact: { notation: 'compact' }
},
date: {
short: { month: 'numeric', day: 'numeric', year: '2-digit' },
long: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
},
relative: {
short: { numeric: 'auto', style: 'short' },
verbose: { numeric: 'always', style: 'long' }
}
};
const onError = (error: any) => console.error('Format error:', error);
// Retrieve existing named formats
const currencyFormat = getNamedFormat(customFormats, 'number', 'currency', onError);
// Result: { style: 'currency', currency: 'USD' }
const longDateFormat = getNamedFormat(customFormats, 'date', 'long', onError);
// Result: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
const shortRelativeFormat = getNamedFormat(customFormats, 'relative', 'short', onError);
// Result: { numeric: 'auto', style: 'short' }
// Handle missing named formats
const missingFormat = getNamedFormat(customFormats, 'number', 'nonexistent', onError);
// Result: undefined
// Also calls onError with UnsupportedFormatterError
// Real-world usage in formatting functions
const formatWithNamedFormat = (
value: number,
formatName: string,
locale: string = 'en-US'
) => {
const formatOptions = getNamedFormat(
customFormats,
'number',
formatName,
onError
);
if (formatOptions) {
return new Intl.NumberFormat(locale, formatOptions).format(value);
}
// Fallback to basic formatting
return new Intl.NumberFormat(locale).format(value);
};
const price = formatWithNamedFormat(29.99, 'currency');
// Result: "$29.99"
const percentage = formatWithNamedFormat(0.156, 'percentage');
// Result: "15.6%"Default configuration object providing sensible defaults for intl instances.
const DEFAULT_INTL_CONFIG: Pick<
ResolvedIntlConfig<any>,
| 'fallbackOnEmptyString'
| 'formats'
| 'messages'
| 'timeZone'
| 'defaultLocale'
| 'defaultFormats'
| 'onError'
| 'onWarn'
>;Usage Examples:
import { DEFAULT_INTL_CONFIG, createIntl } from "@formatjs/intl";
// Inspect default configuration
console.log('Default config:', DEFAULT_INTL_CONFIG);
// Result: {
// formats: {},
// messages: {},
// timeZone: undefined,
// defaultLocale: 'en',
// defaultFormats: {},
// fallbackOnEmptyString: true,
// onError: [Function: defaultErrorHandler],
// onWarn: [Function: defaultWarnHandler]
// }
// Use defaults with custom overrides
const intl = createIntl({
...DEFAULT_INTL_CONFIG,
locale: 'es-ES',
messages: {
greeting: 'Hola {name}'
},
// Other defaults are preserved
});
// Override specific defaults
const customIntl = createIntl({
locale: 'en-US',
messages: { /* ... */ },
// Override default error handling
onError: (error) => {
// Custom error handling
console.error('Custom error handler:', error);
// Send to monitoring service
if (process.env.NODE_ENV === 'production') {
sendErrorToMonitoring(error);
}
},
// Keep other defaults by not specifying them
});
// Access individual default handlers
const defaultErrorHandler = DEFAULT_INTL_CONFIG.onError;
const defaultWarnHandler = DEFAULT_INTL_CONFIG.onWarn;
// Create wrapper with enhanced defaults
const createIntlWithDefaults = (config: Partial<IntlConfig>) => {
return createIntl({
...DEFAULT_INTL_CONFIG,
defaultLocale: 'en-US', // Override default
fallbackOnEmptyString: false, // Override default
...config
});
};Additional utilities for optimizing intl performance in production applications.
Usage Examples:
// Cache warming for critical formatters
const warmCache = (cache: IntlCache, locales: string[]) => {
const formatters = createFormatters(cache);
// Pre-warm common formatters
locales.forEach(locale => {
// Common number formats
formatters.getNumberFormat(locale, { style: 'decimal' });
formatters.getNumberFormat(locale, { style: 'currency', currency: 'USD' });
formatters.getNumberFormat(locale, { style: 'percent' });
// Common date formats
formatters.getDateTimeFormat(locale, { dateStyle: 'short' });
formatters.getDateTimeFormat(locale, { timeStyle: 'short' });
// Common relative time formats
formatters.getRelativeTimeFormat(locale, { numeric: 'auto' });
// Common plural rules
formatters.getPluralRules(locale, { type: 'cardinal' });
});
};
// Usage in app initialization
const appCache = createIntlCache();
warmCache(appCache, ['en-US', 'es-ES', 'fr-FR', 'de-DE']);
// Cache metrics for monitoring
const getCacheMetrics = (cache: IntlCache) => {
return {
dateTime: Object.keys(cache.dateTime).length,
number: Object.keys(cache.number).length,
message: Object.keys(cache.message).length,
relativeTime: Object.keys(cache.relativeTime).length,
pluralRules: Object.keys(cache.pluralRules).length,
list: Object.keys(cache.list).length,
displayNames: Object.keys(cache.displayNames).length,
total: Object.keys(cache.dateTime).length +
Object.keys(cache.number).length +
Object.keys(cache.message).length +
Object.keys(cache.relativeTime).length +
Object.keys(cache.pluralRules).length +
Object.keys(cache.list).length +
Object.keys(cache.displayNames).length
};
};
// Memory management for long-running applications
const createManagedCache = (maxSize: number = 1000) => {
const cache = createIntlCache();
let totalEntries = 0;
const trackingCache = new Proxy(cache, {
set(target, prop, value) {
if (typeof prop === 'string' && prop in target) {
const category = target[prop as keyof IntlCache];
if (typeof value === 'object' && value !== null) {
Object.keys(value).forEach(key => {
if (!(key in category)) {
totalEntries++;
}
});
// Clear oldest entries if over limit
if (totalEntries > maxSize) {
const categories = Object.keys(target) as (keyof IntlCache)[];
categories.forEach(cat => {
const categoryCache = target[cat];
const keys = Object.keys(categoryCache);
if (keys.length > maxSize / categories.length) {
// Clear half the entries (simple LRU approximation)
keys.slice(0, Math.floor(keys.length / 2)).forEach(k => {
delete categoryCache[k];
totalEntries--;
});
}
});
}
}
}
return Reflect.set(target, prop, value);
}
});
return trackingCache;
};Utilities for managing error handling in intl operations.
Usage Examples:
// Enhanced error handler with categorization
const createEnhancedErrorHandler: () => OnErrorFn = () => {
const errorCounts = new Map<string, number>();
return (error) => {
const errorType = error.constructor.name;
errorCounts.set(errorType, (errorCounts.get(errorType) || 0) + 1);
// Log with context
console.error(`[Intl Error ${error.code}] ${errorType}:`, error.message);
// Handle specific error types
switch (error.code) {
case 'MISSING_TRANSLATION':
// Report missing translations to translation management system
reportMissingTranslation(error);
break;
case 'MISSING_DATA':
// Handle missing locale data
console.warn('Consider adding polyfill for:', error.message);
break;
case 'FORMAT_ERROR':
// Handle format errors
console.error('Format error details:', error);
break;
}
// Metrics reporting
if (errorCounts.get(errorType)! % 100 === 0) {
console.warn(`${errorType} has occurred ${errorCounts.get(errorType)} times`);
}
};
};
// Silent error handler for production
const createSilentErrorHandler = (): OnErrorFn => {
return (error) => {
// Only log in development
if (process.env.NODE_ENV === 'development') {
console.error('Intl error:', error);
}
// Send to monitoring in production
if (process.env.NODE_ENV === 'production') {
// Send to error monitoring service
sendToErrorMonitoring({
type: 'intl_error',
code: error.code,
message: error.message,
stack: error.stack
});
}
};
};
const reportMissingTranslation = (error: any) => {
// Implementation for reporting to translation management
console.warn('Missing translation:', error.descriptor?.id);
};
const sendToErrorMonitoring = (errorData: any) => {
// Implementation for error monitoring service
console.log('Sending to monitoring:', errorData);
};