@nexim/localizer
Version:
Lightweight i18n utilities to handle translations, number formatting, date/time localization using native browser APIs.
8 lines (7 loc) • 9.01 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/main.ts"],
"sourcesContent": ["import { createLogger } from '@alwatr/logger';\nimport { packageTracer } from '@alwatr/package-tracer';\nimport { UnicodeDigits, type UnicodeLangKeys } from '@alwatr/unicode-digits';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\n/**\n * Represents a locale code in the format \"language-COUNTRY\".\n * The language part must be in lowercase, and the country part must be in uppercase.\n *\n * @example \"en-US\"\n * @example \"fr-FR\"\n * @example \"de-DE\"\n */\nexport type LocaleCode = `${Lowercase<string>}-${Uppercase<string>}`;\n\n/**\n * Represents a locale configuration with language code and language identifier.\n */\nexport interface Locale {\n /**\n * fa-IR, en-US, ...\n */\n code: LocaleCode;\n\n /**\n * The ISO 639-1 language code (e.g., 'fa', 'en')\n */\n language: UnicodeLangKeys;\n}\n\n/**\n * Provides localization and internationalization functionality.\n *\n * @remarks\n * This class handles number formatting, text translation, date formatting,\n * and relative time calculations based on the specified locale.\n */\nexport class Localizer {\n /**\n * Number formatter instance for formatting numbers according to the locale.\n *\n * @internal\n */\n protected numberFormatter_;\n\n /**\n * UnicodeDigits instance for converting numbers to locale-specific digits.\n *\n * @internal\n */\n protected unicodeDigits_;\n\n protected logger_ = createLogger(__package_name__);\n\n /**\n * Time units used for relative time calculations.\n * @internal\n */\n static timeUnits_ = [\n { label: 'year', seconds: 31536000 },\n { label: 'month', seconds: 2592000 },\n { label: 'week', seconds: 604800 },\n { label: 'day', seconds: 86400 },\n { label: 'hour', seconds: 3600 },\n { label: 'minute', seconds: 60 },\n { label: 'second', seconds: 1 },\n ] as const;\n\n /**\n * Creates a new instance of the Localizer class.\n *\n * @param options__ - Configuration options for localization\n */\n constructor(private options__: { locale: Locale; resource: DictionaryReq | null }) {\n this.numberFormatter_ = new Intl.NumberFormat(this.options__.locale.code);\n this.unicodeDigits_ = new UnicodeDigits(this.options__.locale.language);\n }\n\n /**\n * Retrieves a localized message by key from the resource dictionary.\n *\n * @param key - The key to lookup in the resource dictionary\n * @returns The localized message string\n *\n * @remarks\n * - Returns \"\\{key\\}\" if the key is not found in the dictionary\n * - Returns an empty string if the key is null or undefined\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.message('hello_world')); // \"Hello world!\"\n * console.log(localizer.message('missing_key')); // \"{missing_key}\"\n * ```\n */\n message(key: keyof NonNullable<typeof this.options__.resource>): string {\n // this.logger_.logMethodArgs?.('message', key);\n if (!key) return '';\n\n const msg = this.options__.resource?.[key];\n if (msg === undefined) {\n this.logger_.accident('message', 'l10n_key_not_found', 'Key not defined in the localization resource', {\n key,\n locale: this.options__.locale,\n });\n return `{${key}}`;\n }\n // else\n return msg;\n }\n\n /**\n * Formats a number according to the current locale settings.\n *\n * @param number - The number to format\n * @param decimal - Number of decimal places (default: 2)\n * @returns Formatted number string using locale-specific formatting\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.number(1234.567)); // \"1,234.57\"\n * console.log(localizer.number(1234.567, 1)); // \"1,234.6\"\n * ```\n */\n number(number?: number | null, decimal = 2): string {\n if (number == null) return '';\n decimal = Math.pow(10, decimal);\n number = Math.round(number * decimal) / decimal;\n return this.numberFormatter_.format(number);\n }\n\n /**\n * Replaces all numeric digits in a string with their locale-specific Unicode equivalents.\n *\n * @param str - The input string containing numbers to replace\n * @returns String with numbers converted to locale-specific digits\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({locale: {code: 'fa-IR', language: 'fa'}, ...});\n * console.log(localizer.replaceNumber('123')); // '۱۲۳'\n * ```\n */\n replaceNumber(str: string): string {\n // this.logger_.logMethodArgs?.('replaceNumber', str);\n return this.unicodeDigits_.translate(str);\n }\n\n /**\n * Formats a date according to the current locale settings.\n *\n * @param date - Date to format (as Date object or timestamp)\n * @param options - Intl.DateTimeFormatOptions for customizing the output\n * @returns Localized date string\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.time(new Date())); // \"4/21/2025\"\n * ```\n */\n time(date: number | Date, options?: Intl.DateTimeFormatOptions): string {\n // this.logger_.logMethodArgs?.('time', { date, options });\n if (typeof date === 'number') date = new Date(date);\n return date.toLocaleDateString(this.options__.locale.code, options);\n }\n\n /**\n * Creates a relative time string comparing two dates.\n *\n * @param date - The date to compare (as Date object or timestamp)\n * @param from - Reference date for comparison (defaults to current time)\n * @param options - Formatting options for relative time\n * @returns Localized relative time string\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * const date = new Date(Date.now() - 3600000); // 1 hour ago\n * console.log(localizer.relativeTime(date)); // \"1 hour ago\"\n * ```\n */\n relativeTime(\n date: number | Date,\n from: number | Date = Date.now(),\n options: Intl.RelativeTimeFormatOptions = { numeric: 'auto', style: 'narrow' },\n ): string {\n // this.logger_.logMethodArgs?.('relativeTime', { date, from, options });\n\n const rtf = new Intl.RelativeTimeFormat(this.options__.locale.code, options);\n\n if (typeof date !== 'number') date = date.getTime();\n if (typeof from !== 'number') from = from.getTime();\n const diffSec = (from - date) / 1000;\n\n // Find the appropriate unit for the time difference\n for (const unit of Localizer.timeUnits_) {\n const interval = Math.floor(Math.abs(diffSec / unit.seconds));\n if (interval >= 1) {\n return rtf.format(diffSec > 0 ? interval : -interval, unit.label);\n }\n }\n\n return rtf.format(0, 'second');\n }\n}\n"],
"mappings": "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",
"names": []
}