diff --git a/src/components/base/PieChartBase.ts b/src/components/base/PieChartBase.ts index a4da5c3f..9886f19f 100644 --- a/src/components/base/PieChartBase.ts +++ b/src/components/base/PieChartBase.ts @@ -5,7 +5,6 @@ import { useI18n } from '@/locales/helpers.ts'; import { DEFAULT_CHART_COLORS } from '@/consts/color.ts'; import { isNumber } from '@/lib/common.ts'; -import { formatPercent } from '@/lib/numeral.ts'; export interface CommonPieChartDataItem { id: string; @@ -36,7 +35,7 @@ export interface CommonPieChartProps { } export function usePieChartBase(props: CommonPieChartProps) { - const { formatAmountWithCurrency } = useI18n(); + const { formatAmountWithCurrency, formatPercent } = useI18n(); const selectedIndex = ref(0); diff --git a/src/lib/numeral.ts b/src/lib/numeral.ts index d60cdd44..247216b4 100644 --- a/src/lib/numeral.ts +++ b/src/lib/numeral.ts @@ -1,7 +1,7 @@ -import { type NumberFormatOptions, DecimalSeparator, DigitGroupingSymbol, DigitGroupingType} from '@/core/numeral.ts'; +import { type NumberFormatOptions, DecimalSeparator, DigitGroupingSymbol, DigitGroupingType } from '@/core/numeral.ts'; import { DEFAULT_DECIMAL_NUMBER_COUNT, MAX_SUPPORTED_DECIMAL_NUMBER_COUNT } from '@/consts/numeral.ts'; -import { isString, isNumber, removeAll } from './common.ts'; +import {isString, isNumber, replaceAll, removeAll } from './common.ts'; export function appendDigitGroupingSymbol(value: number | string, options: NumberFormatOptions): string { let textualValue = ''; @@ -76,6 +76,67 @@ export function appendDigitGroupingSymbol(value: number | string, options: Numbe } } +export function appendDecimalSeparator(value: number | string, options: NumberFormatOptions): string { + let textualValue = ''; + + if (isNumber(value)) { + textualValue = value.toString(); + } else { + textualValue = value; + } + + if (!textualValue) { + return textualValue; + } + + if (!options) { + options = {}; + } + + if (!isString(options.decimalSeparator)) { + return textualValue; + } + + if (textualValue.length < 1) { + return textualValue; + } + + const negative = textualValue.charAt(0) === '-'; + + if (negative) { + textualValue = textualValue.substring(1); + } + + const decimalSeparator = options.decimalSeparator || DecimalSeparator.Default.symbol; + + let currentDecimalSeparator = ''; + let integer = ''; + let decimals = ''; + + for (let i = 0; i < textualValue.length; i++) { + const ch = textualValue.charAt(i); + + if ('0' <= ch && ch <= '9') { + integer += ch; + } else { + currentDecimalSeparator = ch; + decimals = textualValue.substring(i + 1); + break; + } + } + + + if (negative) { + integer = `-${integer}`; + } + + if (currentDecimalSeparator) { + return `${integer}${decimalSeparator}${decimals}`; + } else { + return integer; + } +} + export function parseAmount(str: string, options: NumberFormatOptions): number { if (!isString(str)) { return 0; @@ -208,16 +269,30 @@ export function formatAmount(value: number | string, options: NumberFormatOption return textualValue; } -export function formatPercent(value: number, precision: number, lowPrecisionValue: string): string { +export function formatNumber(value: number, precision: number, options: NumberFormatOptions): string { + const ratio = Math.pow(10, precision); + const normalizedValue = Math.floor(value * ratio); + const textualValue = (normalizedValue / ratio).toString(); + + return appendDecimalSeparator(textualValue, options); +} + +export function formatPercent(value: number, precision: number, lowPrecisionValue: string, options: NumberFormatOptions): string { const ratio = Math.pow(10, precision); const normalizedValue = Math.floor(value * ratio); if (value > 0 && normalizedValue < 1 && lowPrecisionValue) { - return lowPrecisionValue + '%'; + const systemDecimalSeparator = DecimalSeparator.Dot.symbol; + const decimalSeparator = options.decimalSeparator || DecimalSeparator.Default.symbol; + + if (systemDecimalSeparator === decimalSeparator) { + return lowPrecisionValue + '%'; + } + + return replaceAll(lowPrecisionValue, systemDecimalSeparator, decimalSeparator) + '%'; } - const result = normalizedValue / ratio; - return result + '%'; + return formatNumber(value, precision, options) + '%'; } export function getAmountWithDecimalNumberCount(amount: number, decimalNumberCount: number): number { diff --git a/src/locales/helpers.ts b/src/locales/helpers.ts index ea621245..7d0ec205 100644 --- a/src/locales/helpers.ts +++ b/src/locales/helpers.ts @@ -137,6 +137,8 @@ import { appendDigitGroupingSymbol, parseAmount, formatAmount, + formatNumber, + formatPercent, formatExchangeRateAmount, getAdaptiveDisplayAmountRate } from '@/lib/numeral.ts'; @@ -1472,6 +1474,16 @@ export function useI18n() { return appendCurrencySymbol(textualValue, currencyDisplayType, finalCurrencyCode, currencyUnit, currencyName, isPlural); } + function getFormattedNumber(value: number, precision: number): string { + const numberFormatOptions = getNumberFormatOptions(); + return formatNumber(value, precision, numberFormatOptions); + } + + function getFormattedPercentValue(value: number, precision: number, lowPrecisionValue: string): string { + const numberFormatOptions = getNumberFormatOptions(); + return formatPercent(value, precision, lowPrecisionValue, numberFormatOptions); + } + function getFormattedExchangeRateAmount(value: number | string): string { const numberFormatOptions = getNumberFormatOptions(); return formatExchangeRateAmount(value, numberFormatOptions); @@ -1746,6 +1758,8 @@ export function useI18n() { parseAmount: getParsedAmountNumber, formatAmount: getFormattedAmount, formatAmountWithCurrency: getFormattedAmountWithCurrency, + formatNumber: getFormattedNumber, + formatPercent: getFormattedPercentValue, formatExchangeRateAmount: getFormattedExchangeRateAmount, getAdaptiveAmountRate, getAmountPrependAndAppendText, diff --git a/src/views/desktop/statistics/TransactionPage.vue b/src/views/desktop/statistics/TransactionPage.vue index 1a604c0f..2105b004 100644 --- a/src/views/desktop/statistics/TransactionPage.vue +++ b/src/views/desktop/statistics/TransactionPage.vue @@ -352,9 +352,6 @@ import { isNumber, arrayItemToObjectField } from '@/lib/common.ts' -import { - formatPercent -} from '@/lib/numeral.ts'; import { getYearAndMonthFromUnixTime, getYearMonthFirstUnixTime, @@ -399,7 +396,7 @@ const props = defineProps(); const router = useRouter(); const display = useDisplay(); const theme = useTheme(); -const { tt, getAllCategoricalChartTypes, getAllTrendChartTypes } = useI18n(); +const { tt, getAllCategoricalChartTypes, getAllTrendChartTypes, formatPercent } = useI18n(); const { loading, diff --git a/src/views/desktop/transactions/import/ImportDialog.vue b/src/views/desktop/transactions/import/ImportDialog.vue index 80d4bd02..85fbcf5e 100644 --- a/src/views/desktop/transactions/import/ImportDialog.vue +++ b/src/views/desktop/transactions/import/ImportDialog.vue @@ -786,7 +786,7 @@ - {{ (submitting && importProcess > 0 ? tt('format.misc.importingTransactions', { process: importProcess.toFixed(2) }) : tt('Import')) }} + {{ (submitting && importProcess > 0 ? tt('format.misc.importingTransactions', { process: formatNumber(importProcess, 2) }) : tt('Import')) }} (); -const { tt, getAllCategoricalChartTypes } = useI18n(); +const { tt, getAllCategoricalChartTypes, formatPercent } = useI18n(); const { showToast, routeBackOnError } = useI18nUIComponents(); const {