@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
1 lines • 16.6 kB
Source Map (JSON)
{"version":3,"file":"localization.cjs","sources":["../../../src/config/localization.ts"],"sourcesContent":["/**\n * @frank-auth/react - Localization Configuration\n *\n * Comprehensive internationalization system with support for multiple locales,\n * dynamic loading, pluralization, and context-aware translations.\n */\n\nimport type {Locale, LocaleDirection, LocalizationConfig,} from './types';\n\nimport {DEFAULT_LOCALE_MESSAGES, DEFAULT_LOCALIZATION_CONFIG} from './defaults';\nimport {LOCALE_INFO, type LocaleMessages} from \"@/locales\";\n\n// ============================================================================\n// Translation Keys and Type Safety\n// ============================================================================\n\n/**\n * Deep key paths for type-safe translations\n */\ntype DeepKeyOf<T> = T extends object ? {\n [K in keyof T]: K extends string ? T[K] extends object\n ? `${K}.${DeepKeyOf<T[K]>}`\n : K\n : never\n}[keyof T] : never;\n\nexport type TranslationKey = DeepKeyOf<LocaleMessages>;\n\n/**\n * Interpolation values for translations\n */\nexport interface InterpolationValues {\n [key: string]: string | number | boolean | Date;\n}\n\n/**\n * Pluralization options\n */\nexport interface PluralOptions {\n count: number;\n zero?: string;\n one?: string;\n two?: string;\n few?: string;\n many?: string;\n other: string;\n}\n\n\n// ============================================================================\n// Localization Manager Class\n// ============================================================================\n\nexport class LocalizationManager {\n private config: LocalizationConfig;\n private currentLocale: Locale;\n private loadedMessages: Map<Locale, LocaleMessages> = new Map();\n private listeners: Set<(locale: Locale) => void> = new Set();\n\n constructor(initialConfig?: Partial<LocalizationConfig>) {\n this.config = { ...DEFAULT_LOCALIZATION_CONFIG, ...initialConfig };\n this.currentLocale = this.config.defaultLocale;\n\n // Load default locale messages\n this.loadedMessages.set(this.currentLocale, {\n ...LOCALE_INFO[this.currentLocale],\n ...this.config.messages,\n });\n }\n\n /**\n * Get current locale\n */\n getCurrentLocale(): Locale {\n return this.currentLocale;\n }\n\n /**\n * Get current locale metadata\n */\n getCurrentLocaleMetadata() {\n return LOCALE_INFO[this.currentLocale];\n }\n\n /**\n * Set current locale\n */\n async setLocale(locale: Locale): Promise<void> {\n if (!this.config.supportedLocales.includes(locale)) {\n console.warn(`Locale ${locale} is not supported. Falling back to ${this.config.fallbackLocale}`);\n locale = this.config.fallbackLocale;\n }\n\n this.currentLocale = locale;\n\n // Load messages if not already loaded\n if (!this.loadedMessages.has(locale)) {\n await this.loadLocaleMessages(locale);\n }\n\n this.notifyListeners();\n }\n\n /**\n * Get translation for a key\n */\n t(key: TranslationKey, interpolation?: InterpolationValues): string {\n const messages = this.getCurrentMessages();\n const value = this.getNestedValue(messages, key);\n\n if (typeof value !== 'string') {\n console.warn(`Translation key \"${key}\" not found for locale \"${this.currentLocale}\"`);\n return key;\n }\n\n return this.interpolate(value, interpolation);\n }\n\n /**\n * Get plural translation\n */\n plural(key: string, options: PluralOptions): string {\n const { count } = options;\n const metadata = LOCALE_INFO[this.currentLocale];\n const rule = metadata.pluralRules.select(count);\n\n let pluralKey: string;\n\n // Handle different plural forms\n if (count === 0 && options.zero) {\n return this.interpolate(options.zero, { count });\n }\n\n switch (rule) {\n case 'one':\n pluralKey = options.one || options.other;\n break;\n case 'two':\n pluralKey = options.two || options.other;\n break;\n case 'few':\n pluralKey = options.few || options.other;\n break;\n case 'many':\n pluralKey = options.many || options.other;\n break;\n default:\n pluralKey = options.other;\n }\n\n return this.interpolate(pluralKey, { count });\n }\n\n /**\n * Format date according to current locale\n */\n formatDate(date: Date, options?: Intl.DateTimeFormatOptions): string {\n const metadata = LOCALE_INFO[this.currentLocale];\n return new Intl.DateTimeFormat(this.currentLocale, {\n ...options,\n ...(options || {}),\n }).format(date);\n }\n\n /**\n * Format time according to current locale\n */\n formatTime(date: Date, options?: Intl.DateTimeFormatOptions): string {\n return new Intl.DateTimeFormat(this.currentLocale, {\n timeStyle: 'short',\n ...options,\n }).format(date);\n }\n\n /**\n * Format number according to current locale\n */\n formatNumber(number: number, options?: Intl.NumberFormatOptions): string {\n const metadata = LOCALE_INFO[this.currentLocale];\n return new Intl.NumberFormat(this.currentLocale, {\n ...metadata.numberFormat,\n ...options,\n }).format(number);\n }\n\n /**\n * Format currency according to current locale\n */\n formatCurrency(amount: number, currency: string, options?: Intl.NumberFormatOptions): string {\n return new Intl.NumberFormat(this.currentLocale, {\n style: 'currency',\n currency,\n ...options,\n }).format(amount);\n }\n\n /**\n * Format relative time (e.g., \"2 hours ago\")\n */\n formatRelativeTime(date: Date, options?: Intl.RelativeTimeFormatOptions): string {\n const rtf = new Intl.RelativeTimeFormat(this.currentLocale, {\n numeric: 'auto',\n ...options,\n });\n\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n const diffSec = Math.round(diffMs / 1000);\n const diffMin = Math.round(diffSec / 60);\n const diffHour = Math.round(diffMin / 60);\n const diffDay = Math.round(diffHour / 24);\n\n if (Math.abs(diffSec) < 60) {\n return rtf.format(diffSec, 'second');\n } else if (Math.abs(diffMin) < 60) {\n return rtf.format(diffMin, 'minute');\n } else if (Math.abs(diffHour) < 24) {\n return rtf.format(diffHour, 'hour');\n } else {\n return rtf.format(diffDay, 'day');\n }\n }\n\n /**\n * Get available locales\n */\n getAvailableLocales(): Array<{ code: Locale; name: string; nativeName: string }> {\n return this.config.supportedLocales.map(locale => ({\n code: locale,\n name: LOCALE_INFO[locale].name,\n nativeName: LOCALE_INFO[locale].nativeName,\n }));\n }\n\n /**\n * Subscribe to locale changes\n */\n subscribe(callback: (locale: Locale) => void): () => void {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n /**\n * Update localization configuration\n */\n updateConfig(updates: Partial<LocalizationConfig>): void {\n this.config = { ...this.config, ...updates };\n\n // Reload messages if custom messages were updated\n if (updates.messages) {\n this.loadedMessages.set(this.currentLocale, {\n ...LOCALE_INFO[this.currentLocale],\n ...this.config.messages,\n });\n }\n }\n\n // Private methods\n\n private getCurrentMessages(): LocaleMessages {\n return this.loadedMessages.get(this.currentLocale) || LOCALE_INFO[this.currentLocale];\n }\n\n private getNestedValue(obj: any, path: string): any {\n return path.split('.').reduce((current, key) => current?.[key], obj);\n }\n\n private interpolate(template: string, values?: InterpolationValues): string {\n if (!values) return template;\n\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {\n const value = values[key];\n if (value === undefined) return match;\n\n if (value instanceof Date) {\n return this.formatDate(value);\n }\n\n return String(value);\n });\n }\n\n private async loadLocaleMessages(locale: Locale): Promise<void> {\n try {\n // In a real implementation, you might load from a remote source\n const messages = LOCALE_INFO[locale] || LOCALE_INFO[this.config.fallbackLocale];\n\n this.loadedMessages.set(locale, {\n ...messages,\n ...this.config.messages,\n });\n } catch (error) {\n console.error(`Failed to load messages for locale ${locale}:`, error);\n // Fallback to default locale\n const fallbackMessages = LOCALE_INFO[this.config.fallbackLocale];\n this.loadedMessages.set(locale, fallbackMessages);\n }\n }\n\n private notifyListeners(): void {\n this.listeners.forEach(callback => callback(this.currentLocale));\n }\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Create a localization manager instance\n */\nexport function createLocalizationManager(config?: Partial<LocalizationConfig>): LocalizationManager {\n return new LocalizationManager(config);\n}\n\n/**\n * Detect browser locale\n */\nexport function detectBrowserLocale(supportedLocales: Locale[]): Locale {\n if (typeof navigator === 'undefined') return 'en';\n\n const browserLocales = [\n navigator.language,\n ...(navigator.languages || []),\n ];\n\n for (const browserLocale of browserLocales) {\n // Extract language code (e.g., 'en' from 'en-US')\n const languageCode = browserLocale.split('-')[0] as Locale;\n\n if (supportedLocales.includes(languageCode)) {\n return languageCode;\n }\n }\n\n return 'en'; // Default fallback\n}\n\n/**\n * Get text direction for a locale\n */\nexport function getLocaleDirection(locale: Locale): LocaleDirection {\n return LOCALE_INFO[locale]?.direction || 'ltr';\n}\n\n/**\n * Check if locale is RTL\n */\nexport function isRTL(locale: Locale): boolean {\n return getLocaleDirection(locale) === 'rtl';\n}\n\n/**\n * Create namespace for translations (useful for component libraries)\n */\nexport function createTranslationNamespace(\n manager: LocalizationManager,\n namespace: string\n) {\n return {\n t: (key: string, interpolation?: InterpolationValues) =>\n manager.t(`${namespace}.${key}` as TranslationKey, interpolation),\n plural: (key: string, options: PluralOptions) =>\n manager.plural(`${namespace}.${key}`, options),\n };\n}\n\n// ============================================================================\n// Export localization utilities\n// ============================================================================\n\nexport {\n DEFAULT_LOCALIZATION_CONFIG,\n DEFAULT_LOCALE_MESSAGES,\n};"],"names":["LocalizationManager","initialConfig","__publicField","DEFAULT_LOCALIZATION_CONFIG","LOCALE_INFO","locale","key","interpolation","messages","value","options","count","rule","pluralKey","date","number","metadata","amount","currency","rtf","now","diffMs","diffSec","diffMin","diffHour","diffDay","callback","updates","obj","path","current","template","values","match","error","fallbackMessages","createLocalizationManager","config","detectBrowserLocale","supportedLocales","browserLocales","browserLocale","languageCode","getLocaleDirection","isRTL","createTranslationNamespace","manager","namespace"],"mappings":"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"}