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:
Khuddite
2024-11-23 14:12:25 -05:00
committed by GitHub
parent 4dd62e24ea
commit e428474478
2 changed files with 133 additions and 6 deletions
@@ -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)
}
})
}
})
+41 -6
View File
@@ -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) => {
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',
maximumFractionDigits: 1,
// `1,953` shouldn't be rounded up to 2k, it should be truncated.
// @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',
// Ideally we'd use roundingMode: 'trunc' but it isn't supported on RN.
})
}