近期在執行將 DB 回傳的數字,根據瀏覽器所提供的語系來決定顯示方式,那麼有沒有反向從顯示數字轉回 DB 內的數字儲存格式呢?網路上有找到一個這樣的轉換實作,值得筆記下來。
內容
根據 Mike Bostock 的這篇:https://observablehq.com/@mbostock/localized-number-parsing,將其調整成能通過 TypeScript 的版本
1. 運用 Intl.NumberFormat.formatToParts 方法,來取得一個物件,內含某個區域的 integer, decimal 和 fraction 類型和值。後續會用到的是 integer 和 decimal
1 2 3 4 5 6 7 8 9 10 11 |
new Intl.NumberFormat('en-US').formatToParts(1234567.6); [ { type: 'integer', value: '1' }, { type: 'group', value: ',' }, { type: 'integer', value: '234' }, { type: 'group', value: ',' }, { type: 'integer', value: '567' }, { type: 'decimal', value: '.' }, { type: 'fraction', value: '60' } ] |
2. 接著用 […new Intl.NumberFormat(locale, {useGrouping: false}).format(9876543210)].reverse(),來生成該語系下對應阿拉伯數字的 0 ~ 9 是怎麼顯示的
1 2 3 4 5 |
new Intl.NumberFormat(locale, {useGrouping: false}).format(9876543210) // 使用 useGrouping 是確保不會有分隔號 // 在使用阿拉伯數字為系統的地區,這結果都會是 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] |
3. 接著生成一組 Map 的 Index 來對應上述的 numerals
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
new Map(numerals.map((d, i) => [d, i])) // 以 en-us 為例 Map(10) { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9 } |
4. 生成 group (在數字顯示時,分群的符號) 的正則表達式。以 en-us 而言,這會是 “,” ;若以 de-de 而言,這會是 “.”
5. 同理,生成 decimal 的正則表達式,代表該地區是如何表示小數點的
6. 生成一個將所有代表該地區數字的 0-9 的正則表達式
7. 接著撰寫一個在方便在 Map 中找尋文字對應的函式
所以,程式碼就會是以下這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/** * format number based on internationalization API * @param {string} locale * @return {function} */ export const localizedNumberParser = (locale: string) => { const parts = new Intl.NumberFormat(locale).formatToParts(12345.6) const numerals = [ ...new Intl.NumberFormat(locale, { useGrouping: false }).format(9876543210), ].reverse() const index = new Map(numerals.map((d, i) => [d, i])) const _group = new RegExp( `[${parts.find((d) => d.type === "group")?.value}]`, "g" ) const _decimal = new RegExp( `[${parts.find((d) => d.type === "decimal")?.value}]` ) const _numeral = new RegExp(`[${numerals.join("")}]`, "g") const _index = (d: string) => index.get(d) return { parse: (string: string): number => { return (string = string .trim() .replace(_group, "") .replace(_decimal, ".") .replace(_numeral, (d) => _index(d) as unknown as string)) ? +string : NaN }, } } new localizedNumberParser('de').parse("123.456,7") // 123456.7 new localizedNumberParser('de').parse("1,234567") // 1.234567 new localizedNumberParser('de').parse("1234567") // 1234567 new localizedNumberParser('de').parse("1,234,567") // NaN |
參考資料
1. Can I use Search NumberFormat
2. Localized Number Parsing