feat(ui): copy logistics header/theme one-to-one
This commit is contained in:
188
app/composables/useLocaleCurrency.ts
Normal file
188
app/composables/useLocaleCurrency.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user