Fix inconsistent number formatting between mobile and web (#6384)
* Manual truncation & identify factor points for each lang * Reduce indirection * Add test Co-authored-by: khuddite <biliie811028@hotmail.com> * Handle big numbers, clarify special case * Clarify the reason --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
import {describe, expect, it} from '@jest/globals'
|
||||||
|
|
||||||
|
import {APP_LANGUAGES} from '#/locale/languages'
|
||||||
|
import {formatCount} from '../format'
|
||||||
|
|
||||||
|
const formatCountRound = (locale: string, num: number) => {
|
||||||
|
const options: Intl.NumberFormatOptions = {
|
||||||
|
notation: 'compact',
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
}
|
||||||
|
return new Intl.NumberFormat(locale, options).format(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatCountTrunc = (locale: string, num: number) => {
|
||||||
|
const options: Intl.NumberFormatOptions = {
|
||||||
|
notation: 'compact',
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
// @ts-ignore
|
||||||
|
roundingMode: 'trunc',
|
||||||
|
}
|
||||||
|
return new Intl.NumberFormat(locale, options).format(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const testNums = [
|
||||||
|
1,
|
||||||
|
5,
|
||||||
|
9,
|
||||||
|
11,
|
||||||
|
55,
|
||||||
|
99,
|
||||||
|
111,
|
||||||
|
555,
|
||||||
|
999,
|
||||||
|
1111,
|
||||||
|
5555,
|
||||||
|
9999,
|
||||||
|
11111,
|
||||||
|
55555,
|
||||||
|
99999,
|
||||||
|
111111,
|
||||||
|
555555,
|
||||||
|
999999,
|
||||||
|
1111111,
|
||||||
|
5555555,
|
||||||
|
9999999,
|
||||||
|
11111111,
|
||||||
|
55555555,
|
||||||
|
99999999,
|
||||||
|
111111111,
|
||||||
|
555555555,
|
||||||
|
999999999,
|
||||||
|
1111111111,
|
||||||
|
5555555555,
|
||||||
|
9999999999,
|
||||||
|
11111111111,
|
||||||
|
55555555555,
|
||||||
|
99999999999,
|
||||||
|
111111111111,
|
||||||
|
555555555555,
|
||||||
|
999999999999,
|
||||||
|
1111111111111,
|
||||||
|
5555555555555,
|
||||||
|
9999999999999,
|
||||||
|
11111111111111,
|
||||||
|
55555555555555,
|
||||||
|
99999999999999,
|
||||||
|
111111111111111,
|
||||||
|
555555555555555,
|
||||||
|
999999999999999,
|
||||||
|
1111111111111111,
|
||||||
|
5555555555555555,
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('formatCount', () => {
|
||||||
|
for (const appLanguage of APP_LANGUAGES) {
|
||||||
|
const locale = appLanguage.code2
|
||||||
|
it('truncates for ' + locale, () => {
|
||||||
|
const mockI8nn = {
|
||||||
|
locale,
|
||||||
|
number(num: number) {
|
||||||
|
return formatCountRound(locale, num)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for (const num of testNums) {
|
||||||
|
const formatManual = formatCount(mockI8nn as any, num)
|
||||||
|
const formatOriginal = formatCountTrunc(locale, num)
|
||||||
|
expect(formatManual).toEqual(formatOriginal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,12 +1,47 @@
|
|||||||
import type {I18n} from '@lingui/core'
|
import {I18n} from '@lingui/core'
|
||||||
|
|
||||||
|
const truncateRounding = (num: number, factors: Array<number>): number => {
|
||||||
|
for (let i = factors.length - 1; i >= 0; i--) {
|
||||||
|
let factor = factors[i]
|
||||||
|
if (num >= 10 ** factor) {
|
||||||
|
if (factor === 10) {
|
||||||
|
// CA and ES abruptly jump from "9999,9 M" to "10 mil M"
|
||||||
|
factor--
|
||||||
|
}
|
||||||
|
const precision = 1
|
||||||
|
const divisor = 10 ** (factor - precision)
|
||||||
|
return Math.floor(num / divisor) * divisor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
const koFactors = [3, 4, 8, 12]
|
||||||
|
const hiFactors = [3, 5, 7, 9, 11, 13]
|
||||||
|
const esCaFactors = [3, 6, 10, 12]
|
||||||
|
const itDeFactors = [6, 9, 12]
|
||||||
|
const jaZhFactors = [4, 8, 12]
|
||||||
|
const restFactors = [3, 6, 9, 12]
|
||||||
|
|
||||||
export const formatCount = (i18n: I18n, num: number) => {
|
export const formatCount = (i18n: I18n, num: number) => {
|
||||||
return i18n.number(num, {
|
const locale = i18n.locale
|
||||||
|
let truncatedNum: number
|
||||||
|
if (locale === 'hi') {
|
||||||
|
truncatedNum = truncateRounding(num, hiFactors)
|
||||||
|
} else if (locale === 'ko') {
|
||||||
|
truncatedNum = truncateRounding(num, koFactors)
|
||||||
|
} else if (locale === 'es' || locale === 'ca') {
|
||||||
|
truncatedNum = truncateRounding(num, esCaFactors)
|
||||||
|
} else if (locale === 'ja' || locale === 'zh-CN' || locale === 'zh-TW') {
|
||||||
|
truncatedNum = truncateRounding(num, jaZhFactors)
|
||||||
|
} else if (locale === 'it' || locale === 'de') {
|
||||||
|
truncatedNum = truncateRounding(num, itDeFactors)
|
||||||
|
} else {
|
||||||
|
truncatedNum = truncateRounding(num, restFactors)
|
||||||
|
}
|
||||||
|
return i18n.number(truncatedNum, {
|
||||||
notation: 'compact',
|
notation: 'compact',
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
// `1,953` shouldn't be rounded up to 2k, it should be truncated.
|
// Ideally we'd use roundingMode: 'trunc' but it isn't supported on RN.
|
||||||
// @ts-expect-error: `roundingMode` doesn't seem to be in the typings yet
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#roundingmode
|
|
||||||
roundingMode: 'trunc',
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user