digit grouping type supports Indian Number Grouping

This commit is contained in:
MaysWind
2025-08-15 23:58:15 +08:00
parent 2f8d4ad5e4
commit 274aa6a17c
19 changed files with 100 additions and 38 deletions
+7 -4
View File
@@ -69,10 +69,11 @@ type DigitGroupingType byte
// Digit Grouping Type // Digit Grouping Type
const ( const (
DIGIT_GROUPING_TYPE_DEFAULT DigitGroupingType = 0 DIGIT_GROUPING_TYPE_DEFAULT DigitGroupingType = 0
DIGIT_GROUPING_TYPE_NONE DigitGroupingType = 1 DIGIT_GROUPING_TYPE_NONE DigitGroupingType = 1
DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR DigitGroupingType = 2 DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR DigitGroupingType = 2
DIGIT_GROUPING_TYPE_INVALID DigitGroupingType = 255 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 // String returns a textual representation of the digit grouping type enum
@@ -84,6 +85,8 @@ func (d DigitGroupingType) String() string {
return "None" return "None"
case DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR: case DIGIT_GROUPING_TYPE_THOUSANDS_SEPARATOR:
return "Thousands Separator" return "Thousands Separator"
case DIGIT_GROUPING_TYPE_INDIAN_NUMBER_GROUPING:
return "Indian Number Grouping"
case DIGIT_GROUPING_TYPE_INVALID: case DIGIT_GROUPING_TYPE_INVALID:
return "Invalid" return "Invalid"
default: default:
+1 -1
View File
@@ -200,7 +200,7 @@ type UserProfileUpdateRequest struct {
FiscalYearFormat *core.FiscalYearFormat `json:"fiscalYearFormat" binding:"omitempty,min=0,max=5"` FiscalYearFormat *core.FiscalYearFormat `json:"fiscalYearFormat" binding:"omitempty,min=0,max=5"`
DecimalSeparator *core.DecimalSeparator `json:"decimalSeparator" binding:"omitempty,min=0,max=3"` DecimalSeparator *core.DecimalSeparator `json:"decimalSeparator" binding:"omitempty,min=0,max=3"`
DigitGroupingSymbol *core.DigitGroupingSymbol `json:"digitGroupingSymbol" binding:"omitempty,min=0,max=4"` 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"` CurrencyDisplayType *core.CurrencyDisplayType `json:"currencyDisplayType" binding:"omitempty,min=0,max=11"`
CoordinateDisplayType *core.CoordinateDisplayType `json:"coordinateDisplayType" binding:"omitempty,min=0,max=6"` CoordinateDisplayType *core.CoordinateDisplayType `json:"coordinateDisplayType" binding:"omitempty,min=0,max=6"`
ExpenseAmountColor *AmountColorType `json:"expenseAmountColor" binding:"omitempty,min=0,max=4"` ExpenseAmountColor *AmountColorType `json:"expenseAmountColor" binding:"omitempty,min=0,max=4"`
+1 -1
View File
@@ -321,7 +321,7 @@ func (s *UserService) UpdateUser(c core.Context, user *models.User, modifyUserLa
updateCols = append(updateCols, "digit_grouping_symbol") 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") updateCols = append(updateCols, "digit_grouping")
} }
+49 -3
View File
@@ -110,8 +110,52 @@ export class DigitGroupingType implements TypeAndName {
private static readonly allInstancesByTypeName: Record<string, DigitGroupingType> = {}; private static readonly allInstancesByTypeName: Record<string, DigitGroupingType> = {};
public static readonly LanguageDefaultType: number = 0; public static readonly LanguageDefaultType: number = 0;
public static readonly None = new DigitGroupingType(1, 'None', 'None', false); public static readonly None = new DigitGroupingType(1, 'None', 'None', false,
public static readonly ThousandsSeparator = new DigitGroupingType(2, 'ThousandsSeparator', 'Thousands Separator', true); (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; public static readonly Default = DigitGroupingType.ThousandsSeparator;
@@ -119,12 +163,14 @@ export class DigitGroupingType implements TypeAndName {
public readonly typeName: string; public readonly typeName: string;
public readonly name: string; public readonly name: string;
public readonly enabled: boolean; 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.type = type;
this.typeName = typeName; this.typeName = typeName;
this.name = name; this.name = name;
this.enabled = enabled; this.enabled = enabled;
this.format = format;
DigitGroupingType.allInstances.push(this); DigitGroupingType.allInstances.push(this);
DigitGroupingType.allInstancesByType[type] = this; DigitGroupingType.allInstancesByType[type] = this;
+13 -13
View File
@@ -30,11 +30,17 @@ export function appendDigitGroupingSymbol(value: number | string, options: Numbe
options = {}; options = {};
} }
if (!isNumber(options.digitGrouping) || options.digitGrouping === DigitGroupingType.None.type) { if (!isNumber(options.digitGrouping)) {
return textualValue; return textualValue;
} }
if (textualValue.length <= 3) { const digitGroupingType = DigitGroupingType.valueOf(options.digitGrouping);
if (!digitGroupingType || !digitGroupingType.enabled) {
return textualValue;
}
if (textualValue.length <= 1) {
return textualValue; return textualValue;
} }
@@ -44,6 +50,10 @@ export function appendDigitGroupingSymbol(value: number | string, options: Numbe
textualValue = textualValue.substring(1); textualValue = textualValue.substring(1);
} }
if (textualValue.length <= 1) {
return textualValue;
}
const digitGroupingSymbol = options.digitGroupingSymbol || DigitGroupingSymbol.Default.symbol; const digitGroupingSymbol = options.digitGroupingSymbol || DigitGroupingSymbol.Default.symbol;
const decimalSeparator = options.decimalSeparator || DecimalSeparator.Default.symbol; const decimalSeparator = options.decimalSeparator || DecimalSeparator.Default.symbol;
@@ -63,17 +73,7 @@ export function appendDigitGroupingSymbol(value: number | string, options: Numbe
} }
} }
let newInteger = ''; let newInteger = digitGroupingType.format(integerChars, digitGroupingSymbol);
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;
}
}
if (negative) { if (negative) {
newInteger = `-${newInteger}`; newInteger = `-${newInteger}`;
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Leerzeichen", "Space": "Leerzeichen",
"Apostrophe": "Apostroph", "Apostrophe": "Apostroph",
"None": "Keine", "None": "Keine",
"Thousands Separator": "Tausender Trennzeichen" "Thousands Separator": "Tausender Trennzeichen",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Space", "Space": "Space",
"Apostrophe": "Apostrophe", "Apostrophe": "Apostrophe",
"None": "None", "None": "None",
"Thousands Separator": "Thousands Separator" "Thousands Separator": "Thousands Separator",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Espacio", "Space": "Espacio",
"Apostrophe": "Apóstrofe", "Apostrophe": "Apóstrofe",
"None": "Ninguno", "None": "Ninguno",
"Thousands Separator": "Separador de miles" "Thousands Separator": "Separador de miles",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+3 -2
View File
@@ -1030,7 +1030,7 @@ export function useI18n() {
return ret; return ret;
} }
function getAllDigitGroupingTypes(): LocalizedDigitGroupingType[] { function getAllDigitGroupingTypes(digitGroupingSymbol: string): LocalizedDigitGroupingType[] {
const defaultDigitGroupingTypeName = t('default.digitGrouping'); const defaultDigitGroupingTypeName = t('default.digitGrouping');
let defaultDigitGroupingType = DigitGroupingType.parse(defaultDigitGroupingTypeName); let defaultDigitGroupingType = DigitGroupingType.parse(defaultDigitGroupingTypeName);
@@ -1050,11 +1050,12 @@ export function useI18n() {
for (let i = 0; i < allDigitGroupingTypes.length; i++) { for (let i = 0; i < allDigitGroupingTypes.length; i++) {
const type = allDigitGroupingTypes[i]; const type = allDigitGroupingTypes[i];
const sampleValue = type.format('123456789'.split(''), digitGroupingSymbol);
ret.push({ ret.push({
type: type.type, type: type.type,
enabled: type.enabled, enabled: type.enabled,
displayName: t('numeral.' + type.name) displayName: `${t('numeral.' + type.name)} (${sampleValue})`
}); });
} }
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Spazio", "Space": "Spazio",
"Apostrophe": "Apostrofo", "Apostrophe": "Apostrofo",
"None": "Nulla", "None": "Nulla",
"Thousands Separator": "Separatore migliaia" "Thousands Separator": "Separatore migliaia",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "スペース", "Space": "スペース",
"Apostrophe": "アポストロフィ", "Apostrophe": "アポストロフィ",
"None": "なし", "None": "なし",
"Thousands Separator": "千の桁区切り" "Thousands Separator": "千の桁区切り",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Spatie", "Space": "Spatie",
"Apostrophe": "Apostrof", "Apostrophe": "Apostrof",
"None": "Geen", "None": "Geen",
"Thousands Separator": "Scheidingsteken voor duizendtallen" "Thousands Separator": "Scheidingsteken voor duizendtallen",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Espaço", "Space": "Espaço",
"Apostrophe": "Apóstrofo", "Apostrophe": "Apóstrofo",
"None": "Nenhum", "None": "Nenhum",
"Thousands Separator": "Separador de Milhares" "Thousands Separator": "Separador de Milhares",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Пробел", "Space": "Пробел",
"Apostrophe": "Апостроф", "Apostrophe": "Апостроф",
"None": "Нет", "None": "Нет",
"Thousands Separator": "Разделитель тысяч" "Thousands Separator": "Разделитель тысяч",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Пробіл", "Space": "Пробіл",
"Apostrophe": "Апостроф", "Apostrophe": "Апостроф",
"None": "Немає", "None": "Немає",
"Thousands Separator": "Роздільник тисяч" "Thousands Separator": "Роздільник тисяч",
"Indian Number Grouping": "Indian Number Grouping"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "Khoảng trắng", "Space": "Khoảng trắng",
"Apostrophe": "Dấu nháy đơn", "Apostrophe": "Dấu nháy đơn",
"None": "Không có", "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": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "空格", "Space": "空格",
"Apostrophe": "撇号", "Apostrophe": "撇号",
"None": "无", "None": "无",
"Thousands Separator": "千位分隔符" "Thousands Separator": "千位分隔符",
"Indian Number Grouping": "印度数字分组"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -1
View File
@@ -265,7 +265,8 @@
"Space": "空格", "Space": "空格",
"Apostrophe": "撇號", "Apostrophe": "撇號",
"None": "無", "None": "無",
"Thousands Separator": "千位分隔符" "Thousands Separator": "千位分隔符",
"Indian Number Grouping": "印度數字分組"
}, },
"color": { "color": {
"amount": { "amount": {
+2 -2
View File
@@ -8,7 +8,7 @@ import { useOverviewStore } from '@/stores/overview.ts';
import type { TypeAndDisplayName } from '@/core/base.ts'; import type { TypeAndDisplayName } from '@/core/base.ts';
import { WeekDay } from '@/core/datetime.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 UserBasicInfo, User } from '@/models/user.ts';
import { type CategorizedAccount, Account} from '@/models/account.ts'; import { type CategorizedAccount, Account} from '@/models/account.ts';
@@ -64,7 +64,7 @@ export function useUserProfilePageBase() {
const allFiscalYearFormats = computed<TypeAndDisplayName[]>(() => getAllFiscalYearFormats()); const allFiscalYearFormats = computed<TypeAndDisplayName[]>(() => getAllFiscalYearFormats());
const allDecimalSeparators = computed<TypeAndDisplayName[]>(() => getAllDecimalSeparators()); const allDecimalSeparators = computed<TypeAndDisplayName[]>(() => getAllDecimalSeparators());
const allDigitGroupingSymbols = computed<TypeAndDisplayName[]>(() => getAllDigitGroupingSymbols()); const allDigitGroupingSymbols = computed<TypeAndDisplayName[]>(() => getAllDigitGroupingSymbols());
const allDigitGroupingTypes = computed<LocalizedDigitGroupingType[]>(() => getAllDigitGroupingTypes()); const allDigitGroupingTypes = computed<LocalizedDigitGroupingType[]>(() => getAllDigitGroupingTypes(DigitGroupingSymbol.valueOf(newProfile.value.digitGroupingSymbol)?.symbol || DigitGroupingSymbol.Default.symbol));
const allCurrencyDisplayTypes = computed<TypeAndDisplayName[]>(() => getAllCurrencyDisplayTypes()); const allCurrencyDisplayTypes = computed<TypeAndDisplayName[]>(() => getAllCurrencyDisplayTypes());
const allCoordinateDisplayTypes = computed<TypeAndDisplayName[]>(() => getAllCoordinateDisplayTypes()); const allCoordinateDisplayTypes = computed<TypeAndDisplayName[]>(() => getAllCoordinateDisplayTypes());
const allExpenseAmountColorTypes = computed<TypeAndDisplayName[]>(() => getAllExpenseAmountColors()); const allExpenseAmountColorTypes = computed<TypeAndDisplayName[]>(() => getAllExpenseAmountColors());