Files
webapp/app/composables/useLocaleCurrency.ts
2026-04-21 10:34:54 +07:00

189 lines
5.0 KiB
TypeScript

export type AppLocale = 'ru' | 'en'
export type AppCurrency = 'USD' | 'RUB' | 'CNY' | 'EUR' | 'AED'
export type CurrencyRatesResponse = {
baseCurrency: 'USD'
rates: Record<AppCurrency, number>
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<AppLocale, string> = {
ru: 'ru-RU',
en: 'en-US',
}
const CURRENCY_SYMBOLS: Record<AppCurrency, string> = {
USD: '$',
RUB: '₽',
CNY: '¥',
EUR: '€',
AED: 'د.إ',
}
const LOCALES: AppLocale[] = ['ru', 'en']
const CURRENCIES: AppCurrency[] = ['USD', 'RUB', 'CNY', 'EUR', 'AED']
const localeCodes = new Set<AppLocale>(LOCALES)
const currencyCodes = new Set<AppCurrency>(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<AppCurrency>('ex_currency', {
default: () => 'USD',
sameSite: 'lax',
path: '/',
})
const currencyRates = useState<CurrencyRatesResponse | null>('currency-rates', () => null)
const locale = computed<AppLocale>({
get: () => normalizeLocale(i18nLocale.value),
set: (value) => {
i18nLocale.value = normalizeLocale(value)
},
})
const currency = computed<AppCurrency>({
get: () => normalizeCurrency(currencyCookie.value),
set: (value) => {
currencyCookie.value = normalizeCurrency(value)
},
})
const intlLocale = computed(() => LOCALE_MAP[locale.value])
const localeOptions = computed<LocaleOption[]>(() => LOCALES.map(code => ({
code,
label: i18nT(`settings.locales.${code}.label`),
nativeLabel: i18nT(`settings.locales.${code}.nativeLabel`),
})))
const currencyOptions = computed<CurrencyOption[]>(() => 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,
}
}