@coherent.js/i18n 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -2
- package/dist/formatters.js +30 -23
- package/dist/formatters.js.map +2 -2
- package/dist/index.js +183 -52
- package/dist/index.js.map +2 -2
- package/dist/translator.js +152 -28
- package/dist/translator.js.map +2 -2
- package/package.json +1 -4
- package/types/index.d.ts +99 -10
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/translator.js", "../src/formatters.js", "../src/locale.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * Coherent.js Translator\n * \n * Handles translation of strings with interpolation and pluralization\n * \n * @module i18n/translator\n */\n\n/**\n * Translator\n * Manages translations and locale switching\n */\nexport class Translator {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n missingKeyHandler: null,\n interpolation: {\n prefix: '{{',\n suffix: '}}'\n },\n ...options\n };\n \n this.translations = new Map();\n this.currentLocale = this.options.defaultLocale;\n this.loadedLocales = new Set();\n }\n\n /**\n * Add translations for a locale\n * \n * @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')\n * @param {Object} translations - Translation object\n */\n addTranslations(locale, translations) {\n if (!this.translations.has(locale)) {\n this.translations.set(locale, {});\n }\n \n const existing = this.translations.get(locale);\n this.translations.set(locale, this.deepMerge(existing, translations));\n this.loadedLocales.add(locale);\n }\n\n /**\n * Deep merge objects\n */\n deepMerge(target, source) {\n const result = { ...target };\n \n for (const [key, value] of Object.entries(source)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = this.deepMerge(result[key] || {}, value);\n } else {\n result[key] = value;\n }\n }\n \n return result;\n }\n\n /**\n * Set current locale\n * \n * @param {string} locale - Locale code\n */\n setLocale(locale) {\n if (!this.loadedLocales.has(locale)) {\n console.warn(`Locale ${locale} not loaded, using fallback`);\n this.currentLocale = this.options.fallbackLocale;\n } else {\n this.currentLocale = locale;\n }\n }\n\n /**\n * Get current locale\n * \n * @returns {string} Current locale code\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Translate a key\n * \n * @param {string} key - Translation key (supports dot notation)\n * @param {Object} [params] - Interpolation parameters\n * @param {string} [locale] - Override locale\n * @returns {string} Translated string\n */\n t(key, params = {}, locale = null) {\n const targetLocale = locale || this.currentLocale;\n \n // Get translation\n let translation = this.getTranslation(key, targetLocale);\n \n // Fallback to default locale\n if (translation === null && targetLocale !== this.options.fallbackLocale) {\n translation = this.getTranslation(key, this.options.fallbackLocale);\n }\n \n // Handle missing translation\n if (translation === null) {\n if (this.options.missingKeyHandler) {\n return this.options.missingKeyHandler(key, targetLocale);\n }\n return key;\n }\n \n // Handle pluralization\n if (typeof translation === 'object' && params.count !== undefined) {\n translation = this.selectPlural(translation, params.count, targetLocale);\n }\n \n // Interpolate parameters\n if (typeof translation === 'string') {\n return this.interpolate(translation, params);\n }\n \n return String(translation);\n }\n\n /**\n * Get translation from nested object\n */\n getTranslation(key, locale) {\n const translations = this.translations.get(locale);\n if (!translations) return null;\n \n const keys = key.split('.');\n let value = translations;\n \n for (const k of keys) {\n if (value && typeof value === 'object' && k in value) {\n value = value[k];\n } else {\n return null;\n }\n }\n \n return value;\n }\n\n /**\n * Select plural form\n */\n selectPlural(pluralObject, count, locale) {\n // Check for explicit zero first (takes precedence over Intl rules)\n if (count === 0 && pluralObject.zero) {\n return pluralObject.zero;\n }\n \n // Use Intl.PluralRules for locale-specific pluralization\n if (typeof Intl !== 'undefined' && Intl.PluralRules) {\n const rules = new Intl.PluralRules(locale);\n const rule = rules.select(count);\n \n if (pluralObject[rule]) {\n return pluralObject[rule];\n }\n }\n \n // Fallback to simple rules\n if (count === 1 && pluralObject.one) {\n return pluralObject.one;\n } else if (pluralObject.other) {\n return pluralObject.other;\n }\n \n return pluralObject.one || pluralObject.other || '';\n }\n\n /**\n * Interpolate parameters into string\n */\n interpolate(str, params) {\n const { prefix, suffix } = this.options.interpolation;\n let result = str;\n \n for (const [key, value] of Object.entries(params)) {\n const placeholder = `${prefix}${key}${suffix}`;\n result = result.replace(new RegExp(placeholder, 'g'), String(value));\n }\n \n return result;\n }\n\n /**\n * Check if translation exists\n * \n * @param {string} key - Translation key\n * @param {string} [locale] - Locale to check\n * @returns {boolean} True if translation exists\n */\n has(key, locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.getTranslation(key, targetLocale) !== null;\n }\n\n /**\n * Get all translations for current locale\n * \n * @returns {Object} All translations\n */\n getTranslations(locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.translations.get(targetLocale) || {};\n }\n\n /**\n * Get all loaded locales\n * \n * @returns {Array<string>} Array of locale codes\n */\n getLoadedLocales() {\n return Array.from(this.loadedLocales);\n }\n\n /**\n * Remove translations for a locale\n * \n * @param {string} locale - Locale code\n */\n removeLocale(locale) {\n this.translations.delete(locale);\n this.loadedLocales.delete(locale);\n \n if (this.currentLocale === locale) {\n this.currentLocale = this.options.defaultLocale;\n }\n }\n\n /**\n * Clear all translations\n */\n clear() {\n this.translations.clear();\n this.loadedLocales.clear();\n this.currentLocale = this.options.defaultLocale;\n }\n}\n\n/**\n * Create a translator instance\n * \n * @param {Object} [options] - Translator options\n * @returns {Translator} Translator instance\n */\nexport function createTranslator(options = {}) {\n return new Translator(options);\n}\n\n/**\n * Create a scoped translator\n * Automatically prefixes all keys with a namespace\n * \n * @param {Translator} translator - Base translator\n * @param {string} namespace - Namespace prefix\n * @returns {Object} Scoped translator\n */\nexport function createScopedTranslator(translator, namespace) {\n return {\n t: (key, params, locale) => {\n return translator.t(`${namespace}.${key}`, params, locale);\n },\n has: (key, locale) => {\n return translator.has(`${namespace}.${key}`, locale);\n },\n getLocale: () => translator.getLocale(),\n setLocale: (locale) => translator.setLocale(locale)\n };\n}\n\nexport default {\n Translator,\n createTranslator,\n createScopedTranslator\n};\n", "/**\n * Coherent.js I18n Formatters\n * \n * Locale-aware formatting for dates, numbers, and currencies\n * \n * @module i18n/formatters\n */\n\n/**\n * Date Formatter\n * Formats dates according to locale\n */\nexport class DateFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a date\n * \n * @param {Date|string|number} date - Date to format\n * @param {Object} [options] - Intl.DateTimeFormat options\n * @returns {string} Formatted date\n */\n format(date, options = {}) {\n const dateObj = date instanceof Date ? date : new Date(date);\n \n if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {\n const formatter = new Intl.DateTimeFormat(this.locale, options);\n return formatter.format(dateObj);\n }\n \n // Fallback\n return dateObj.toLocaleDateString();\n }\n\n /**\n * Format date as short (e.g., 1/1/2024)\n */\n short(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as medium (e.g., Jan 1, 2024)\n */\n medium(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as long (e.g., January 1, 2024)\n */\n long(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as full (e.g., Monday, January 1, 2024)\n */\n full(date) {\n return this.format(date, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format time\n */\n time(date, options = {}) {\n return this.format(date, {\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format date and time\n */\n dateTime(date, options = {}) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format relative time (e.g., \"2 days ago\")\n */\n relative(date) {\n const dateObj = date instanceof Date ? date : new Date(date);\n const now = new Date();\n const diffMs = now - dateObj;\n const diffSec = Math.floor(diffMs / 1000);\n const diffMin = Math.floor(diffSec / 60);\n const diffHour = Math.floor(diffMin / 60);\n const diffDay = Math.floor(diffHour / 24);\n\n if (typeof Intl !== 'undefined' && Intl.RelativeTimeFormat) {\n const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: 'auto' });\n \n if (diffDay > 0) {\n return rtf.format(-diffDay, 'day');\n } else if (diffHour > 0) {\n return rtf.format(-diffHour, 'hour');\n } else if (diffMin > 0) {\n return rtf.format(-diffMin, 'minute');\n } else {\n return rtf.format(-diffSec, 'second');\n }\n }\n\n // Fallback\n if (diffDay > 0) {\n return `${diffDay} day${diffDay > 1 ? 's' : ''} ago`;\n } else if (diffHour > 0) {\n return `${diffHour} hour${diffHour > 1 ? 's' : ''} ago`;\n } else if (diffMin > 0) {\n return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`;\n } else {\n return 'just now';\n }\n }\n}\n\n/**\n * Number Formatter\n * Formats numbers according to locale\n */\nexport class NumberFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a number\n * \n * @param {number} value - Number to format\n * @param {Object} [options] - Intl.NumberFormat options\n * @returns {string} Formatted number\n */\n format(value, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, options);\n return formatter.format(value);\n }\n \n // Fallback\n return value.toLocaleString();\n }\n\n /**\n * Format as decimal\n */\n decimal(value, decimals = 2) {\n return this.format(value, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as percentage\n */\n percent(value, decimals = 0) {\n return this.format(value, {\n style: 'percent',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as compact (e.g., 1.2K, 3.4M)\n */\n compact(value) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n notation: 'compact',\n compactDisplay: 'short'\n });\n return formatter.format(value);\n } catch {\n // Fallback for older browsers\n }\n }\n \n // Manual compact formatting\n if (value >= 1e9) {\n return `${(value / 1e9).toFixed(1) }B`;\n } else if (value >= 1e6) {\n return `${(value / 1e6).toFixed(1) }M`;\n } else if (value >= 1e3) {\n return `${(value / 1e3).toFixed(1) }K`;\n }\n \n return String(value);\n }\n\n /**\n * Format with units\n */\n unit(value, unit, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit,\n ...options\n });\n return formatter.format(value);\n } catch {\n // Fallback\n }\n }\n \n return `${value} ${unit}`;\n }\n}\n\n/**\n * Currency Formatter\n * Formats currency values according to locale\n */\nexport class CurrencyFormatter {\n constructor(locale = 'en', defaultCurrency = 'USD') {\n this.locale = locale;\n this.defaultCurrency = defaultCurrency;\n }\n\n /**\n * Format a currency value\n * \n * @param {number} value - Amount to format\n * @param {string} [currency] - Currency code (e.g., 'USD', 'EUR')\n * @param {Object} [options] - Additional options\n * @returns {string} Formatted currency\n */\n format(value, currency = null, options = {}) {\n const currencyCode = currency || this.defaultCurrency;\n \n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'currency',\n currency: currencyCode,\n ...options\n });\n return formatter.format(value);\n }\n \n // Fallback\n return `${currencyCode} ${value.toFixed(2)}`;\n }\n\n /**\n * Format without decimal places\n */\n whole(value, currency = null) {\n return this.format(value, currency, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n });\n }\n\n /**\n * Format with symbol only (no code)\n */\n symbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'symbol'\n });\n }\n\n /**\n * Format with narrow symbol\n */\n narrowSymbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'narrowSymbol'\n });\n }\n\n /**\n * Format with code (e.g., USD 100.00)\n */\n code(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'code'\n });\n }\n}\n\n/**\n * List Formatter\n * Formats lists according to locale\n */\nexport class ListFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a list\n * \n * @param {Array} items - Items to format\n * @param {Object} [options] - Formatting options\n * @returns {string} Formatted list\n */\n format(items, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.ListFormat) {\n const formatter = new Intl.ListFormat(this.locale, options);\n return formatter.format(items);\n }\n \n // Fallback\n if (items.length === 0) return '';\n if (items.length === 1) return items[0];\n if (items.length === 2) return `${items[0]} and ${items[1]}`;\n \n const last = items[items.length - 1];\n const rest = items.slice(0, -1);\n return `${rest.join(', ')}, and ${last}`;\n }\n\n /**\n * Format as conjunction (and)\n */\n and(items) {\n return this.format(items, { type: 'conjunction' });\n }\n\n /**\n * Format as disjunction (or)\n */\n or(items) {\n return this.format(items, { type: 'disjunction' });\n }\n\n /**\n * Format as unit list\n */\n unit(items) {\n return this.format(items, { type: 'unit' });\n }\n}\n\n/**\n * Create formatters for a locale\n * \n * @param {string} locale - Locale code\n * @param {Object} [options] - Formatter options\n * @returns {Object} Formatter instances\n */\nexport function createFormatters(locale = 'en', options = {}) {\n return {\n date: new DateFormatter(locale),\n number: new NumberFormatter(locale),\n currency: new CurrencyFormatter(locale, options.defaultCurrency),\n list: new ListFormatter(locale)\n };\n}\n\nexport default {\n DateFormatter,\n NumberFormatter,\n CurrencyFormatter,\n ListFormatter,\n createFormatters\n};\n", "/**\n * Coherent.js Locale Utilities\n * \n * Utilities for locale detection and management\n * \n * @module i18n/locale\n */\n\n/**\n * Detect browser locale\n * \n * @returns {string} Detected locale code\n */\nexport function detectLocale() {\n if (typeof navigator !== 'undefined') {\n // Try navigator.language first\n if (navigator.language) {\n return normalizeLocale(navigator.language);\n }\n \n // Try navigator.languages array\n if (navigator.languages && navigator.languages.length > 0) {\n return normalizeLocale(navigator.languages[0]);\n }\n \n // Fallback to userLanguage (IE)\n if (navigator.userLanguage) {\n return normalizeLocale(navigator.userLanguage);\n }\n }\n \n // Default fallback\n return 'en';\n}\n\n/**\n * Normalize locale code\n * Converts various formats to standard format (e.g., 'en-US' -> 'en')\n * \n * @param {string} locale - Locale code\n * @param {boolean} [keepRegion=false] - Keep region code\n * @returns {string} Normalized locale\n */\nexport function normalizeLocale(locale, keepRegion = false) {\n if (!locale) return 'en';\n \n // Convert to lowercase and replace underscores\n let normalized = locale.toLowerCase().replace('_', '-');\n \n // Extract language code\n if (!keepRegion && normalized.includes('-')) {\n normalized = normalized.split('-')[0];\n }\n \n return normalized;\n}\n\n/**\n * Parse locale into components\n * \n * @param {string} locale - Locale code\n * @returns {Object} Parsed locale components\n */\nexport function parseLocale(locale) {\n const normalized = locale.replace('_', '-');\n const parts = normalized.split('-');\n \n return {\n language: parts[0]?.toLowerCase() || 'en',\n region: parts[1]?.toUpperCase() || null,\n script: parts.length > 2 ? parts[1] : null,\n full: normalized\n };\n}\n\n/**\n * Get locale direction (LTR or RTL)\n * \n * @param {string} locale - Locale code\n * @returns {string} 'ltr' or 'rtl'\n */\nexport function getLocaleDirection(locale) {\n const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi'];\n const language = parseLocale(locale).language;\n \n return rtlLocales.includes(language) ? 'rtl' : 'ltr';\n}\n\n/**\n * Check if locale is RTL\n * \n * @param {string} locale - Locale code\n * @returns {boolean} True if RTL\n */\nexport function isRTL(locale) {\n return getLocaleDirection(locale) === 'rtl';\n}\n\n/**\n * Get locale display name\n * \n * @param {string} locale - Locale code\n * @param {string} [displayLocale] - Locale to display name in\n * @returns {string} Display name\n */\nexport function getLocaleDisplayName(locale, displayLocale = 'en') {\n if (typeof Intl !== 'undefined' && Intl.DisplayNames) {\n try {\n const displayNames = new Intl.DisplayNames([displayLocale], { type: 'language' });\n return displayNames.of(locale);\n } catch {\n // Fallback\n }\n }\n \n // Fallback to locale code\n return locale;\n}\n\n/**\n * Match locale from available locales\n * Finds best matching locale from available options\n * \n * @param {string} requestedLocale - Requested locale\n * @param {Array<string>} availableLocales - Available locales\n * @param {string} [defaultLocale='en'] - Default fallback\n * @returns {string} Best matching locale\n */\nexport function matchLocale(requestedLocale, availableLocales, defaultLocale = 'en') {\n const normalized = normalizeLocale(requestedLocale);\n \n // Exact match\n if (availableLocales.includes(normalized)) {\n return normalized;\n }\n \n // Try with region\n const withRegion = normalizeLocale(requestedLocale, true);\n if (availableLocales.includes(withRegion)) {\n return withRegion;\n }\n \n // Try language match (ignore region)\n const language = parseLocale(requestedLocale).language;\n const languageMatch = availableLocales.find(locale => \n parseLocale(locale).language === language\n );\n \n if (languageMatch) {\n return languageMatch;\n }\n \n // Fallback to default\n return availableLocales.includes(defaultLocale) ? defaultLocale : availableLocales[0];\n}\n\n/**\n * Get supported locales from browser\n * \n * @returns {Array<string>} Array of supported locales\n */\nexport function getSupportedLocales() {\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages.map(locale => normalizeLocale(locale));\n }\n \n return [detectLocale()];\n}\n\n/**\n * Locale Manager\n * Manages locale state and persistence\n */\nexport class LocaleManager {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n availableLocales: ['en'],\n storageKey: 'coherent-locale',\n autoDetect: true,\n ...options\n };\n \n this.currentLocale = this.options.defaultLocale;\n this.listeners = [];\n \n // Auto-detect or load from storage\n if (this.options.autoDetect) {\n this.currentLocale = this.detectAndMatch();\n }\n \n this.loadFromStorage();\n }\n\n /**\n * Detect and match best locale\n */\n detectAndMatch() {\n const detected = detectLocale();\n return matchLocale(\n detected,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n }\n\n /**\n * Get current locale\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Set locale\n */\n setLocale(locale) {\n const matched = matchLocale(\n locale,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n \n if (matched !== this.currentLocale) {\n const oldLocale = this.currentLocale;\n this.currentLocale = matched;\n \n this.saveToStorage();\n this.notifyListeners(oldLocale, matched);\n }\n }\n\n /**\n * Add locale change listener\n */\n onChange(listener) {\n this.listeners.push(listener);\n \n // Return unsubscribe function\n return () => {\n const index = this.listeners.indexOf(listener);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify listeners of locale change\n */\n notifyListeners(oldLocale, newLocale) {\n this.listeners.forEach(listener => {\n try {\n listener(newLocale, oldLocale);\n } catch (error) {\n console.error('Error in locale change listener:', error);\n }\n });\n }\n\n /**\n * Save locale to storage\n */\n saveToStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(this.options.storageKey, this.currentLocale);\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Load locale from storage\n */\n loadFromStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n const stored = localStorage.getItem(this.options.storageKey);\n if (stored) {\n this.setLocale(stored);\n }\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Get available locales\n */\n getAvailableLocales() {\n return [...this.options.availableLocales];\n }\n\n /**\n * Check if locale is available\n */\n isAvailable(locale) {\n return this.options.availableLocales.includes(locale);\n }\n}\n\n/**\n * Create a locale manager\n */\nexport function createLocaleManager(options = {}) {\n return new LocaleManager(options);\n}\n\nexport default {\n detectLocale,\n normalizeLocale,\n parseLocale,\n getLocaleDirection,\n isRTL,\n getLocaleDisplayName,\n matchLocale,\n getSupportedLocales,\n LocaleManager,\n createLocaleManager\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Translator\n * \n * Handles translation of strings with interpolation and pluralization\n * \n * @module i18n/translator\n */\n\n/**\n * Escape a string for literal use inside a regular expression.\n * @param {string} value\n * @returns {string}\n */\nfunction escapeRegExp(value) {\n return String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nconst HTML_ESCAPES = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n};\n\n/**\n * Escape a value for HTML text or a quoted attribute.\n * @param {unknown} value\n * @returns {string}\n */\nfunction escapeHtml(value) {\n return String(value).replace(/[&<>\"']/g, (char) => HTML_ESCAPES[char]);\n}\n\n/**\n * The primary language subtag of a locale (`pt-BR` \u2192 `pt`).\n * @param {unknown} locale\n * @returns {string}\n */\nfunction primaryLanguage(locale) {\n return String(locale).split(/[-_]/)[0].toLowerCase();\n}\n\n/**\n * `t()`'s third argument is either a locale string (the original signature)\n * or an options object `{ locale, escape }`.\n * @param {unknown} localeOrOptions\n * @returns {{ locale?: string | null, escape?: boolean }}\n */\nfunction normalizeCallOptions(localeOrOptions) {\n if (localeOrOptions !== null && typeof localeOrOptions === 'object') {\n return localeOrOptions;\n }\n return { locale: localeOrOptions };\n}\n\n/**\n * Translator\n * Manages translations and locale switching\n */\nexport class Translator {\n constructor(options = {}) {\n const { interpolation, ...rest } = options;\n this.options = {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n missingKeyHandler: null,\n // HTML-escape interpolated params (never the translation itself).\n escape: false,\n ...rest,\n // Merged, so overriding only `prefix` keeps the default `suffix`.\n interpolation: {\n prefix: '{{',\n suffix: '}}',\n ...interpolation\n }\n };\n \n this.translations = new Map();\n this.currentLocale = this.options.defaultLocale;\n this.loadedLocales = new Set();\n }\n\n /**\n * Add translations for a locale\n * \n * @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')\n * @param {Object} translations - Translation object\n */\n addTranslations(locale, translations) {\n if (!this.translations.has(locale)) {\n this.translations.set(locale, {});\n }\n \n const existing = this.translations.get(locale);\n this.translations.set(locale, this.deepMerge(existing, translations));\n this.loadedLocales.add(locale);\n }\n\n /**\n * Deep merge objects\n */\n deepMerge(target, source) {\n const result = { ...target };\n \n for (const [key, value] of Object.entries(source)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = this.deepMerge(result[key] || {}, value);\n } else {\n result[key] = value;\n }\n }\n \n return result;\n }\n\n /**\n * Loaded locales that can serve `locale`, most specific first: the locale\n * itself, then its parents with trailing subtags dropped (`zh-Hant-TW` \u2192\n * `zh-Hant` \u2192 `zh`). Matching ignores case and accepts `_` for `-`.\n *\n * @param {string} locale\n * @returns {string[]}\n */\n localeCandidates(locale) {\n if (typeof locale !== 'string' || locale === '') return [];\n\n const byLowerCase = new Map();\n for (const loaded of this.loadedLocales) {\n byLowerCase.set(String(loaded).toLowerCase(), loaded);\n }\n\n const candidates = [];\n const parts = locale.replace(/_/g, '-').toLowerCase().split('-');\n while (parts.length > 0) {\n const match = byLowerCase.get(parts.join('-'));\n if (match !== undefined && !candidates.includes(match)) {\n candidates.push(match);\n }\n parts.pop();\n }\n return candidates;\n }\n\n /**\n * Resolve a requested locale to a loaded one (`fr-FR` \u2192 `fr` when only\n * `fr` is loaded), or `null` when neither it nor a parent is loaded.\n *\n * @param {string} locale\n * @returns {string|null}\n */\n resolveLocale(locale) {\n return this.localeCandidates(locale)[0] ?? null;\n }\n\n /**\n * Set current locale\n *\n * Resolves to the closest loaded locale (`fr-FR` \u2192 `fr`); if neither it nor\n * a parent is loaded, the fallback locale is used.\n *\n * @param {string} locale - Locale code\n */\n setLocale(locale) {\n const resolved = this.resolveLocale(locale);\n if (resolved === null) {\n console.warn(`Locale ${locale} not loaded, using fallback`);\n this.currentLocale = this.options.fallbackLocale;\n } else {\n this.currentLocale = resolved;\n }\n }\n\n /**\n * Get current locale\n * \n * @returns {string} Current locale code\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Get a translator bound to one locale, without touching the shared\n * instance's current locale.\n *\n * Use this on the server: `setLocale()` mutates `currentLocale`, which every\n * concurrent request rendering with the same instance shares, so one\n * request's locale leaks into another's output. A bound translator always\n * passes its own locale. It reads the shared translations live, so\n * translations added later are visible.\n *\n * The locale resolves like `setLocale()` (`fr-FR` \u2192 `fr`), falling back to\n * the fallback locale \u2014 silently, since request locales are untrusted input.\n *\n * @param {string} locale - Requested locale (e.g. from Accept-Language)\n * @param {{escape?: boolean}} [options] - `escape` default for this\n * translator's calls (defaults to the shared instance's `escape`)\n * @returns {{ locale: string, t: Function, has: Function, getLocale: () => string }}\n */\n forLocale(locale, options = {}) {\n const boundLocale = this.resolveLocale(locale) ?? this.options.fallbackLocale;\n\n return {\n locale: boundLocale,\n t: (key, params = {}, localeOrOptions = null) => {\n const callOptions = normalizeCallOptions(localeOrOptions);\n return this.t(key, params, {\n locale: callOptions.locale || boundLocale,\n escape: callOptions.escape ?? options.escape ?? this.options.escape\n });\n },\n has: (key, override = null) => this.has(key, override || boundLocale),\n getLocale: () => boundLocale\n };\n }\n\n /**\n * Translate a key\n *\n * @param {string} key - Translation key (supports dot notation)\n * @param {Object} [params] - Interpolation parameters\n * @param {string|{locale?: string|null, escape?: boolean}|null} [localeOrOptions]\n * Override locale, or `{ locale, escape }`. `escape: true` HTML-escapes the\n * interpolated params (defaults to the translator's `escape` option).\n * @returns {string} Translated string\n */\n t(key, params = {}, localeOrOptions = null) {\n const callOptions = normalizeCallOptions(localeOrOptions);\n const targetLocale = callOptions.locale || this.currentLocale;\n const escape = callOptions.escape ?? this.options.escape;\n params = params || {};\n\n // Look the key up in the target locale, its parents (fr-FR \u2192 fr), then\n // the fallback locale and its parents.\n const chain = [\n ...this.localeCandidates(targetLocale),\n ...this.localeCandidates(this.options.fallbackLocale)\n ];\n let translation = null;\n let messageLocale = targetLocale;\n for (const candidate of chain) {\n translation = this.getTranslation(key, candidate);\n if (translation !== null) {\n messageLocale = candidate;\n break;\n }\n }\n\n // Handle missing translation\n if (translation === null) {\n if (this.options.missingKeyHandler) {\n return this.options.missingKeyHandler(key, targetLocale);\n }\n return key;\n }\n\n // Handle pluralization with the rules of the language the message is\n // written in: English fallback text must not use Russian plural rules.\n if (typeof translation === 'object' && params.count !== undefined) {\n const pluralLocale = primaryLanguage(messageLocale) === primaryLanguage(targetLocale)\n ? targetLocale\n : messageLocale;\n translation = this.selectPlural(translation, params.count, pluralLocale);\n }\n \n // Interpolate parameters\n if (typeof translation === 'string') {\n return this.interpolate(translation, params, { escape });\n }\n \n return String(translation);\n }\n\n /**\n * Get translation from nested object\n */\n getTranslation(key, locale) {\n const translations = this.translations.get(locale);\n if (!translations) return null;\n \n const keys = String(key).split('.');\n let value = translations;\n\n for (const k of keys) {\n // Own properties only: `constructor`, `toString`, `__proto__` \u2026 are\n // inherited from Object.prototype and are not translations.\n if (value && typeof value === 'object' && Object.hasOwn(value, k)) {\n value = value[k];\n } else {\n return null;\n }\n }\n \n return value;\n }\n\n /**\n * Select plural form\n */\n selectPlural(pluralObject, count, locale) {\n // Check for explicit zero first (takes precedence over Intl rules)\n if (count === 0 && pluralObject.zero) {\n return pluralObject.zero;\n }\n \n // Use Intl.PluralRules for locale-specific pluralization\n if (typeof Intl !== 'undefined' && Intl.PluralRules) {\n let rule;\n try {\n rule = new Intl.PluralRules(locale).select(count);\n } catch {\n // Not a valid BCP 47 tag (e.g. `en_US`): use the simple rules below.\n }\n\n if (rule !== undefined && pluralObject[rule]) {\n return pluralObject[rule];\n }\n }\n \n // Fallback to simple rules\n if (count === 1 && pluralObject.one) {\n return pluralObject.one;\n } else if (pluralObject.other) {\n return pluralObject.other;\n }\n \n return pluralObject.one || pluralObject.other || '';\n }\n\n /**\n * Interpolate parameters into string\n *\n * @param {string} str - Translation template (trusted; never escaped)\n * @param {Object} params - Values for the placeholders\n * @param {{escape?: boolean}} [options] - `escape: true` HTML-escapes each\n * value; defaults to the translator's `escape` option\n */\n interpolate(str, params, options = {}) {\n if (!params || typeof params !== 'object') return str;\n\n const escape = options.escape ?? this.options.escape;\n const format = escape ? escapeHtml : String;\n\n const names = Object.keys(params);\n if (names.length === 0) return str;\n\n const { prefix, suffix } = this.options.interpolation;\n // Longest names first, so `{{ab}}` is never read as `{{a}}` + `b}}`.\n const alternatives = names\n .sort((a, b) => b.length - a.length)\n .map(escapeRegExp)\n .join('|');\n const pattern = new RegExp(`${escapeRegExp(prefix)}(${alternatives})${escapeRegExp(suffix)}`, 'g');\n\n // One pass with a replacer function: `$&`, `$'` or `$$` in a value are\n // inserted literally, and a value that itself contains a placeholder is\n // not interpolated a second time.\n return str.replace(pattern, (_match, name) => format(params[name]));\n }\n\n /**\n * Check if translation exists\n * \n * @param {string} key - Translation key\n * @param {string} [locale] - Locale to check\n * @returns {boolean} True if translation exists\n */\n has(key, locale = null) {\n const targetLocale = locale || this.currentLocale;\n // The locale and its parents (fr-FR \u2192 fr), but not the fallback locale.\n return this.localeCandidates(targetLocale)\n .some(candidate => this.getTranslation(key, candidate) !== null);\n }\n\n /**\n * Get all translations for current locale\n * \n * @returns {Object} All translations\n */\n getTranslations(locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.translations.get(targetLocale) || {};\n }\n\n /**\n * Get all loaded locales\n * \n * @returns {Array<string>} Array of locale codes\n */\n getLoadedLocales() {\n return Array.from(this.loadedLocales);\n }\n\n /**\n * Remove translations for a locale\n * \n * @param {string} locale - Locale code\n */\n removeLocale(locale) {\n this.translations.delete(locale);\n this.loadedLocales.delete(locale);\n \n if (this.currentLocale === locale) {\n this.currentLocale = this.options.defaultLocale;\n }\n }\n\n /**\n * Clear all translations\n */\n clear() {\n this.translations.clear();\n this.loadedLocales.clear();\n this.currentLocale = this.options.defaultLocale;\n }\n}\n\n/**\n * Create a translator instance\n * \n * @param {Object} [options] - Translator options\n * @returns {Translator} Translator instance\n */\nexport function createTranslator(options = {}) {\n return new Translator(options);\n}\n\n/**\n * Create a scoped translator\n * Automatically prefixes all keys with a namespace\n * \n * @param {Translator} translator - Base translator\n * @param {string} namespace - Namespace prefix\n * @returns {Object} Scoped translator\n */\nexport function createScopedTranslator(translator, namespace) {\n return {\n t: (key, params, locale) => {\n return translator.t(`${namespace}.${key}`, params, locale);\n },\n has: (key, locale) => {\n return translator.has(`${namespace}.${key}`, locale);\n },\n getLocale: () => translator.getLocale(),\n setLocale: (locale) => translator.setLocale(locale)\n };\n}\n\nexport default {\n Translator,\n createTranslator,\n createScopedTranslator\n};\n", "/**\n * Coherent.js I18n Formatters\n * \n * Locale-aware formatting for dates, numbers, and currencies\n * \n * @module i18n/formatters\n */\n\n/**\n * Units for `DateFormatter#relative`, largest first, with their length in\n * seconds (months and years approximated as 30 and 365 days).\n */\nconst RELATIVE_TIME_UNITS = [\n ['year', 365 * 24 * 60 * 60],\n ['month', 30 * 24 * 60 * 60],\n ['week', 7 * 24 * 60 * 60],\n ['day', 24 * 60 * 60],\n ['hour', 60 * 60],\n ['minute', 60]\n];\n\n/**\n * Date Formatter\n * Formats dates according to locale\n */\nexport class DateFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a date\n * \n * @param {Date|string|number} date - Date to format\n * @param {Object} [options] - Intl.DateTimeFormat options\n * @returns {string} Formatted date\n */\n format(date, options = {}) {\n const dateObj = date instanceof Date ? date : new Date(date);\n \n if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {\n const formatter = new Intl.DateTimeFormat(this.locale, options);\n return formatter.format(dateObj);\n }\n \n // Fallback\n return dateObj.toLocaleDateString();\n }\n\n /**\n * Format date as short (e.g., 1/1/2024)\n */\n short(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as medium (e.g., Jan 1, 2024)\n */\n medium(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as long (e.g., January 1, 2024)\n */\n long(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as full (e.g., Monday, January 1, 2024)\n */\n full(date) {\n return this.format(date, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format time\n */\n time(date, options = {}) {\n return this.format(date, {\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format date and time\n */\n dateTime(date, options = {}) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format relative time (e.g., \"2 days ago\", \"tomorrow\", \"in 3 weeks\")\n *\n * Works for past and future dates and uses the largest unit that fits:\n * seconds, minutes, hours, days, weeks (from 7 days), months (from 30 days)\n * or years (from 365 days). Values are truncated toward zero, so 36 hours\n * ago is \"yesterday\".\n */\n relative(date) {\n const dateObj = date instanceof Date ? date : new Date(date);\n // Negative for the past, positive for the future. Rounded to the second so\n // the milliseconds elapsed since the caller computed `date` do not turn\n // \"tomorrow\" into \"in 23 hours\".\n const diffSec = Math.round((dateObj.getTime() - Date.now()) / 1000);\n const absSec = Math.abs(diffSec);\n\n let unit = 'second';\n let amount = absSec;\n for (const [name, seconds] of RELATIVE_TIME_UNITS) {\n if (absSec >= seconds) {\n unit = name;\n amount = Math.floor(absSec / seconds);\n break;\n }\n }\n const value = diffSec < 0 ? -amount : amount;\n\n if (typeof Intl !== 'undefined' && Intl.RelativeTimeFormat) {\n const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: 'auto' });\n return rtf.format(value, unit);\n }\n\n // Fallback\n if (unit === 'second') {\n return 'just now';\n }\n const label = `${amount} ${unit}${amount > 1 ? 's' : ''}`;\n return value < 0 ? `${label} ago` : `in ${label}`;\n }\n}\n\n/**\n * Number Formatter\n * Formats numbers according to locale\n */\nexport class NumberFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a number\n * \n * @param {number} value - Number to format\n * @param {Object} [options] - Intl.NumberFormat options\n * @returns {string} Formatted number\n */\n format(value, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, options);\n return formatter.format(value);\n }\n \n // Fallback\n return value.toLocaleString();\n }\n\n /**\n * Format as decimal\n */\n decimal(value, decimals = 2) {\n return this.format(value, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as percentage\n */\n percent(value, decimals = 0) {\n return this.format(value, {\n style: 'percent',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as compact (e.g., 1.2K, 3.4M)\n */\n compact(value) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n notation: 'compact',\n compactDisplay: 'short'\n });\n return formatter.format(value);\n } catch {\n // Fallback for older browsers\n }\n }\n \n // Manual compact formatting\n if (value >= 1e9) {\n return `${(value / 1e9).toFixed(1) }B`;\n } else if (value >= 1e6) {\n return `${(value / 1e6).toFixed(1) }M`;\n } else if (value >= 1e3) {\n return `${(value / 1e3).toFixed(1) }K`;\n }\n \n return String(value);\n }\n\n /**\n * Format with units\n */\n unit(value, unit, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit,\n ...options\n });\n return formatter.format(value);\n } catch {\n // Fallback\n }\n }\n \n return `${value} ${unit}`;\n }\n}\n\n/**\n * Currency Formatter\n * Formats currency values according to locale\n */\nexport class CurrencyFormatter {\n constructor(locale = 'en', defaultCurrency = 'USD') {\n this.locale = locale;\n this.defaultCurrency = defaultCurrency;\n }\n\n /**\n * Format a currency value\n * \n * @param {number} value - Amount to format\n * @param {string} [currency] - Currency code (e.g., 'USD', 'EUR')\n * @param {Object} [options] - Additional options\n * @returns {string} Formatted currency\n */\n format(value, currency = null, options = {}) {\n const currencyCode = currency || this.defaultCurrency;\n \n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'currency',\n currency: currencyCode,\n ...options\n });\n return formatter.format(value);\n }\n \n // Fallback\n return `${currencyCode} ${value.toFixed(2)}`;\n }\n\n /**\n * Format without decimal places\n */\n whole(value, currency = null) {\n return this.format(value, currency, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n });\n }\n\n /**\n * Format with symbol only (no code)\n */\n symbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'symbol'\n });\n }\n\n /**\n * Format with narrow symbol\n */\n narrowSymbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'narrowSymbol'\n });\n }\n\n /**\n * Format with code (e.g., USD 100.00)\n */\n code(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'code'\n });\n }\n}\n\n/**\n * List Formatter\n * Formats lists according to locale\n */\nexport class ListFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a list\n * \n * @param {Array} items - Items to format\n * @param {Object} [options] - Formatting options\n * @returns {string} Formatted list\n */\n format(items, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.ListFormat) {\n const formatter = new Intl.ListFormat(this.locale, options);\n return formatter.format(items);\n }\n \n // Fallback\n if (items.length === 0) return '';\n if (items.length === 1) return items[0];\n if (items.length === 2) return `${items[0]} and ${items[1]}`;\n \n const last = items[items.length - 1];\n const rest = items.slice(0, -1);\n return `${rest.join(', ')}, and ${last}`;\n }\n\n /**\n * Format as conjunction (and)\n */\n and(items) {\n return this.format(items, { type: 'conjunction' });\n }\n\n /**\n * Format as disjunction (or)\n */\n or(items) {\n return this.format(items, { type: 'disjunction' });\n }\n\n /**\n * Format as unit list\n */\n unit(items) {\n return this.format(items, { type: 'unit' });\n }\n}\n\n/**\n * Create formatters for a locale\n * \n * @param {string} locale - Locale code\n * @param {Object} [options] - Formatter options\n * @returns {Object} Formatter instances\n */\nexport function createFormatters(locale = 'en', options = {}) {\n return {\n date: new DateFormatter(locale),\n number: new NumberFormatter(locale),\n currency: new CurrencyFormatter(locale, options.defaultCurrency),\n list: new ListFormatter(locale)\n };\n}\n\nexport default {\n DateFormatter,\n NumberFormatter,\n CurrencyFormatter,\n ListFormatter,\n createFormatters\n};\n", "/**\n * Coherent.js Locale Utilities\n * \n * Utilities for locale detection and management\n * \n * @module i18n/locale\n */\n\n/**\n * Detect browser locale\n * \n * @returns {string} Detected locale code\n */\nexport function detectLocale() {\n if (typeof navigator !== 'undefined') {\n // Try navigator.language first\n if (navigator.language) {\n return normalizeLocale(navigator.language);\n }\n \n // Try navigator.languages array\n if (navigator.languages && navigator.languages.length > 0) {\n return normalizeLocale(navigator.languages[0]);\n }\n \n // Fallback to userLanguage (IE)\n if (navigator.userLanguage) {\n return normalizeLocale(navigator.userLanguage);\n }\n }\n \n // Default fallback\n return 'en';\n}\n\n/**\n * Normalize locale code\n * Converts various formats to standard format (e.g., 'en-US' -> 'en')\n * \n * @param {string} locale - Locale code\n * @param {boolean} [keepRegion=false] - Keep region code\n * @returns {string} Normalized locale\n */\nexport function normalizeLocale(locale, keepRegion = false) {\n if (!locale) return 'en';\n \n // Convert to lowercase and replace underscores\n let normalized = locale.toLowerCase().replace('_', '-');\n \n // Extract language code\n if (!keepRegion && normalized.includes('-')) {\n normalized = normalized.split('-')[0];\n }\n \n return normalized;\n}\n\n/**\n * Parse locale into components\n * \n * @param {string} locale - Locale code\n * @returns {Object} Parsed locale components\n */\nexport function parseLocale(locale) {\n const normalized = locale.replace('_', '-');\n const parts = normalized.split('-');\n \n return {\n language: parts[0]?.toLowerCase() || 'en',\n region: parts[1]?.toUpperCase() || null,\n script: parts.length > 2 ? parts[1] : null,\n full: normalized\n };\n}\n\n/**\n * Get locale direction (LTR or RTL)\n * \n * @param {string} locale - Locale code\n * @returns {string} 'ltr' or 'rtl'\n */\nexport function getLocaleDirection(locale) {\n const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi'];\n const language = parseLocale(locale).language;\n \n return rtlLocales.includes(language) ? 'rtl' : 'ltr';\n}\n\n/**\n * Check if locale is RTL\n * \n * @param {string} locale - Locale code\n * @returns {boolean} True if RTL\n */\nexport function isRTL(locale) {\n return getLocaleDirection(locale) === 'rtl';\n}\n\n/**\n * Get locale display name\n * \n * @param {string} locale - Locale code\n * @param {string} [displayLocale] - Locale to display name in\n * @returns {string} Display name\n */\nexport function getLocaleDisplayName(locale, displayLocale = 'en') {\n if (typeof Intl !== 'undefined' && Intl.DisplayNames) {\n try {\n const displayNames = new Intl.DisplayNames([displayLocale], { type: 'language' });\n return displayNames.of(locale);\n } catch {\n // Fallback\n }\n }\n \n // Fallback to locale code\n return locale;\n}\n\n/**\n * Match locale from available locales\n * Finds best matching locale from available options\n * \n * @param {string} requestedLocale - Requested locale\n * @param {Array<string>} availableLocales - Available locales\n * @param {string} [defaultLocale='en'] - Default fallback\n * @returns {string} Best matching locale\n */\nexport function matchLocale(requestedLocale, availableLocales, defaultLocale = 'en') {\n const normalized = normalizeLocale(requestedLocale);\n \n // Exact match\n if (availableLocales.includes(normalized)) {\n return normalized;\n }\n \n // Try with region\n const withRegion = normalizeLocale(requestedLocale, true);\n if (availableLocales.includes(withRegion)) {\n return withRegion;\n }\n \n // Try language match (ignore region)\n const language = parseLocale(requestedLocale).language;\n const languageMatch = availableLocales.find(locale => \n parseLocale(locale).language === language\n );\n \n if (languageMatch) {\n return languageMatch;\n }\n \n // Fallback to default\n return availableLocales.includes(defaultLocale) ? defaultLocale : availableLocales[0];\n}\n\n/**\n * Get supported locales from browser\n * \n * @returns {Array<string>} Array of supported locales\n */\nexport function getSupportedLocales() {\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages.map(locale => normalizeLocale(locale));\n }\n \n return [detectLocale()];\n}\n\n/**\n * Locale Manager\n * Manages locale state and persistence\n */\nexport class LocaleManager {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n availableLocales: ['en'],\n storageKey: 'coherent-locale',\n autoDetect: true,\n ...options\n };\n \n this.currentLocale = this.options.defaultLocale;\n this.listeners = [];\n \n // Auto-detect or load from storage\n if (this.options.autoDetect) {\n this.currentLocale = this.detectAndMatch();\n }\n \n this.loadFromStorage();\n }\n\n /**\n * Detect and match best locale\n */\n detectAndMatch() {\n const detected = detectLocale();\n return matchLocale(\n detected,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n }\n\n /**\n * Get current locale\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Set locale\n */\n setLocale(locale) {\n const matched = matchLocale(\n locale,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n \n if (matched !== this.currentLocale) {\n const oldLocale = this.currentLocale;\n this.currentLocale = matched;\n \n this.saveToStorage();\n this.notifyListeners(oldLocale, matched);\n }\n }\n\n /**\n * Add locale change listener\n */\n onChange(listener) {\n this.listeners.push(listener);\n \n // Return unsubscribe function\n return () => {\n const index = this.listeners.indexOf(listener);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify listeners of locale change\n */\n notifyListeners(oldLocale, newLocale) {\n this.listeners.forEach(listener => {\n try {\n listener(newLocale, oldLocale);\n } catch (error) {\n console.error('Error in locale change listener:', error);\n }\n });\n }\n\n /**\n * Save locale to storage\n */\n saveToStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(this.options.storageKey, this.currentLocale);\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Load locale from storage\n */\n loadFromStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n const stored = localStorage.getItem(this.options.storageKey);\n if (stored) {\n this.setLocale(stored);\n }\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Get available locales\n */\n getAvailableLocales() {\n return [...this.options.availableLocales];\n }\n\n /**\n * Check if locale is available\n */\n isAvailable(locale) {\n return this.options.availableLocales.includes(locale);\n }\n}\n\n/**\n * Create a locale manager\n */\nexport function createLocaleManager(options = {}) {\n return new LocaleManager(options);\n}\n\nexport default {\n detectLocale,\n normalizeLocale,\n parseLocale,\n getLocaleDirection,\n isRTL,\n getLocaleDisplayName,\n matchLocale,\n getSupportedLocales,\n LocaleManager,\n createLocaleManager\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/translator.js
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
// src/translator.js
|
|
2
|
+
function escapeRegExp(value) {
|
|
3
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4
|
+
}
|
|
5
|
+
var HTML_ESCAPES = {
|
|
6
|
+
"&": "&",
|
|
7
|
+
"<": "<",
|
|
8
|
+
">": ">",
|
|
9
|
+
'"': """,
|
|
10
|
+
"'": "'"
|
|
11
|
+
};
|
|
12
|
+
function escapeHtml(value) {
|
|
13
|
+
return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
|
|
14
|
+
}
|
|
15
|
+
function primaryLanguage(locale) {
|
|
16
|
+
return String(locale).split(/[-_]/)[0].toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
function normalizeCallOptions(localeOrOptions) {
|
|
19
|
+
if (localeOrOptions !== null && typeof localeOrOptions === "object") {
|
|
20
|
+
return localeOrOptions;
|
|
21
|
+
}
|
|
22
|
+
return { locale: localeOrOptions };
|
|
23
|
+
}
|
|
2
24
|
var Translator = class {
|
|
3
25
|
constructor(options = {}) {
|
|
26
|
+
const { interpolation, ...rest } = options;
|
|
4
27
|
this.options = {
|
|
5
28
|
defaultLocale: "en",
|
|
6
29
|
fallbackLocale: "en",
|
|
7
30
|
missingKeyHandler: null,
|
|
31
|
+
// HTML-escape interpolated params (never the translation itself).
|
|
32
|
+
escape: false,
|
|
33
|
+
...rest,
|
|
34
|
+
// Merged, so overriding only `prefix` keeps the default `suffix`.
|
|
8
35
|
interpolation: {
|
|
9
36
|
prefix: "{{",
|
|
10
|
-
suffix: "}}"
|
|
11
|
-
|
|
12
|
-
|
|
37
|
+
suffix: "}}",
|
|
38
|
+
...interpolation
|
|
39
|
+
}
|
|
13
40
|
};
|
|
14
41
|
this.translations = /* @__PURE__ */ new Map();
|
|
15
42
|
this.currentLocale = this.options.defaultLocale;
|
|
@@ -43,17 +70,56 @@ var Translator = class {
|
|
|
43
70
|
}
|
|
44
71
|
return result;
|
|
45
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Loaded locales that can serve `locale`, most specific first: the locale
|
|
75
|
+
* itself, then its parents with trailing subtags dropped (`zh-Hant-TW` →
|
|
76
|
+
* `zh-Hant` → `zh`). Matching ignores case and accepts `_` for `-`.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} locale
|
|
79
|
+
* @returns {string[]}
|
|
80
|
+
*/
|
|
81
|
+
localeCandidates(locale) {
|
|
82
|
+
if (typeof locale !== "string" || locale === "") return [];
|
|
83
|
+
const byLowerCase = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const loaded of this.loadedLocales) {
|
|
85
|
+
byLowerCase.set(String(loaded).toLowerCase(), loaded);
|
|
86
|
+
}
|
|
87
|
+
const candidates = [];
|
|
88
|
+
const parts = locale.replace(/_/g, "-").toLowerCase().split("-");
|
|
89
|
+
while (parts.length > 0) {
|
|
90
|
+
const match = byLowerCase.get(parts.join("-"));
|
|
91
|
+
if (match !== void 0 && !candidates.includes(match)) {
|
|
92
|
+
candidates.push(match);
|
|
93
|
+
}
|
|
94
|
+
parts.pop();
|
|
95
|
+
}
|
|
96
|
+
return candidates;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolve a requested locale to a loaded one (`fr-FR` → `fr` when only
|
|
100
|
+
* `fr` is loaded), or `null` when neither it nor a parent is loaded.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} locale
|
|
103
|
+
* @returns {string|null}
|
|
104
|
+
*/
|
|
105
|
+
resolveLocale(locale) {
|
|
106
|
+
return this.localeCandidates(locale)[0] ?? null;
|
|
107
|
+
}
|
|
46
108
|
/**
|
|
47
109
|
* Set current locale
|
|
48
|
-
*
|
|
110
|
+
*
|
|
111
|
+
* Resolves to the closest loaded locale (`fr-FR` → `fr`); if neither it nor
|
|
112
|
+
* a parent is loaded, the fallback locale is used.
|
|
113
|
+
*
|
|
49
114
|
* @param {string} locale - Locale code
|
|
50
115
|
*/
|
|
51
116
|
setLocale(locale) {
|
|
52
|
-
|
|
117
|
+
const resolved = this.resolveLocale(locale);
|
|
118
|
+
if (resolved === null) {
|
|
53
119
|
console.warn(`Locale ${locale} not loaded, using fallback`);
|
|
54
120
|
this.currentLocale = this.options.fallbackLocale;
|
|
55
121
|
} else {
|
|
56
|
-
this.currentLocale =
|
|
122
|
+
this.currentLocale = resolved;
|
|
57
123
|
}
|
|
58
124
|
}
|
|
59
125
|
/**
|
|
@@ -64,19 +130,66 @@ var Translator = class {
|
|
|
64
130
|
getLocale() {
|
|
65
131
|
return this.currentLocale;
|
|
66
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Get a translator bound to one locale, without touching the shared
|
|
135
|
+
* instance's current locale.
|
|
136
|
+
*
|
|
137
|
+
* Use this on the server: `setLocale()` mutates `currentLocale`, which every
|
|
138
|
+
* concurrent request rendering with the same instance shares, so one
|
|
139
|
+
* request's locale leaks into another's output. A bound translator always
|
|
140
|
+
* passes its own locale. It reads the shared translations live, so
|
|
141
|
+
* translations added later are visible.
|
|
142
|
+
*
|
|
143
|
+
* The locale resolves like `setLocale()` (`fr-FR` → `fr`), falling back to
|
|
144
|
+
* the fallback locale — silently, since request locales are untrusted input.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} locale - Requested locale (e.g. from Accept-Language)
|
|
147
|
+
* @param {{escape?: boolean}} [options] - `escape` default for this
|
|
148
|
+
* translator's calls (defaults to the shared instance's `escape`)
|
|
149
|
+
* @returns {{ locale: string, t: Function, has: Function, getLocale: () => string }}
|
|
150
|
+
*/
|
|
151
|
+
forLocale(locale, options = {}) {
|
|
152
|
+
const boundLocale = this.resolveLocale(locale) ?? this.options.fallbackLocale;
|
|
153
|
+
return {
|
|
154
|
+
locale: boundLocale,
|
|
155
|
+
t: (key, params = {}, localeOrOptions = null) => {
|
|
156
|
+
const callOptions = normalizeCallOptions(localeOrOptions);
|
|
157
|
+
return this.t(key, params, {
|
|
158
|
+
locale: callOptions.locale || boundLocale,
|
|
159
|
+
escape: callOptions.escape ?? options.escape ?? this.options.escape
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
has: (key, override = null) => this.has(key, override || boundLocale),
|
|
163
|
+
getLocale: () => boundLocale
|
|
164
|
+
};
|
|
165
|
+
}
|
|
67
166
|
/**
|
|
68
167
|
* Translate a key
|
|
69
|
-
*
|
|
168
|
+
*
|
|
70
169
|
* @param {string} key - Translation key (supports dot notation)
|
|
71
170
|
* @param {Object} [params] - Interpolation parameters
|
|
72
|
-
* @param {string} [
|
|
171
|
+
* @param {string|{locale?: string|null, escape?: boolean}|null} [localeOrOptions]
|
|
172
|
+
* Override locale, or `{ locale, escape }`. `escape: true` HTML-escapes the
|
|
173
|
+
* interpolated params (defaults to the translator's `escape` option).
|
|
73
174
|
* @returns {string} Translated string
|
|
74
175
|
*/
|
|
75
|
-
t(key, params = {},
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
176
|
+
t(key, params = {}, localeOrOptions = null) {
|
|
177
|
+
const callOptions = normalizeCallOptions(localeOrOptions);
|
|
178
|
+
const targetLocale = callOptions.locale || this.currentLocale;
|
|
179
|
+
const escape = callOptions.escape ?? this.options.escape;
|
|
180
|
+
params = params || {};
|
|
181
|
+
const chain = [
|
|
182
|
+
...this.localeCandidates(targetLocale),
|
|
183
|
+
...this.localeCandidates(this.options.fallbackLocale)
|
|
184
|
+
];
|
|
185
|
+
let translation = null;
|
|
186
|
+
let messageLocale = targetLocale;
|
|
187
|
+
for (const candidate of chain) {
|
|
188
|
+
translation = this.getTranslation(key, candidate);
|
|
189
|
+
if (translation !== null) {
|
|
190
|
+
messageLocale = candidate;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
80
193
|
}
|
|
81
194
|
if (translation === null) {
|
|
82
195
|
if (this.options.missingKeyHandler) {
|
|
@@ -85,10 +198,11 @@ var Translator = class {
|
|
|
85
198
|
return key;
|
|
86
199
|
}
|
|
87
200
|
if (typeof translation === "object" && params.count !== void 0) {
|
|
88
|
-
|
|
201
|
+
const pluralLocale = primaryLanguage(messageLocale) === primaryLanguage(targetLocale) ? targetLocale : messageLocale;
|
|
202
|
+
translation = this.selectPlural(translation, params.count, pluralLocale);
|
|
89
203
|
}
|
|
90
204
|
if (typeof translation === "string") {
|
|
91
|
-
return this.interpolate(translation, params);
|
|
205
|
+
return this.interpolate(translation, params, { escape });
|
|
92
206
|
}
|
|
93
207
|
return String(translation);
|
|
94
208
|
}
|
|
@@ -98,10 +212,10 @@ var Translator = class {
|
|
|
98
212
|
getTranslation(key, locale) {
|
|
99
213
|
const translations = this.translations.get(locale);
|
|
100
214
|
if (!translations) return null;
|
|
101
|
-
const keys = key.split(".");
|
|
215
|
+
const keys = String(key).split(".");
|
|
102
216
|
let value = translations;
|
|
103
217
|
for (const k of keys) {
|
|
104
|
-
if (value && typeof value === "object" && k
|
|
218
|
+
if (value && typeof value === "object" && Object.hasOwn(value, k)) {
|
|
105
219
|
value = value[k];
|
|
106
220
|
} else {
|
|
107
221
|
return null;
|
|
@@ -117,9 +231,12 @@ var Translator = class {
|
|
|
117
231
|
return pluralObject.zero;
|
|
118
232
|
}
|
|
119
233
|
if (typeof Intl !== "undefined" && Intl.PluralRules) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
234
|
+
let rule;
|
|
235
|
+
try {
|
|
236
|
+
rule = new Intl.PluralRules(locale).select(count);
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
if (rule !== void 0 && pluralObject[rule]) {
|
|
123
240
|
return pluralObject[rule];
|
|
124
241
|
}
|
|
125
242
|
}
|
|
@@ -132,15 +249,22 @@ var Translator = class {
|
|
|
132
249
|
}
|
|
133
250
|
/**
|
|
134
251
|
* Interpolate parameters into string
|
|
252
|
+
*
|
|
253
|
+
* @param {string} str - Translation template (trusted; never escaped)
|
|
254
|
+
* @param {Object} params - Values for the placeholders
|
|
255
|
+
* @param {{escape?: boolean}} [options] - `escape: true` HTML-escapes each
|
|
256
|
+
* value; defaults to the translator's `escape` option
|
|
135
257
|
*/
|
|
136
|
-
interpolate(str, params) {
|
|
258
|
+
interpolate(str, params, options = {}) {
|
|
259
|
+
if (!params || typeof params !== "object") return str;
|
|
260
|
+
const escape = options.escape ?? this.options.escape;
|
|
261
|
+
const format = escape ? escapeHtml : String;
|
|
262
|
+
const names = Object.keys(params);
|
|
263
|
+
if (names.length === 0) return str;
|
|
137
264
|
const { prefix, suffix } = this.options.interpolation;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
result = result.replace(new RegExp(placeholder, "g"), String(value));
|
|
142
|
-
}
|
|
143
|
-
return result;
|
|
265
|
+
const alternatives = names.sort((a, b) => b.length - a.length).map(escapeRegExp).join("|");
|
|
266
|
+
const pattern = new RegExp(`${escapeRegExp(prefix)}(${alternatives})${escapeRegExp(suffix)}`, "g");
|
|
267
|
+
return str.replace(pattern, (_match, name) => format(params[name]));
|
|
144
268
|
}
|
|
145
269
|
/**
|
|
146
270
|
* Check if translation exists
|
|
@@ -151,7 +275,7 @@ var Translator = class {
|
|
|
151
275
|
*/
|
|
152
276
|
has(key, locale = null) {
|
|
153
277
|
const targetLocale = locale || this.currentLocale;
|
|
154
|
-
return this.getTranslation(key,
|
|
278
|
+
return this.localeCandidates(targetLocale).some((candidate) => this.getTranslation(key, candidate) !== null);
|
|
155
279
|
}
|
|
156
280
|
/**
|
|
157
281
|
* Get all translations for current locale
|
package/dist/translator.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/translator.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * Coherent.js Translator\n * \n * Handles translation of strings with interpolation and pluralization\n * \n * @module i18n/translator\n */\n\n/**\n * Translator\n * Manages translations and locale switching\n */\nexport class Translator {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n missingKeyHandler: null,\n interpolation: {\n prefix: '{{',\n suffix: '}}'\n },\n ...options\n };\n \n this.translations = new Map();\n this.currentLocale = this.options.defaultLocale;\n this.loadedLocales = new Set();\n }\n\n /**\n * Add translations for a locale\n * \n * @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')\n * @param {Object} translations - Translation object\n */\n addTranslations(locale, translations) {\n if (!this.translations.has(locale)) {\n this.translations.set(locale, {});\n }\n \n const existing = this.translations.get(locale);\n this.translations.set(locale, this.deepMerge(existing, translations));\n this.loadedLocales.add(locale);\n }\n\n /**\n * Deep merge objects\n */\n deepMerge(target, source) {\n const result = { ...target };\n \n for (const [key, value] of Object.entries(source)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = this.deepMerge(result[key] || {}, value);\n } else {\n result[key] = value;\n }\n }\n \n return result;\n }\n\n /**\n * Set current locale\n * \n * @param {string} locale - Locale code\n */\n setLocale(locale) {\n if (!this.loadedLocales.has(locale)) {\n console.warn(`Locale ${locale} not loaded, using fallback`);\n this.currentLocale = this.options.fallbackLocale;\n } else {\n this.currentLocale = locale;\n }\n }\n\n /**\n * Get current locale\n * \n * @returns {string} Current locale code\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Translate a key\n * \n * @param {string} key - Translation key (supports dot notation)\n * @param {Object} [params] - Interpolation parameters\n * @param {string} [locale] - Override locale\n * @returns {string} Translated string\n */\n t(key, params = {}, locale = null) {\n const targetLocale = locale || this.currentLocale;\n \n // Get translation\n let translation = this.getTranslation(key, targetLocale);\n \n // Fallback to default locale\n if (translation === null && targetLocale !== this.options.fallbackLocale) {\n translation = this.getTranslation(key, this.options.fallbackLocale);\n }\n \n // Handle missing translation\n if (translation === null) {\n if (this.options.missingKeyHandler) {\n return this.options.missingKeyHandler(key, targetLocale);\n }\n return key;\n }\n \n // Handle pluralization\n if (typeof translation === 'object' && params.count !== undefined) {\n translation = this.selectPlural(translation, params.count, targetLocale);\n }\n \n // Interpolate parameters\n if (typeof translation === 'string') {\n return this.interpolate(translation, params);\n }\n \n return String(translation);\n }\n\n /**\n * Get translation from nested object\n */\n getTranslation(key, locale) {\n const translations = this.translations.get(locale);\n if (!translations) return null;\n \n const keys = key.split('.');\n let value = translations;\n \n for (const k of keys) {\n if (value && typeof value === 'object' && k in value) {\n value = value[k];\n } else {\n return null;\n }\n }\n \n return value;\n }\n\n /**\n * Select plural form\n */\n selectPlural(pluralObject, count, locale) {\n // Check for explicit zero first (takes precedence over Intl rules)\n if (count === 0 && pluralObject.zero) {\n return pluralObject.zero;\n }\n \n // Use Intl.PluralRules for locale-specific pluralization\n if (typeof Intl !== 'undefined' && Intl.PluralRules) {\n const rules = new Intl.PluralRules(locale);\n const rule = rules.select(count);\n \n if (pluralObject[rule]) {\n return pluralObject[rule];\n }\n }\n \n // Fallback to simple rules\n if (count === 1 && pluralObject.one) {\n return pluralObject.one;\n } else if (pluralObject.other) {\n return pluralObject.other;\n }\n \n return pluralObject.one || pluralObject.other || '';\n }\n\n /**\n * Interpolate parameters into string\n */\n interpolate(str, params) {\n const { prefix, suffix } = this.options.interpolation;\n let result = str;\n \n for (const [key, value] of Object.entries(params)) {\n const placeholder = `${prefix}${key}${suffix}`;\n result = result.replace(new RegExp(placeholder, 'g'), String(value));\n }\n \n return result;\n }\n\n /**\n * Check if translation exists\n * \n * @param {string} key - Translation key\n * @param {string} [locale] - Locale to check\n * @returns {boolean} True if translation exists\n */\n has(key, locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.getTranslation(key, targetLocale) !== null;\n }\n\n /**\n * Get all translations for current locale\n * \n * @returns {Object} All translations\n */\n getTranslations(locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.translations.get(targetLocale) || {};\n }\n\n /**\n * Get all loaded locales\n * \n * @returns {Array<string>} Array of locale codes\n */\n getLoadedLocales() {\n return Array.from(this.loadedLocales);\n }\n\n /**\n * Remove translations for a locale\n * \n * @param {string} locale - Locale code\n */\n removeLocale(locale) {\n this.translations.delete(locale);\n this.loadedLocales.delete(locale);\n \n if (this.currentLocale === locale) {\n this.currentLocale = this.options.defaultLocale;\n }\n }\n\n /**\n * Clear all translations\n */\n clear() {\n this.translations.clear();\n this.loadedLocales.clear();\n this.currentLocale = this.options.defaultLocale;\n }\n}\n\n/**\n * Create a translator instance\n * \n * @param {Object} [options] - Translator options\n * @returns {Translator} Translator instance\n */\nexport function createTranslator(options = {}) {\n return new Translator(options);\n}\n\n/**\n * Create a scoped translator\n * Automatically prefixes all keys with a namespace\n * \n * @param {Translator} translator - Base translator\n * @param {string} namespace - Namespace prefix\n * @returns {Object} Scoped translator\n */\nexport function createScopedTranslator(translator, namespace) {\n return {\n t: (key, params, locale) => {\n return translator.t(`${namespace}.${key}`, params, locale);\n },\n has: (key, locale) => {\n return translator.has(`${namespace}.${key}`, locale);\n },\n getLocale: () => translator.getLocale(),\n setLocale: (locale) => translator.setLocale(locale)\n };\n}\n\nexport default {\n Translator,\n createTranslator,\n createScopedTranslator\n};\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Translator\n * \n * Handles translation of strings with interpolation and pluralization\n * \n * @module i18n/translator\n */\n\n/**\n * Escape a string for literal use inside a regular expression.\n * @param {string} value\n * @returns {string}\n */\nfunction escapeRegExp(value) {\n return String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nconst HTML_ESCAPES = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n};\n\n/**\n * Escape a value for HTML text or a quoted attribute.\n * @param {unknown} value\n * @returns {string}\n */\nfunction escapeHtml(value) {\n return String(value).replace(/[&<>\"']/g, (char) => HTML_ESCAPES[char]);\n}\n\n/**\n * The primary language subtag of a locale (`pt-BR` \u2192 `pt`).\n * @param {unknown} locale\n * @returns {string}\n */\nfunction primaryLanguage(locale) {\n return String(locale).split(/[-_]/)[0].toLowerCase();\n}\n\n/**\n * `t()`'s third argument is either a locale string (the original signature)\n * or an options object `{ locale, escape }`.\n * @param {unknown} localeOrOptions\n * @returns {{ locale?: string | null, escape?: boolean }}\n */\nfunction normalizeCallOptions(localeOrOptions) {\n if (localeOrOptions !== null && typeof localeOrOptions === 'object') {\n return localeOrOptions;\n }\n return { locale: localeOrOptions };\n}\n\n/**\n * Translator\n * Manages translations and locale switching\n */\nexport class Translator {\n constructor(options = {}) {\n const { interpolation, ...rest } = options;\n this.options = {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n missingKeyHandler: null,\n // HTML-escape interpolated params (never the translation itself).\n escape: false,\n ...rest,\n // Merged, so overriding only `prefix` keeps the default `suffix`.\n interpolation: {\n prefix: '{{',\n suffix: '}}',\n ...interpolation\n }\n };\n \n this.translations = new Map();\n this.currentLocale = this.options.defaultLocale;\n this.loadedLocales = new Set();\n }\n\n /**\n * Add translations for a locale\n * \n * @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')\n * @param {Object} translations - Translation object\n */\n addTranslations(locale, translations) {\n if (!this.translations.has(locale)) {\n this.translations.set(locale, {});\n }\n \n const existing = this.translations.get(locale);\n this.translations.set(locale, this.deepMerge(existing, translations));\n this.loadedLocales.add(locale);\n }\n\n /**\n * Deep merge objects\n */\n deepMerge(target, source) {\n const result = { ...target };\n \n for (const [key, value] of Object.entries(source)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = this.deepMerge(result[key] || {}, value);\n } else {\n result[key] = value;\n }\n }\n \n return result;\n }\n\n /**\n * Loaded locales that can serve `locale`, most specific first: the locale\n * itself, then its parents with trailing subtags dropped (`zh-Hant-TW` \u2192\n * `zh-Hant` \u2192 `zh`). Matching ignores case and accepts `_` for `-`.\n *\n * @param {string} locale\n * @returns {string[]}\n */\n localeCandidates(locale) {\n if (typeof locale !== 'string' || locale === '') return [];\n\n const byLowerCase = new Map();\n for (const loaded of this.loadedLocales) {\n byLowerCase.set(String(loaded).toLowerCase(), loaded);\n }\n\n const candidates = [];\n const parts = locale.replace(/_/g, '-').toLowerCase().split('-');\n while (parts.length > 0) {\n const match = byLowerCase.get(parts.join('-'));\n if (match !== undefined && !candidates.includes(match)) {\n candidates.push(match);\n }\n parts.pop();\n }\n return candidates;\n }\n\n /**\n * Resolve a requested locale to a loaded one (`fr-FR` \u2192 `fr` when only\n * `fr` is loaded), or `null` when neither it nor a parent is loaded.\n *\n * @param {string} locale\n * @returns {string|null}\n */\n resolveLocale(locale) {\n return this.localeCandidates(locale)[0] ?? null;\n }\n\n /**\n * Set current locale\n *\n * Resolves to the closest loaded locale (`fr-FR` \u2192 `fr`); if neither it nor\n * a parent is loaded, the fallback locale is used.\n *\n * @param {string} locale - Locale code\n */\n setLocale(locale) {\n const resolved = this.resolveLocale(locale);\n if (resolved === null) {\n console.warn(`Locale ${locale} not loaded, using fallback`);\n this.currentLocale = this.options.fallbackLocale;\n } else {\n this.currentLocale = resolved;\n }\n }\n\n /**\n * Get current locale\n * \n * @returns {string} Current locale code\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Get a translator bound to one locale, without touching the shared\n * instance's current locale.\n *\n * Use this on the server: `setLocale()` mutates `currentLocale`, which every\n * concurrent request rendering with the same instance shares, so one\n * request's locale leaks into another's output. A bound translator always\n * passes its own locale. It reads the shared translations live, so\n * translations added later are visible.\n *\n * The locale resolves like `setLocale()` (`fr-FR` \u2192 `fr`), falling back to\n * the fallback locale \u2014 silently, since request locales are untrusted input.\n *\n * @param {string} locale - Requested locale (e.g. from Accept-Language)\n * @param {{escape?: boolean}} [options] - `escape` default for this\n * translator's calls (defaults to the shared instance's `escape`)\n * @returns {{ locale: string, t: Function, has: Function, getLocale: () => string }}\n */\n forLocale(locale, options = {}) {\n const boundLocale = this.resolveLocale(locale) ?? this.options.fallbackLocale;\n\n return {\n locale: boundLocale,\n t: (key, params = {}, localeOrOptions = null) => {\n const callOptions = normalizeCallOptions(localeOrOptions);\n return this.t(key, params, {\n locale: callOptions.locale || boundLocale,\n escape: callOptions.escape ?? options.escape ?? this.options.escape\n });\n },\n has: (key, override = null) => this.has(key, override || boundLocale),\n getLocale: () => boundLocale\n };\n }\n\n /**\n * Translate a key\n *\n * @param {string} key - Translation key (supports dot notation)\n * @param {Object} [params] - Interpolation parameters\n * @param {string|{locale?: string|null, escape?: boolean}|null} [localeOrOptions]\n * Override locale, or `{ locale, escape }`. `escape: true` HTML-escapes the\n * interpolated params (defaults to the translator's `escape` option).\n * @returns {string} Translated string\n */\n t(key, params = {}, localeOrOptions = null) {\n const callOptions = normalizeCallOptions(localeOrOptions);\n const targetLocale = callOptions.locale || this.currentLocale;\n const escape = callOptions.escape ?? this.options.escape;\n params = params || {};\n\n // Look the key up in the target locale, its parents (fr-FR \u2192 fr), then\n // the fallback locale and its parents.\n const chain = [\n ...this.localeCandidates(targetLocale),\n ...this.localeCandidates(this.options.fallbackLocale)\n ];\n let translation = null;\n let messageLocale = targetLocale;\n for (const candidate of chain) {\n translation = this.getTranslation(key, candidate);\n if (translation !== null) {\n messageLocale = candidate;\n break;\n }\n }\n\n // Handle missing translation\n if (translation === null) {\n if (this.options.missingKeyHandler) {\n return this.options.missingKeyHandler(key, targetLocale);\n }\n return key;\n }\n\n // Handle pluralization with the rules of the language the message is\n // written in: English fallback text must not use Russian plural rules.\n if (typeof translation === 'object' && params.count !== undefined) {\n const pluralLocale = primaryLanguage(messageLocale) === primaryLanguage(targetLocale)\n ? targetLocale\n : messageLocale;\n translation = this.selectPlural(translation, params.count, pluralLocale);\n }\n \n // Interpolate parameters\n if (typeof translation === 'string') {\n return this.interpolate(translation, params, { escape });\n }\n \n return String(translation);\n }\n\n /**\n * Get translation from nested object\n */\n getTranslation(key, locale) {\n const translations = this.translations.get(locale);\n if (!translations) return null;\n \n const keys = String(key).split('.');\n let value = translations;\n\n for (const k of keys) {\n // Own properties only: `constructor`, `toString`, `__proto__` \u2026 are\n // inherited from Object.prototype and are not translations.\n if (value && typeof value === 'object' && Object.hasOwn(value, k)) {\n value = value[k];\n } else {\n return null;\n }\n }\n \n return value;\n }\n\n /**\n * Select plural form\n */\n selectPlural(pluralObject, count, locale) {\n // Check for explicit zero first (takes precedence over Intl rules)\n if (count === 0 && pluralObject.zero) {\n return pluralObject.zero;\n }\n \n // Use Intl.PluralRules for locale-specific pluralization\n if (typeof Intl !== 'undefined' && Intl.PluralRules) {\n let rule;\n try {\n rule = new Intl.PluralRules(locale).select(count);\n } catch {\n // Not a valid BCP 47 tag (e.g. `en_US`): use the simple rules below.\n }\n\n if (rule !== undefined && pluralObject[rule]) {\n return pluralObject[rule];\n }\n }\n \n // Fallback to simple rules\n if (count === 1 && pluralObject.one) {\n return pluralObject.one;\n } else if (pluralObject.other) {\n return pluralObject.other;\n }\n \n return pluralObject.one || pluralObject.other || '';\n }\n\n /**\n * Interpolate parameters into string\n *\n * @param {string} str - Translation template (trusted; never escaped)\n * @param {Object} params - Values for the placeholders\n * @param {{escape?: boolean}} [options] - `escape: true` HTML-escapes each\n * value; defaults to the translator's `escape` option\n */\n interpolate(str, params, options = {}) {\n if (!params || typeof params !== 'object') return str;\n\n const escape = options.escape ?? this.options.escape;\n const format = escape ? escapeHtml : String;\n\n const names = Object.keys(params);\n if (names.length === 0) return str;\n\n const { prefix, suffix } = this.options.interpolation;\n // Longest names first, so `{{ab}}` is never read as `{{a}}` + `b}}`.\n const alternatives = names\n .sort((a, b) => b.length - a.length)\n .map(escapeRegExp)\n .join('|');\n const pattern = new RegExp(`${escapeRegExp(prefix)}(${alternatives})${escapeRegExp(suffix)}`, 'g');\n\n // One pass with a replacer function: `$&`, `$'` or `$$` in a value are\n // inserted literally, and a value that itself contains a placeholder is\n // not interpolated a second time.\n return str.replace(pattern, (_match, name) => format(params[name]));\n }\n\n /**\n * Check if translation exists\n * \n * @param {string} key - Translation key\n * @param {string} [locale] - Locale to check\n * @returns {boolean} True if translation exists\n */\n has(key, locale = null) {\n const targetLocale = locale || this.currentLocale;\n // The locale and its parents (fr-FR \u2192 fr), but not the fallback locale.\n return this.localeCandidates(targetLocale)\n .some(candidate => this.getTranslation(key, candidate) !== null);\n }\n\n /**\n * Get all translations for current locale\n * \n * @returns {Object} All translations\n */\n getTranslations(locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.translations.get(targetLocale) || {};\n }\n\n /**\n * Get all loaded locales\n * \n * @returns {Array<string>} Array of locale codes\n */\n getLoadedLocales() {\n return Array.from(this.loadedLocales);\n }\n\n /**\n * Remove translations for a locale\n * \n * @param {string} locale - Locale code\n */\n removeLocale(locale) {\n this.translations.delete(locale);\n this.loadedLocales.delete(locale);\n \n if (this.currentLocale === locale) {\n this.currentLocale = this.options.defaultLocale;\n }\n }\n\n /**\n * Clear all translations\n */\n clear() {\n this.translations.clear();\n this.loadedLocales.clear();\n this.currentLocale = this.options.defaultLocale;\n }\n}\n\n/**\n * Create a translator instance\n * \n * @param {Object} [options] - Translator options\n * @returns {Translator} Translator instance\n */\nexport function createTranslator(options = {}) {\n return new Translator(options);\n}\n\n/**\n * Create a scoped translator\n * Automatically prefixes all keys with a namespace\n * \n * @param {Translator} translator - Base translator\n * @param {string} namespace - Namespace prefix\n * @returns {Object} Scoped translator\n */\nexport function createScopedTranslator(translator, namespace) {\n return {\n t: (key, params, locale) => {\n return translator.t(`${namespace}.${key}`, params, locale);\n },\n has: (key, locale) => {\n return translator.has(`${namespace}.${key}`, locale);\n },\n getLocale: () => translator.getLocale(),\n setLocale: (locale) => translator.setLocale(locale)\n };\n}\n\nexport default {\n Translator,\n createTranslator,\n createScopedTranslator\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coherent.js/i18n",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Internationalization support for Coherent.js applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,9 +22,6 @@
|
|
|
22
22
|
],
|
|
23
23
|
"author": "Coherent.js Team",
|
|
24
24
|
"license": "MIT",
|
|
25
|
-
"peerDependencies": {
|
|
26
|
-
"@coherent.js/core": "^1.1.2"
|
|
27
|
-
},
|
|
28
25
|
"repository": {
|
|
29
26
|
"type": "git",
|
|
30
27
|
"url": "git+https://github.com/Tomdrouv1/coherent.js.git"
|