-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]> * Handle big numbers, clarify special case * Clarify the reason --------- Co-authored-by: Dan Abramov <[email protected]>
- Loading branch information
Showing
2 changed files
with
133 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
}) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
}) | ||
} |