163 lines
4.7 KiB
TypeScript
163 lines
4.7 KiB
TypeScript
import { useRegionStore } from '~/stores/region'
|
|
|
|
/**
|
|
* 🌍 useFormatter Composable
|
|
*
|
|
* Provides locale-aware formatting utilities for numbers, currencies, dates,
|
|
* and percentages. Uses the active region configuration from the RegionStore
|
|
* to determine the appropriate locale, currency, and timezone.
|
|
*
|
|
* Falls back to 'en-EU' locale and 'EUR' currency if no region is configured.
|
|
*/
|
|
export function useFormatter() {
|
|
const regionStore = useRegionStore()
|
|
|
|
/**
|
|
* Format a number according to the active locale.
|
|
* Example: 1234567.89 → "1,234,567.89" (en) or "1 234 567,89" (hu)
|
|
*/
|
|
function formatNumber(value: number, options?: Intl.NumberFormatOptions): string {
|
|
const locale = regionStore.activeLocale || 'en-EU'
|
|
try {
|
|
return new Intl.NumberFormat(locale, options).format(value)
|
|
} catch {
|
|
return new Intl.NumberFormat('en-EU', options).format(value)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a currency value according to the active region's currency and locale.
|
|
* Example: 1234.50 → "€1,234.50" (EUR/en) or "3 456 Ft" (HUF/hu)
|
|
*/
|
|
function formatCurrency(
|
|
value: number,
|
|
currency?: string,
|
|
options?: Intl.NumberFormatOptions,
|
|
): string {
|
|
const locale = regionStore.activeLocale || 'en-EU'
|
|
const curr = currency || regionStore.activeCurrency || 'EUR'
|
|
try {
|
|
return new Intl.NumberFormat(locale, {
|
|
style: 'currency',
|
|
currency: curr,
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
...options,
|
|
}).format(value)
|
|
} catch {
|
|
return `${value.toFixed(2)} ${curr}`
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a date according to the active locale and timezone.
|
|
* Example: 2026-06-24 → "24/06/2026" (en-GB) or "2026. 06. 24." (hu-HU)
|
|
*/
|
|
function formatDate(
|
|
date: Date | string | number,
|
|
options?: Intl.DateTimeFormatOptions,
|
|
): string {
|
|
const locale = regionStore.activeLocale || 'en-EU'
|
|
const timezone = regionStore.activeTimezone || 'Europe/Brussels'
|
|
const d = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date
|
|
|
|
try {
|
|
return new Intl.DateTimeFormat(locale, {
|
|
timezone,
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
...options,
|
|
}).format(d)
|
|
} catch {
|
|
return d.toLocaleDateString('en-GB')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a datetime with time according to the active locale and timezone.
|
|
* Example: "2026-06-24T14:30:00Z" → "24/06/2026, 16:30" (hu-HU/Budapest)
|
|
*/
|
|
function formatDateTime(
|
|
date: Date | string | number,
|
|
options?: Intl.DateTimeFormatOptions,
|
|
): string {
|
|
const locale = regionStore.activeLocale || 'en-EU'
|
|
const timezone = regionStore.activeTimezone || 'Europe/Brussels'
|
|
const d = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date
|
|
|
|
try {
|
|
return new Intl.DateTimeFormat(locale, {
|
|
timezone,
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
...options,
|
|
}).format(d)
|
|
} catch {
|
|
return d.toLocaleString('en-GB')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a percentage value.
|
|
* Example: 27 → "27%" or 27.5 → "27,5%" (hu)
|
|
*/
|
|
function formatPercent(
|
|
value: number,
|
|
options?: Intl.NumberFormatOptions,
|
|
): string {
|
|
const locale = regionStore.activeLocale || 'en-EU'
|
|
try {
|
|
return new Intl.NumberFormat(locale, {
|
|
style: 'percent',
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 1,
|
|
...options,
|
|
}).format(value / 100)
|
|
} catch {
|
|
return `${value}%`
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate price including VAT for the active region.
|
|
* VAT rate comes from the active region's default_vat_rate.
|
|
*/
|
|
function calculatePriceWithVat(
|
|
netPrice: number,
|
|
vatRate?: number,
|
|
): { net: number; vat: number; gross: number; vatRate: number } {
|
|
const rate = vatRate ?? regionStore.activeVatRate ?? 0
|
|
const vat = netPrice * (rate / 100)
|
|
const gross = netPrice + vat
|
|
return { net: netPrice, vat, gross, vatRate: rate }
|
|
}
|
|
|
|
/**
|
|
* Format a price range with VAT breakdown.
|
|
* Example: (100, 27) → "Net: €100.00 | VAT: €27.00 (27%) | Gross: €127.00"
|
|
*/
|
|
function formatPriceWithVat(
|
|
netPrice: number,
|
|
vatRate?: number,
|
|
currency?: string,
|
|
): string {
|
|
const { net, vat, gross, vatRate: rate } = calculatePriceWithVat(netPrice, vatRate)
|
|
const curr = currency || regionStore.activeCurrency || 'EUR'
|
|
return `Net: ${formatCurrency(net, curr)} | VAT: ${formatCurrency(vat, curr)} (${rate}%) | Gross: ${formatCurrency(gross, curr)}`
|
|
}
|
|
|
|
return {
|
|
formatNumber,
|
|
formatCurrency,
|
|
formatDate,
|
|
formatDateTime,
|
|
formatPercent,
|
|
calculatePriceWithVat,
|
|
formatPriceWithVat,
|
|
}
|
|
}
|