export type AppLocale = 'ru' | 'en' export type AppCurrency = 'USD' | 'RUB' | 'CNY' | 'EUR' | 'AED' export type CurrencyRatesResponse = { baseCurrency: 'USD' rates: Record updatedAt: string nextUpdateAt: string provider: string documentation: string termsOfUse: string attributionUrl: string } export type LocaleOption = { code: AppLocale label: string nativeLabel: string } export type CurrencyOption = { code: AppCurrency label: string symbol: string } type TranslationKey = | 'settings.language' | 'settings.currency' | 'settings.open' | 'settings.apply' | 'settings.ratesProvider' | 'settings.ratesBy' const LOCALE_MAP: Record = { ru: 'ru-RU', en: 'en-US', } const CURRENCY_SYMBOLS: Record = { USD: '$', RUB: '₽', CNY: '¥', EUR: '€', AED: 'د.إ', } const LOCALES: AppLocale[] = ['ru', 'en'] const CURRENCIES: AppCurrency[] = ['USD', 'RUB', 'CNY', 'EUR', 'AED'] const localeCodes = new Set(LOCALES) const currencyCodes = new Set(CURRENCIES) function normalizeLocale(value: unknown): AppLocale { return localeCodes.has(value as AppLocale) ? value as AppLocale : 'ru' } function assertCurrency(value: unknown): AppCurrency { const normalized = String(value || '').trim().toUpperCase() if (!currencyCodes.has(normalized as AppCurrency)) { throw createError({ statusCode: 500, statusMessage: `Unsupported currency: ${normalized || 'empty'}`, }) } return normalized as AppCurrency } function normalizeCurrency(value: unknown): AppCurrency { const normalized = String(value || '').trim().toUpperCase() return currencyCodes.has(normalized as AppCurrency) ? normalized as AppCurrency : 'USD' } export function useLocaleCurrency() { const { locale: i18nLocale, setLocale: setI18nLocale, t: i18nT } = useI18n() const currencyCookie = useCookie('ex_currency', { default: () => 'USD', sameSite: 'lax', path: '/', }) const currencyRates = useState('currency-rates', () => null) const locale = computed({ get: () => normalizeLocale(i18nLocale.value), set: (value) => { i18nLocale.value = normalizeLocale(value) }, }) const currency = computed({ get: () => normalizeCurrency(currencyCookie.value), set: (value) => { currencyCookie.value = normalizeCurrency(value) }, }) const intlLocale = computed(() => LOCALE_MAP[locale.value]) const localeOptions = computed(() => LOCALES.map(code => ({ code, label: i18nT(`settings.locales.${code}.label`), nativeLabel: i18nT(`settings.locales.${code}.nativeLabel`), }))) const currencyOptions = computed(() => CURRENCIES.map(code => ({ code, label: i18nT(`settings.currencies.${code}`), symbol: CURRENCY_SYMBOLS[code], }))) const languageCode = computed(() => locale.value.toUpperCase()) const currencyCode = computed(() => currency.value) const ratesProviderUrl = computed(() => currencyRates.value?.attributionUrl || 'https://www.exchangerate-api.com') async function setLocale(value: AppLocale) { const normalizedLocale = normalizeLocale(value) locale.value = normalizedLocale await setI18nLocale(normalizedLocale) } function setCurrency(value: AppCurrency) { currency.value = value } function t(key: TranslationKey) { return i18nT(key) } function getRates() { if (!currencyRates.value) { throw createError({ statusCode: 500, statusMessage: 'Currency rates are not loaded', }) } return currencyRates.value.rates } function convertMoney(value: number, sourceCurrency: AppCurrency = 'USD', targetCurrency: AppCurrency = currency.value) { const rates = getRates() const amountInUsd = value / rates[sourceCurrency] return amountInUsd * rates[targetCurrency] } function formatMoney(value: number, sourceCurrency: string = 'USD') { const normalizedSourceCurrency = assertCurrency(sourceCurrency) const convertedValue = convertMoney(value, normalizedSourceCurrency, currency.value) return new Intl.NumberFormat(intlLocale.value, { style: 'currency', currency: currency.value, maximumFractionDigits: currency.value === 'RUB' ? 0 : 0, }).format(convertedValue) } function formatDate(value: Date | string, options: Intl.DateTimeFormatOptions = {}) { const date = value instanceof Date ? value : new Date(value) return new Intl.DateTimeFormat(intlLocale.value, options).format(date) } function formatDateTime(value: Date | string) { return formatDate(value, { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', }) } return { locale, currency, intlLocale, localeOptions, currencyOptions, languageCode, currencyCode, setLocale, setCurrency, convertMoney, formatMoney, formatDate, formatDateTime, currencyRates, ratesProviderUrl, t, } }