fixing Numbers formatting (#14403)
fix #13880 --------- Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
import { detectNumberFormat } from '../detectNumberFormat';
|
||||
|
||||
// Mock navigator.language
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'en-US',
|
||||
});
|
||||
|
||||
describe('detectNumberFormat', () => {
|
||||
beforeEach(() => {
|
||||
// Reset to default
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'en-US',
|
||||
});
|
||||
});
|
||||
|
||||
it('should detect COMMAS_AND_DOT format for en-US locale', () => {
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'en-US',
|
||||
});
|
||||
expect(detectNumberFormat()).toBe('COMMAS_AND_DOT');
|
||||
});
|
||||
|
||||
it('should detect SPACES_AND_COMMA format for fr-FR locale', () => {
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'fr-FR',
|
||||
});
|
||||
expect(detectNumberFormat()).toBe('SPACES_AND_COMMA');
|
||||
});
|
||||
|
||||
it('should detect DOTS_AND_COMMA format for de-DE locale', () => {
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'de-DE',
|
||||
});
|
||||
expect(detectNumberFormat()).toBe('DOTS_AND_COMMA');
|
||||
});
|
||||
|
||||
it('should detect APOSTROPHE_AND_DOT format for de-CH locale', () => {
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'de-CH',
|
||||
});
|
||||
expect(detectNumberFormat()).toBe('APOSTROPHE_AND_DOT');
|
||||
});
|
||||
|
||||
it('should fallback to COMMAS_AND_DOT for unknown patterns', () => {
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'ja-JP', // Uses different separators not in our patterns
|
||||
});
|
||||
expect(detectNumberFormat()).toBe('COMMAS_AND_DOT');
|
||||
});
|
||||
|
||||
it('should handle invalid locale gracefully', () => {
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: 'invalid-locale',
|
||||
});
|
||||
expect(detectNumberFormat()).toBe('COMMAS_AND_DOT');
|
||||
});
|
||||
|
||||
it('should handle missing navigator.language', () => {
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
writable: true,
|
||||
value: undefined,
|
||||
});
|
||||
expect(detectNumberFormat()).toBe('COMMAS_AND_DOT');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NumberFormat } from '@/localization/constants/NumberFormat';
|
||||
|
||||
const SPACE_CHARS = new Set([' ', '\u00A0', '\u202F']); // space, non-breaking space, narrow no-break space
|
||||
const APOSTROPHE_CHARS = new Set(["'", '\u2019']); // apostrophe, right single quotation mark
|
||||
|
||||
const FORMAT_PATTERNS = new Map<string, keyof typeof NumberFormat>([
|
||||
[',|.', NumberFormat.COMMAS_AND_DOT],
|
||||
['.|,', NumberFormat.DOTS_AND_COMMA],
|
||||
['space|,', NumberFormat.SPACES_AND_COMMA],
|
||||
['apostrophe|.', NumberFormat.APOSTROPHE_AND_DOT],
|
||||
]);
|
||||
|
||||
export const detectNumberFormat = (): keyof typeof NumberFormat => {
|
||||
const testNumber = 1234567.89;
|
||||
let language = navigator?.language || 'en-US';
|
||||
|
||||
let formatter: Intl.NumberFormat;
|
||||
try {
|
||||
formatter = new Intl.NumberFormat(language);
|
||||
} catch {
|
||||
formatter = new Intl.NumberFormat('en-US');
|
||||
}
|
||||
|
||||
const parts = formatter.formatToParts(testNumber);
|
||||
const thousandSeparator =
|
||||
parts.find((part) => part.type === 'group')?.value || '';
|
||||
const decimalSeparator =
|
||||
parts.find((part) => part.type === 'decimal')?.value || '';
|
||||
|
||||
let thousandCategory: string;
|
||||
if (SPACE_CHARS.has(thousandSeparator)) {
|
||||
thousandCategory = 'space';
|
||||
} else if (APOSTROPHE_CHARS.has(thousandSeparator)) {
|
||||
thousandCategory = 'apostrophe';
|
||||
} else {
|
||||
thousandCategory = thousandSeparator;
|
||||
}
|
||||
|
||||
const pattern = `${thousandCategory}|${decimalSeparator}`;
|
||||
return FORMAT_PATTERNS.get(pattern) || NumberFormat.COMMAS_AND_DOT;
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { NumberFormat } from '@/localization/constants/NumberFormat';
|
||||
import { detectNumberFormat } from '@/localization/utils/detectNumberFormat';
|
||||
import { WorkspaceMemberNumberFormatEnum } from '~/generated/graphql';
|
||||
|
||||
export const getNumberFormatFromWorkspaceNumberFormat = (
|
||||
numberFormat: WorkspaceMemberNumberFormatEnum,
|
||||
): NumberFormat => {
|
||||
switch (numberFormat) {
|
||||
case WorkspaceMemberNumberFormatEnum.SYSTEM:
|
||||
return NumberFormat[detectNumberFormat()];
|
||||
case WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT:
|
||||
return NumberFormat.COMMAS_AND_DOT;
|
||||
case WorkspaceMemberNumberFormatEnum.SPACES_AND_COMMA:
|
||||
return NumberFormat.SPACES_AND_COMMA;
|
||||
case WorkspaceMemberNumberFormatEnum.DOTS_AND_COMMA:
|
||||
return NumberFormat.DOTS_AND_COMMA;
|
||||
case WorkspaceMemberNumberFormatEnum.APOSTROPHE_AND_DOT:
|
||||
return NumberFormat.APOSTROPHE_AND_DOT;
|
||||
default:
|
||||
return NumberFormat.COMMAS_AND_DOT;
|
||||
}
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { NumberFormat } from '@/localization/constants/NumberFormat';
|
||||
import { WorkspaceMemberNumberFormatEnum } from '~/generated/graphql';
|
||||
|
||||
export const getWorkspaceNumberFormatFromNumberFormat = (
|
||||
numberFormat: NumberFormat,
|
||||
): WorkspaceMemberNumberFormatEnum => {
|
||||
switch (numberFormat) {
|
||||
case NumberFormat.SYSTEM:
|
||||
return WorkspaceMemberNumberFormatEnum.SYSTEM;
|
||||
case NumberFormat.COMMAS_AND_DOT:
|
||||
return WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT;
|
||||
case NumberFormat.SPACES_AND_COMMA:
|
||||
return WorkspaceMemberNumberFormatEnum.SPACES_AND_COMMA;
|
||||
case NumberFormat.DOTS_AND_COMMA:
|
||||
return WorkspaceMemberNumberFormatEnum.DOTS_AND_COMMA;
|
||||
case NumberFormat.APOSTROPHE_AND_DOT:
|
||||
return WorkspaceMemberNumberFormatEnum.APOSTROPHE_AND_DOT;
|
||||
default:
|
||||
return WorkspaceMemberNumberFormatEnum.COMMAS_AND_DOT;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user