69 lines
1.9 KiB
JavaScript
69 lines
1.9 KiB
JavaScript
// Generic currency formatting composable with fallback chain.
|
||
// Usage:
|
||
// const { formatMoney, currentCurrency } = useCurrencyFormat({
|
||
// primary: clientCurrency, // e.g. 'EUR'
|
||
// fallbacks: ['EUR'],
|
||
// locale: 'sl-SI'
|
||
// })
|
||
// formatMoney(123.45) -> '123,45 €'
|
||
|
||
import { ref, computed } from 'vue'
|
||
|
||
export function useCurrencyFormat(options = {}) {
|
||
const {
|
||
primary = 'EUR',
|
||
fallbacks = ['EUR'],
|
||
locale = 'sl-SI',
|
||
minimumFractionDigits = 2,
|
||
maximumFractionDigits = 2,
|
||
} = options
|
||
|
||
const primaryCurrency = ref(primary)
|
||
const fallbackList = ref(Array.isArray(fallbacks) && fallbacks.length ? fallbacks : ['EUR'])
|
||
|
||
const currencyChain = computed(() => [primaryCurrency.value, ...fallbackList.value].filter(Boolean))
|
||
|
||
const formatterByCode = new Map()
|
||
function getFormatter(code) {
|
||
if (!code) return null
|
||
if (!formatterByCode.has(code)) {
|
||
try {
|
||
formatterByCode.set(
|
||
code,
|
||
new Intl.NumberFormat(locale, {
|
||
style: 'currency',
|
||
currency: code,
|
||
minimumFractionDigits,
|
||
maximumFractionDigits,
|
||
})
|
||
)
|
||
} catch (e) {
|
||
// invalid currency code – skip
|
||
formatterByCode.set(code, null)
|
||
}
|
||
}
|
||
return formatterByCode.get(code)
|
||
}
|
||
|
||
const activeCurrency = computed(() => {
|
||
for (const c of currencyChain.value) {
|
||
if (getFormatter(c)) return c
|
||
}
|
||
return 'EUR'
|
||
})
|
||
|
||
function formatMoney(val, overrideCurrency) {
|
||
if (val === null || val === undefined || val === '' || isNaN(val)) return '—'
|
||
const code = overrideCurrency || activeCurrency.value
|
||
const fmt = getFormatter(code) || getFormatter('EUR')
|
||
return fmt ? fmt.format(Number(val)) : Number(val).toFixed(2) + ' ' + code
|
||
}
|
||
|
||
return {
|
||
formatMoney,
|
||
activeCurrency,
|
||
primaryCurrency,
|
||
setCurrency(code) { primaryCurrency.value = code },
|
||
}
|
||
}
|