about summary refs log tree commit diff
path: root/src/view/com/util/numeric
diff options
context:
space:
mode:
authorKhuddite <62555977+khuddite@users.noreply.github.com>2024-11-23 14:12:25 -0500
committerGitHub <noreply@github.com>2024-11-23 19:12:25 +0000
commite4284744785495c5832234c79703c1a2f8052b8b (patch)
treeb464e59dacc1e2e5aa444a282cf096eaa915756d /src/view/com/util/numeric
parent4dd62e24ea6e2fc3904678ca7af1236972e067e0 (diff)
downloadvoidsky-e4284744785495c5832234c79703c1a2f8052b8b.tar.zst
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>
Diffstat (limited to 'src/view/com/util/numeric')
-rw-r--r--src/view/com/util/numeric/__tests__/format-test.ts92
-rw-r--r--src/view/com/util/numeric/format.ts47
2 files changed, 133 insertions, 6 deletions
diff --git a/src/view/com/util/numeric/__tests__/format-test.ts b/src/view/com/util/numeric/__tests__/format-test.ts
new file mode 100644
index 000000000..74df4be4c
--- /dev/null
+++ b/src/view/com/util/numeric/__tests__/format-test.ts
@@ -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)
+      }
+    })
+  }
+})
diff --git a/src/view/com/util/numeric/format.ts b/src/view/com/util/numeric/format.ts
index cca9fc7e7..0c3d24957 100644
--- a/src/view/com/util/numeric/format.ts
+++ b/src/view/com/util/numeric/format.ts
@@ -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.
   })
 }