From 274aa6a17c85a19e8fb3916689be5b82d4739cf5 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Fri, 15 Aug 2025 23:58:15 +0800 Subject: [PATCH] digit grouping type supports Indian Number Grouping --- pkg/core/numeral.go | 11 +++-- pkg/models/user.go | 2 +- pkg/services/users.go | 2 +- src/core/numeral.ts | 52 +++++++++++++++++++-- src/lib/numeral.ts | 26 +++++------ src/locales/de.json | 3 +- src/locales/en.json | 3 +- src/locales/es.json | 3 +- src/locales/helpers.ts | 5 +- src/locales/it.json | 3 +- src/locales/ja.json | 3 +- src/locales/nl.json | 3 +- src/locales/pt_BR.json | 3 +- src/locales/ru.json | 3 +- src/locales/uk.json | 3 +- src/locales/vi.json | 3 +- src/locales/zh_Hans.json | 3 +- src/locales/zh_Hant.json | 3 +- src/views/base/users/UserProfilePageBase.ts | 4 +- 19 files changed, 100 insertions(+), 38 deletions(-) diff --git a/pkg/core/numeral.go b/pkg/core/numeral.go index 3c0f36b9..86549557 100644 --- a/pkg/core/numeral.go +++ b/pkg/core/numeral.go @@ -69,10 +69,11 @@ type DigitGroupingType byte // Digit Grouping Type const ( - DIGIT_GROUPING_TYPE_DEFAULT DigitGroupingType = 0 - DIGIT_GROUPING_TYPE_NONE DigitGroupingType = 1 - DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR DigitGroupingType = 2 - DIGIT_GROUPING_TYPE_INVALID DigitGroupingType = 255 + DIGIT_GROUPING_TYPE_DEFAULT DigitGroupingType = 0 + DIGIT_GROUPING_TYPE_NONE DigitGroupingType = 1 + DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR DigitGroupingType = 2 + DIGIT_GROUPING_TYPE_INDIAN_NUMBER_GROUPING DigitGroupingType = 3 + DIGIT_GROUPING_TYPE_INVALID DigitGroupingType = 255 ) // String returns a textual representation of the digit grouping type enum @@ -84,6 +85,8 @@ func (d DigitGroupingType) String() string { return "None" case DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR: return "Thousands Separator" + case DIGIT_GROUPING_TYPE_INDIAN_NUMBER_GROUPING: + return "Indian Number Grouping" case DIGIT_GROUPING_TYPE_INVALID: return "Invalid" default: diff --git a/pkg/models/user.go b/pkg/models/user.go index 27fe28f1..5d6cc325 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -200,7 +200,7 @@ type UserProfileUpdateRequest struct { FiscalYearFormat *core.FiscalYearFormat `json:"fiscalYearFormat" binding:"omitempty,min=0,max=5"` DecimalSeparator *core.DecimalSeparator `json:"decimalSeparator" binding:"omitempty,min=0,max=3"` DigitGroupingSymbol *core.DigitGroupingSymbol `json:"digitGroupingSymbol" binding:"omitempty,min=0,max=4"` - DigitGrouping *core.DigitGroupingType `json:"digitGrouping" binding:"omitempty,min=0,max=2"` + DigitGrouping *core.DigitGroupingType `json:"digitGrouping" binding:"omitempty,min=0,max=3"` CurrencyDisplayType *core.CurrencyDisplayType `json:"currencyDisplayType" binding:"omitempty,min=0,max=11"` CoordinateDisplayType *core.CoordinateDisplayType `json:"coordinateDisplayType" binding:"omitempty,min=0,max=6"` ExpenseAmountColor *AmountColorType `json:"expenseAmountColor" binding:"omitempty,min=0,max=4"` diff --git a/pkg/services/users.go b/pkg/services/users.go index 06e884e3..3d13ef58 100644 --- a/pkg/services/users.go +++ b/pkg/services/users.go @@ -321,7 +321,7 @@ func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLa updateCols = append(updateCols, "digit_grouping_symbol") } - if core.DIGIT_GROUPING_TYPE_DEFAULT <= user.DigitGrouping && user.DigitGrouping <= core.DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR { + if core.DIGIT_GROUPING_TYPE_DEFAULT <= user.DigitGrouping && user.DigitGrouping <= core.DIGIT_GROUPING_TYPE_INDIAN_NUMBER_GROUPING { updateCols = append(updateCols, "digit_grouping") } diff --git a/src/core/numeral.ts b/src/core/numeral.ts index aca8a8ce..bf5e6349 100644 --- a/src/core/numeral.ts +++ b/src/core/numeral.ts @@ -110,8 +110,52 @@ export class DigitGroupingType implements TypeAndName { private static readonly allInstancesByTypeName: Record = {}; public static readonly LanguageDefaultType: number = 0; - public static readonly None = new DigitGroupingType(1, 'None', 'None', false); - public static readonly ThousandsSeparator = new DigitGroupingType(2, 'ThousandsSeparator', 'Thousands Separator', true); + public static readonly None = new DigitGroupingType(1, 'None', 'None', false, + (numericChars: string[]) => { + return numericChars.join(''); + } + ); + public static readonly ThousandsSeparator = new DigitGroupingType(2, 'ThousandsSeparator', 'Thousands Separator', true, + (numericChars: string[], digitGroupingSymbol: string) => { + if (numericChars.length <= 3) { + return numericChars.join(''); + } + + let ret = ''; + + for (let i = numericChars.length - 1, j = 0; i >= 0; i--, j++) { + if (j > 0 && j % 3 === 0) { + ret = digitGroupingSymbol + ret; + } + + ret = numericChars[i] + ret; + } + + return ret; + } + ); + public static readonly IndianNumberGrouping = new DigitGroupingType(3, 'IndianNumberGrouping', 'Indian Number Grouping', true, + (numericChars: string[], digitGroupingSymbol: string) => { + if (numericChars.length <= 3) { + return numericChars.join(''); + } + + let ret = ''; + const length = numericChars.length; + + for (let i = length - 1, j = 0; i >= 0; i--, j++) { + if (j === 3) { + ret = digitGroupingSymbol + ret; + } else if (j > 3 && (j - 3) % 2 === 0) { + ret = digitGroupingSymbol + ret; + } + + ret = numericChars[i] + ret; + } + + return ret; + } + ); public static readonly Default = DigitGroupingType.ThousandsSeparator; @@ -119,12 +163,14 @@ export class DigitGroupingType implements TypeAndName { public readonly typeName: string; public readonly name: string; public readonly enabled: boolean; + public readonly format: (numericChars: string[], digitGroupingSymbol: string) => string; - private constructor(type: number, typeName: string, name: string, enabled: boolean) { + private constructor(type: number, typeName: string, name: string, enabled: boolean, format: (numericChars: string[], digitGroupingSymbol: string) => string) { this.type = type; this.typeName = typeName; this.name = name; this.enabled = enabled; + this.format = format; DigitGroupingType.allInstances.push(this); DigitGroupingType.allInstancesByType[type] = this; diff --git a/src/lib/numeral.ts b/src/lib/numeral.ts index f45710a2..b3b741cc 100644 --- a/src/lib/numeral.ts +++ b/src/lib/numeral.ts @@ -30,11 +30,17 @@ export function appendDigitGroupingSymbol(value: number | string, options: Numbe options = {}; } - if (!isNumber(options.digitGrouping) || options.digitGrouping === DigitGroupingType.None.type) { + if (!isNumber(options.digitGrouping)) { return textualValue; } - if (textualValue.length <= 3) { + const digitGroupingType = DigitGroupingType.valueOf(options.digitGrouping); + + if (!digitGroupingType || !digitGroupingType.enabled) { + return textualValue; + } + + if (textualValue.length <= 1) { return textualValue; } @@ -44,6 +50,10 @@ export function appendDigitGroupingSymbol(value: number | string, options: Numbe textualValue = textualValue.substring(1); } + if (textualValue.length <= 1) { + return textualValue; + } + const digitGroupingSymbol = options.digitGroupingSymbol || DigitGroupingSymbol.Default.symbol; const decimalSeparator = options.decimalSeparator || DecimalSeparator.Default.symbol; @@ -63,17 +73,7 @@ export function appendDigitGroupingSymbol(value: number | string, options: Numbe } } - let newInteger = ''; - - if (options.digitGrouping === DigitGroupingType.ThousandsSeparator.type) { - for (let i = integerChars.length - 1, j = 0; i >= 0; i--, j++) { - if (j % 3 === 0 && j > 0) { - newInteger = digitGroupingSymbol + newInteger; - } - - newInteger = integerChars[i] + newInteger; - } - } + let newInteger = digitGroupingType.format(integerChars, digitGroupingSymbol); if (negative) { newInteger = `-${newInteger}`; diff --git a/src/locales/de.json b/src/locales/de.json index 0386c4bf..2fe7fdbe 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -265,7 +265,8 @@ "Space": "Leerzeichen", "Apostrophe": "Apostroph", "None": "Keine", - "Thousands Separator": "Tausender Trennzeichen" + "Thousands Separator": "Tausender Trennzeichen", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/en.json b/src/locales/en.json index a962b5ec..1d484742 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -265,7 +265,8 @@ "Space": "Space", "Apostrophe": "Apostrophe", "None": "None", - "Thousands Separator": "Thousands Separator" + "Thousands Separator": "Thousands Separator", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/es.json b/src/locales/es.json index 923a4089..639f4208 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -265,7 +265,8 @@ "Space": "Espacio", "Apostrophe": "Apóstrofe", "None": "Ninguno", - "Thousands Separator": "Separador de miles" + "Thousands Separator": "Separador de miles", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/helpers.ts b/src/locales/helpers.ts index 01028026..b5a106df 100644 --- a/src/locales/helpers.ts +++ b/src/locales/helpers.ts @@ -1030,7 +1030,7 @@ export function useI18n() { return ret; } - function getAllDigitGroupingTypes(): LocalizedDigitGroupingType[] { + function getAllDigitGroupingTypes(digitGroupingSymbol: string): LocalizedDigitGroupingType[] { const defaultDigitGroupingTypeName = t('default.digitGrouping'); let defaultDigitGroupingType = DigitGroupingType.parse(defaultDigitGroupingTypeName); @@ -1050,11 +1050,12 @@ export function useI18n() { for (let i = 0; i < allDigitGroupingTypes.length; i++) { const type = allDigitGroupingTypes[i]; + const sampleValue = type.format('123456789'.split(''), digitGroupingSymbol); ret.push({ type: type.type, enabled: type.enabled, - displayName: t('numeral.' + type.name) + displayName: `${t('numeral.' + type.name)} (${sampleValue})` }); } diff --git a/src/locales/it.json b/src/locales/it.json index d31ce031..7105cc5d 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -265,7 +265,8 @@ "Space": "Spazio", "Apostrophe": "Apostrofo", "None": "Nulla", - "Thousands Separator": "Separatore migliaia" + "Thousands Separator": "Separatore migliaia", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/ja.json b/src/locales/ja.json index 72f054b9..51e8acb9 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -265,7 +265,8 @@ "Space": "スペース", "Apostrophe": "アポストロフィ", "None": "なし", - "Thousands Separator": "千の桁区切り" + "Thousands Separator": "千の桁区切り", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/nl.json b/src/locales/nl.json index b897fb50..27deee96 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -265,7 +265,8 @@ "Space": "Spatie", "Apostrophe": "Apostrof", "None": "Geen", - "Thousands Separator": "Scheidingsteken voor duizendtallen" + "Thousands Separator": "Scheidingsteken voor duizendtallen", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 7e48ebc8..25f1f95f 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -265,7 +265,8 @@ "Space": "Espaço", "Apostrophe": "Apóstrofo", "None": "Nenhum", - "Thousands Separator": "Separador de Milhares" + "Thousands Separator": "Separador de Milhares", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/ru.json b/src/locales/ru.json index 1a9bfe93..978ab692 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -265,7 +265,8 @@ "Space": "Пробел", "Apostrophe": "Апостроф", "None": "Нет", - "Thousands Separator": "Разделитель тысяч" + "Thousands Separator": "Разделитель тысяч", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/uk.json b/src/locales/uk.json index d9861942..a38a1e05 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -265,7 +265,8 @@ "Space": "Пробіл", "Apostrophe": "Апостроф", "None": "Немає", - "Thousands Separator": "Роздільник тисяч" + "Thousands Separator": "Роздільник тисяч", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/vi.json b/src/locales/vi.json index 2124275d..cf0f7ee1 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -265,7 +265,8 @@ "Space": "Khoảng trắng", "Apostrophe": "Dấu nháy đơn", "None": "Không có", - "Thousands Separator": "Dấu phân cách hàng nghìn" + "Thousands Separator": "Dấu phân cách hàng nghìn", + "Indian Number Grouping": "Indian Number Grouping" }, "color": { "amount": { diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index a4625fe7..37106018 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -265,7 +265,8 @@ "Space": "空格", "Apostrophe": "撇号", "None": "无", - "Thousands Separator": "千位分隔符" + "Thousands Separator": "千位分隔符", + "Indian Number Grouping": "印度数字分组" }, "color": { "amount": { diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index a63e943e..d4caff3f 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -265,7 +265,8 @@ "Space": "空格", "Apostrophe": "撇號", "None": "無", - "Thousands Separator": "千位分隔符" + "Thousands Separator": "千位分隔符", + "Indian Number Grouping": "印度數字分組" }, "color": { "amount": { diff --git a/src/views/base/users/UserProfilePageBase.ts b/src/views/base/users/UserProfilePageBase.ts index f8683a6f..a46b4412 100644 --- a/src/views/base/users/UserProfilePageBase.ts +++ b/src/views/base/users/UserProfilePageBase.ts @@ -8,7 +8,7 @@ import { useOverviewStore } from '@/stores/overview.ts'; import type { TypeAndDisplayName } from '@/core/base.ts'; import { WeekDay } from '@/core/datetime.ts'; -import type { LocalizedDigitGroupingType } from '@/core/numeral.ts'; +import { type LocalizedDigitGroupingType, DigitGroupingSymbol } from '@/core/numeral.ts'; import { type UserBasicInfo, User } from '@/models/user.ts'; import { type CategorizedAccount, Account} from '@/models/account.ts'; @@ -64,7 +64,7 @@ export function useUserProfilePageBase() { const allFiscalYearFormats = computed(() => getAllFiscalYearFormats()); const allDecimalSeparators = computed(() => getAllDecimalSeparators()); const allDigitGroupingSymbols = computed(() => getAllDigitGroupingSymbols()); - const allDigitGroupingTypes = computed(() => getAllDigitGroupingTypes()); + const allDigitGroupingTypes = computed(() => getAllDigitGroupingTypes(DigitGroupingSymbol.valueOf(newProfile.value.digitGroupingSymbol)?.symbol || DigitGroupingSymbol.Default.symbol)); const allCurrencyDisplayTypes = computed(() => getAllCurrencyDisplayTypes()); const allCoordinateDisplayTypes = computed(() => getAllCoordinateDisplayTypes()); const allExpenseAmountColorTypes = computed(() => getAllExpenseAmountColors());