189 lines
5.0 KiB
TypeScript
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,
|
|
}
|
|
}
|