├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.js ├── commitlint.config.js ├── docs ├── advanced.md ├── api │ ├── formatCurrency.md │ ├── formatDate.md │ ├── formatMessage.md │ ├── formatNumber.md │ ├── formatPlural.md │ ├── formatRelativeTime.md │ ├── formatUnit.md │ ├── parseDate.md │ ├── parseNumber.md │ ├── useGlobalize.md │ └── utilities.md ├── components │ ├── FormattedCurrency.md │ ├── FormattedDate.md │ ├── FormattedMessage.md │ ├── FormattedNumber.md │ ├── FormattedPlural.md │ ├── FormattedRelativeTime.md │ ├── FormattedUnit.md │ ├── GlobalizeProvider.md │ └── withGlobalize.md ├── getting-started.md ├── migration-v4.md ├── performance.md ├── ref-usage.md └── web-usage.md ├── gulpfile.js ├── locale-data ├── af-NA.js ├── af.js ├── am.js ├── ar-AE.js ├── ar-BH.js ├── ar-DJ.js ├── ar-DZ.js ├── ar-EG.js ├── ar-EH.js ├── ar-ER.js ├── ar-IL.js ├── ar-IQ.js ├── ar-JO.js ├── ar-KM.js ├── ar-KW.js ├── ar-LB.js ├── ar-LY.js ├── ar-MA.js ├── ar-MR.js ├── ar-OM.js ├── ar-PS.js ├── ar-QA.js ├── ar-SA.js ├── ar-SD.js ├── ar-SO.js ├── ar-SS.js ├── ar-SY.js ├── ar-TD.js ├── ar-TN.js ├── ar-YE.js ├── ar.js ├── as.js ├── az-Latn.js ├── az.js ├── be.js ├── bg.js ├── bn-IN.js ├── bn.js ├── bs-Latn.js ├── bs.js ├── ca-AD.js ├── ca-ES-VALENCIA.js ├── ca-FR.js ├── ca-IT.js ├── ca.js ├── core.js ├── cs.js ├── cy.js ├── da-GL.js ├── da.js ├── de-AT.js ├── de-BE.js ├── de-CH.js ├── de-IT.js ├── de-LI.js ├── de-LU.js ├── de.js ├── el-CY.js ├── el.js ├── en-001.js ├── en-150.js ├── en-AE.js ├── en-AG.js ├── en-AI.js ├── en-AS.js ├── en-AT.js ├── en-AU.js ├── en-BB.js ├── en-BE.js ├── en-BI.js ├── en-BM.js ├── en-BS.js ├── en-BW.js ├── en-BZ.js ├── en-CA.js ├── en-CC.js ├── en-CH.js ├── en-CK.js ├── en-CM.js ├── en-CX.js ├── en-CY.js ├── en-DE.js ├── en-DG.js ├── en-DK.js ├── en-DM.js ├── en-ER.js ├── en-FI.js ├── en-FJ.js ├── en-FK.js ├── en-FM.js ├── en-GB.js ├── en-GD.js ├── en-GG.js ├── en-GH.js ├── en-GI.js ├── en-GM.js ├── en-GU.js ├── en-GY.js ├── en-HK.js ├── en-IE.js ├── en-IL.js ├── en-IM.js ├── en-IN.js ├── en-IO.js ├── en-JE.js ├── en-JM.js ├── en-KE.js ├── en-KI.js ├── en-KN.js ├── en-KY.js ├── en-LC.js ├── en-LR.js ├── en-LS.js ├── en-MG.js ├── en-MH.js ├── en-MO.js ├── en-MP.js ├── en-MS.js ├── en-MT.js ├── en-MU.js ├── en-MW.js ├── en-MY.js ├── en-NA.js ├── en-NF.js ├── en-NG.js ├── en-NL.js ├── en-NR.js ├── en-NU.js ├── en-NZ.js ├── en-PG.js ├── en-PH.js ├── en-PK.js ├── en-PN.js ├── en-PR.js ├── en-PW.js ├── en-RW.js ├── en-SB.js ├── en-SC.js ├── en-SD.js ├── en-SE.js ├── en-SG.js ├── en-SH.js ├── en-SI.js ├── en-SL.js ├── en-SS.js ├── en-SX.js ├── en-SZ.js ├── en-TC.js ├── en-TK.js ├── en-TO.js ├── en-TT.js ├── en-TV.js ├── en-TZ.js ├── en-UG.js ├── en-UM.js ├── en-VC.js ├── en-VG.js ├── en-VI.js ├── en-VU.js ├── en-WS.js ├── en-ZA.js ├── en-ZM.js ├── en-ZW.js ├── en.js ├── es-419.js ├── es-AR.js ├── es-BO.js ├── es-BR.js ├── es-BZ.js ├── es-CL.js ├── es-CO.js ├── es-CR.js ├── es-CU.js ├── es-DO.js ├── es-EA.js ├── es-EC.js ├── es-GQ.js ├── es-GT.js ├── es-HN.js ├── es-IC.js ├── es-MX.js ├── es-NI.js ├── es-PA.js ├── es-PE.js ├── es-PH.js ├── es-PR.js ├── es-PY.js ├── es-SV.js ├── es-US.js ├── es-UY.js ├── es-VE.js ├── es.js ├── et.js ├── eu.js ├── fa-AF.js ├── fa.js ├── fi.js ├── fil.js ├── fr-BE.js ├── fr-BF.js ├── fr-BI.js ├── fr-BJ.js ├── fr-BL.js ├── fr-CA.js ├── fr-CD.js ├── fr-CF.js ├── fr-CG.js ├── fr-CH.js ├── fr-CI.js ├── fr-CM.js ├── fr-DJ.js ├── fr-DZ.js ├── fr-GA.js ├── fr-GF.js ├── fr-GN.js ├── fr-GP.js ├── fr-GQ.js ├── fr-HT.js ├── fr-KM.js ├── fr-LU.js ├── fr-MA.js ├── fr-MC.js ├── fr-MF.js ├── fr-MG.js ├── fr-ML.js ├── fr-MQ.js ├── fr-MR.js ├── fr-MU.js ├── fr-NC.js ├── fr-NE.js ├── fr-PF.js ├── fr-PM.js ├── fr-RE.js ├── fr-RW.js ├── fr-SC.js ├── fr-SN.js ├── fr-SY.js ├── fr-TD.js ├── fr-TG.js ├── fr-TN.js ├── fr-VU.js ├── fr-WF.js ├── fr-YT.js ├── fr.js ├── ga-GB.js ├── ga.js ├── gl.js ├── gu.js ├── he.js ├── hi.js ├── hr-BA.js ├── hr.js ├── hu.js ├── hy.js ├── id.js ├── is.js ├── it-CH.js ├── it-SM.js ├── it-VA.js ├── it.js ├── ja.js ├── jv.js ├── ka.js ├── kk.js ├── km.js ├── kn.js ├── ko-KP.js ├── ko.js ├── ky.js ├── lo.js ├── lt.js ├── lv.js ├── mk.js ├── ml.js ├── mn.js ├── mr.js ├── ms-BN.js ├── ms-SG.js ├── ms.js ├── my.js ├── nb-SJ.js ├── nb.js ├── ne-IN.js ├── ne.js ├── nl-AW.js ├── nl-BE.js ├── nl-BQ.js ├── nl-CW.js ├── nl-SR.js ├── nl-SX.js ├── nl.js ├── or.js ├── pa-Guru.js ├── pa.js ├── pl.js ├── ps-PK.js ├── ps.js ├── pt-AO.js ├── pt-CH.js ├── pt-CV.js ├── pt-GQ.js ├── pt-GW.js ├── pt-LU.js ├── pt-MO.js ├── pt-MZ.js ├── pt-PT.js ├── pt-ST.js ├── pt-TL.js ├── pt.js ├── ro-MD.js ├── ro.js ├── ru-BY.js ├── ru-KG.js ├── ru-KZ.js ├── ru-MD.js ├── ru-UA.js ├── ru.js ├── sd.js ├── si.js ├── sk.js ├── sl.js ├── so-DJ.js ├── so-ET.js ├── so-KE.js ├── so.js ├── sq-MK.js ├── sq-XK.js ├── sq.js ├── sr-Cyrl-BA.js ├── sr-Cyrl-ME.js ├── sr-Cyrl-XK.js ├── sr-Cyrl.js ├── sr-Latn-BA.js ├── sr-Latn-ME.js ├── sr-Latn-XK.js ├── sr-Latn.js ├── sr.js ├── sv-AX.js ├── sv-FI.js ├── sv.js ├── sw-CD.js ├── sw-KE.js ├── sw-UG.js ├── sw.js ├── ta-LK.js ├── ta-MY.js ├── ta-SG.js ├── ta.js ├── te.js ├── th.js ├── tk.js ├── tr-CY.js ├── tr.js ├── uk.js ├── ur-IN.js ├── ur.js ├── uz-Latn.js ├── uz.js ├── vi.js ├── yue-Hant.js ├── yue.js ├── zh-Hans-HK.js ├── zh-Hans-MO.js ├── zh-Hans-SG.js ├── zh-Hans.js ├── zh-Hant-HK.js ├── zh-Hant-MO.js ├── zh-Hant.js ├── zh.js └── zu.js ├── package.json ├── src ├── components │ ├── FormattedPlural.tsx │ ├── GlobalizeProvider.tsx │ ├── __tests__ │ │ ├── FormattedCurrency.test.tsx │ │ ├── FormattedDate.test.tsx │ │ ├── FormattedMessage.test.tsx │ │ ├── FormattedNumber.test.tsx │ │ ├── FormattedPlural.test.tsx │ │ ├── FormattedRelativeTime.test.tsx │ │ ├── FormattedUnit.test.tsx │ │ ├── GlobalizeProvider.test.tsx │ │ ├── __snapshots__ │ │ │ ├── FormattedCurrency.test.tsx.snap │ │ │ ├── FormattedDate.test.tsx.snap │ │ │ ├── FormattedMessage.test.tsx.snap │ │ │ ├── FormattedNumber.test.tsx.snap │ │ │ ├── FormattedPlural.test.tsx.snap │ │ │ ├── FormattedRelativeTime.test.tsx.snap │ │ │ ├── FormattedUnit.test.tsx.snap │ │ │ ├── GlobalizeProvider.test.tsx.snap │ │ │ └── withGlobalize.test.tsx.snap │ │ └── withGlobalize.test.tsx │ ├── index.tsx │ ├── utils.tsx │ └── withGlobalize.tsx ├── context.ts ├── globalize │ ├── __tests__ │ │ └── index.test.ts │ ├── cache.ts │ ├── formatters │ │ ├── currency.ts │ │ ├── date.ts │ │ ├── index.ts │ │ ├── message.ts │ │ ├── number.ts │ │ ├── plural.ts │ │ ├── relativeTime.ts │ │ └── unit.ts │ ├── index.ts │ ├── loaders.ts │ ├── types.ts │ └── utils.ts ├── hooks │ └── index.ts └── index.ts ├── test ├── createWithGlobalize.tsx └── setup.test.js ├── tsconfig.json ├── utils ├── cldr.js └── currencies.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | working_directory: ~/repo 6 | docker: 7 | - image: circleci/node:12 8 | steps: 9 | - checkout 10 | - restore_cache: 11 | keys: 12 | - yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }} 13 | - yarn-packages-v1-{{ .Branch }}- 14 | - yarn-packages-v1- 15 | - run: 16 | name: yarn install 17 | command: yarn install --frozen-lockfile 18 | - save_cache: 19 | key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }} 20 | paths: 21 | - ~/.cache/yarn 22 | - run: mkdir artifacts 23 | - run: 24 | name: lint 25 | command: yarn lint --format junit --output-file ./artifacts/eslint/results.xml 26 | - run: 27 | name: test 28 | command: yarn test --ci --reporters=default --reporters=jest-junit 29 | - run: 30 | name: coverage 31 | command: COVERALLS_GIT_BRANCH=$CIRCLE_BRANCH cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 32 | - store_artifacts: 33 | path: artifacts/eslint/results.xml 34 | prefix: lint 35 | - store_artifacts: 36 | path: artifacts/jest/results.xml 37 | prefix: tests 38 | - store_artifacts: 39 | path: coverage 40 | prefix: coverage 41 | - store_test_results: 42 | path: artifacts 43 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "jsx": true 6 | }, 7 | "sourceType": "module", 8 | "useJSXTextNode": true 9 | }, 10 | "extends": [ 11 | "airbnb", 12 | "airbnb/hooks", 13 | "plugin:@typescript-eslint/recommended", 14 | "prettier", 15 | "prettier/react", 16 | "prettier/@typescript-eslint" 17 | ], 18 | "plugins": ["prettier", "@typescript-eslint"], 19 | "settings": { 20 | "import/resolver": { 21 | "node": { 22 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 23 | } 24 | } 25 | }, 26 | "rules": { 27 | "prettier/prettier": ["error"], 28 | "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], 29 | "no-underscore-dangle": ["error", { "allow": ["_raw"] }], 30 | "no-use-before-define": "off", 31 | "import/extensions": [ 32 | "error", 33 | "ignorePackages", 34 | { 35 | "js": "never", 36 | "mjs": "never", 37 | "jsx": "never", 38 | "ts": "never", 39 | "tsx": "never" 40 | } 41 | ], 42 | "import/prefer-default-export": "off", 43 | "react/jsx-filename-extension": ["error", { "extensions": [".jsx", ".tsx"] }], 44 | "react/jsx-props-no-spreading": "off", 45 | "react/prop-types": "off", 46 | "@typescript-eslint/ban-ts-comment": "off", 47 | "@typescript-eslint/camelcase": "off", 48 | "@typescript-eslint/explicit-function-return-type": "off", 49 | "@typescript-eslint/explicit-member-accessibility": "off", 50 | "@typescript-eslint/explicit-module-boundary-types": "off", 51 | "@typescript-eslint/no-explicit-any": "off", 52 | "@typescript-eslint/no-non-null-assertion": "off", 53 | "@typescript-eslint/no-unused-vars": "error" 54 | }, 55 | "overrides": [ 56 | { 57 | "files": ["*.test.*"], 58 | "env": { "jest": true, "node": true }, 59 | "rules": { 60 | "global-require": "off", 61 | "@typescript-eslint/no-empty-function": "off" 62 | } 63 | }, 64 | { 65 | "files": ["*.js"], 66 | "rules": { 67 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], 68 | "@typescript-eslint/no-unused-vars": "off", 69 | "@typescript-eslint/no-var-requires": "off" 70 | } 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /artifacts 2 | /dist 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directory 31 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 32 | node_modules 33 | 34 | 35 | ### Windows ### 36 | # Windows image file caches 37 | Thumbs.db 38 | ehthumbs.db 39 | 40 | # Folder config file 41 | Desktop.ini 42 | 43 | # Recycle Bin used on file shares 44 | $RECYCLE.BIN/ 45 | 46 | # Windows Installer files 47 | *.cab 48 | *.msi 49 | *.msm 50 | *.msp 51 | 52 | # Windows shortcuts 53 | *.lnk 54 | 55 | 56 | ### OSX ### 57 | .DS_Store 58 | .AppleDouble 59 | .LSOverride 60 | 61 | # Icon must end with two \r 62 | Icon 63 | 64 | 65 | # Thumbnails 66 | ._* 67 | 68 | # Files that might appear in the root of a volume 69 | .DocumentRevisions-V100 70 | .fseventsd 71 | .Spotlight-V100 72 | .TemporaryItems 73 | .Trashes 74 | .VolumeIcon.icns 75 | 76 | # Directories potentially created on remote AFP share 77 | .AppleDB 78 | .AppleDesktop 79 | Network Trash Folder 80 | Temporary Items 81 | .apdisk 82 | 83 | 84 | ### Linux ### 85 | *~ 86 | 87 | # KDE directory preferences 88 | .directory 89 | 90 | # Linux trash folder which might appear on any partition or disk 91 | .Trash-* 92 | 93 | 94 | ### SublimeText ### 95 | # cache files for sublime text 96 | *.tmlanguage.cache 97 | *.tmPreferences.cache 98 | *.stTheme.cache 99 | 100 | # workspace files are user-specific 101 | *.sublime-workspace 102 | 103 | # project files should be checked into the repository, unless a significant 104 | # proportion of contributors will probably not be using SublimeText 105 | # *.sublime-project 106 | 107 | # sftp configuration file 108 | sftp-config.json 109 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .eslintignore 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2020 Josh Swan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Globalize 2 | 3 | [![Version][version-image]][package-url] [![Downloads][downloads-image]][package-url] [![Build Status][build-image]][build-url] [![Coverage][coverage-image]][coverage-url] [![License][license-image]][license-url] 4 | 5 | React Native Globalize provides an easy-to-use and powerful solution for internationalization (i18n) in React Native apps. 6 | 7 | ## Example 8 | 9 | Using the `useGlobalize()` hook: 10 | 11 | ```javascript 12 | import { useGlobalize } from 'react-native-globalize'; 13 | 14 | export const HookExample = () => { 15 | const { formatCurrency } = useGlobalize(); 16 | 17 | return ( 18 | 19 | {formatCurrency(1000, 'USD', { 20 | maximumFractionDigits: 0, 21 | useGrouping: true, 22 | })} 23 | 24 | ); 25 | }; 26 | ``` 27 | 28 | Using a `Formatted` component: 29 | 30 | ```javascript 31 | import { FormattedCurrency } from 'react-native-globalize'; 32 | 33 | export const ComponentExample = () => ( 34 | 35 | ); 36 | ``` 37 | 38 | ## Docs 39 | 40 | - [Installation](docs/getting-started.md) 41 | - [Upgrading to v4](docs/migration-v4.md) 42 | - [Usage](docs/getting-started.md#usage) 43 | - [Performance](docs/performance.md) 44 | - [Advanced](docs/advanced.md) 45 | - [Usage Outside React](docs/ref-usage.md) 46 | - [Usage on the Web](docs/web-usage.md) 47 | - API 48 | - [`useGlobalize`](docs/api/useGlobalize.md) 49 | - [`formatCurrency`](docs/api/formatCurrency.md) 50 | - [`formatDate`](docs/api/formatDate.md) 51 | - [`formatMessage`](docs/api/formatMessage.md) 52 | - [`formatNumber`](docs/api/formatNumber.md) 53 | - [`formatPlural`](docs/api/formatPlural.md) 54 | - [`formatRelativeTime`](docs/api/formatRelativeTime.md) 55 | - [`formatUnit`](docs/api/formatUnit.md) 56 | - [`parseDate`](docs/api/parseDate.md) 57 | - [`parseNumber`](docs/api/parseNumber.md) 58 | - [Utilities](docs/api/utilities.md) 59 | - [`getAvailableLocales`](docs/api/utilities.md#getavailablelocales) 60 | - [`getCurrencySymbol`](docs/api/utilities.md#getcurrencysymbol) 61 | - [`loadCldr`](docs/api/utilities.md#loadcldr) 62 | - [`loadMessages`](docs/api/utilities.md#loadmessages) 63 | - [`localeIsLoaded`](docs/api/utilities.md#localeisloaded) 64 | - Components 65 | - [`GlobalizeProvider`](docs/components/GlobalizeProvider.md) 66 | - [`FormattedCurrency`](docs/components/FormattedCurrency.md) 67 | - [`FormattedDate`](docs/components/FormattedDate.md) 68 | - [`FormattedMessage`](docs/components/FormattedMessage.md) 69 | - [`FormattedNumber`](docs/components/FormattedNumber.md) 70 | - [`FormattedPlural`](docs/components/FormattedPlural.md) 71 | - [`FormattedRelativeTime`](docs/components/FormattedRelativeTime.md) 72 | - [`FormattedUnit`](docs/components/FormattedUnit.md) 73 | - [`withGlobalize`](docs/components/withGlobalize.md) 74 | 75 | ## Supported By 76 | 77 | 78 | Disruptive Labs 79 | 80 | 81 | ## License 82 | 83 | ```text 84 | Copyright (c) 2015-2020 Josh Swan 85 | 86 | Licensed under the The MIT License (MIT) (the "License"); 87 | you may not use this file except in compliance with the License. 88 | You may obtain a copy of the License at 89 | 90 | https://raw.githubusercontent.com/joshswan/react-native-globalize/master/LICENSE 91 | 92 | Unless required by applicable law or agreed to in writing, software 93 | distributed under the License is distributed on an "AS IS" BASIS, 94 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 95 | 96 | See the License for the specific language governing permissions and 97 | limitations under the License. 98 | ``` 99 | 100 | [build-image]: https://img.shields.io/circleci/build/gh/joshswan/react-native-globalize?style=flat-square 101 | [build-url]: https://circleci.com/gh/joshswan/react-native-globalize 102 | [coverage-image]: https://img.shields.io/coveralls/github/joshswan/react-native-globalize?style=flat-square 103 | [coverage-url]: https://coveralls.io/github/joshswan/react-native-globalize 104 | [downloads-image]: https://img.shields.io/npm/dm/react-native-globalize?style=flat-square 105 | [license-image]: https://img.shields.io/npm/l/react-native-globalize?color=blue&style=flat-square 106 | [license-url]: https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 107 | [package-url]: https://www.npmjs.com/package/react-native-globalize 108 | [version-image]: https://img.shields.io/npm/v/react-native-globalize?style=flat-square 109 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | module.exports = { 10 | presets: ['module:metro-react-native-babel-preset', '@babel/preset-typescript'], 11 | }; 12 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | module.exports = { 10 | extends: ['@commitlint/config-conventional'], 11 | }; 12 | -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | # Advanced 2 | 3 | Assuming you're aware of the potential pitfalls covered by the [Performance](performance.md) guide, it's unlikely you'll need to actually use any of the recipes included in this section. Nonetheless, some users will find themselves needing to optimize their React Native Globalize usage in order to keep apps buttery smooth. 4 | 5 | ## On-Demand Loading of Locales 6 | 7 | As mentioned previously, only the locales that are actually going to be used should be loaded in order to avoid wasted costs. However, some apps require supporting many more locales than others. However, it's unlikely that one user of the app is actually going to use more than a couple of locales. Therefore, loading all the necessary locales on startup would just slow down the app boot and load up the device memory with data that won't be needed. 8 | 9 | A better strategy would be to only load the locale data once it's needed (i.e. when a user switches locales or once you detect the device locale). There's one caveat, however. The React Native bundler will only include files that are directly referenced in a `require` call. So we can't just fire off `loadCldr` with a template string and be done unfortunately. 10 | 11 | Below is an example that loads locale data on-demand and is meant to inspire your own solution: 12 | 13 | ```js 14 | import React from 'react'; 15 | import { Text, TouchableOpacity, View } from 'react-native'; 16 | import { loadCldr } from 'react-native-globalize'; 17 | 18 | const localeLoaders = { 19 | de: () => loadCldr(require('react-native-globalize/locale-data/de')), 20 | en: () => loadCldr(require('react-native-globalize/locale-data/en')), 21 | es: () => loadCldr(require('react-native-globalize/locale-data/es')), 22 | }; 23 | 24 | const LocaleSwitcher = () => ( 25 | 26 | {['de', 'en', 'es'].map((locale) => ( 27 | { 30 | // Load locale data 31 | localeLoaders[locale](); 32 | 33 | // Update locale on using 34 | // state, redux action, etc. 35 | switchLocale(locale); 36 | }} 37 | > 38 | 39 | Switch to {locale} 40 | 41 | 42 | ))} 43 | 44 | ); 45 | ``` 46 | 47 | ## Formatting Function Creators 48 | 49 | As described in the previous guide, React Native Globalize memoizes the formatting function creators, eliminating the cost of building the same formatting function repeatedly. However, there is still a slight cost associated with serializing the formatting options and retrieving the formatter from the cache. This should basically never be an issue unless you're doing some high-performance rendering or otherwise calling the formatter repeatedly in rapid succession. 50 | 51 | For situations such as these, you can take advantage of the `getFormatter` functions on the Globalize object. The main functions, such as `formatDate`, use these `getFormatter` functions internally in order to build or retrieve their formatting functions. But to avoid repeatedly going to the cache, you can simply call them directly yourself and hold the reference to the function within your component. 52 | 53 | ```js 54 | import React from 'react'; 55 | import { Text, View } from 'react-native'; 56 | import { useGlobalize } from 'react-native-globalize'; 57 | 58 | const ExpensiveComponent = ({ list }) => { 59 | // `getDateFormatter` returns a direct reference to a date formatter 60 | // function and does not require retrieval from the memoization cache 61 | // even if called repeatedly 62 | const { getDateFormatter } = useGlobalize(); 63 | const format = getDateFormatter({ skeleton: 'yMd' }); 64 | 65 | return ( 66 | 67 | {list.map((item) => ( 68 | 69 | {myDateFormatter(item.date)} 70 | 71 | ))} 72 | 73 | ) 74 | }; 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/api/formatCurrency.md: -------------------------------------------------------------------------------- 1 | # `formatCurrency` 2 | 3 | Formats a number as currency based on the locale and options. 4 | 5 | `formatCurrency(value, currencyCode?, options?)` 6 | 7 | ## Arguments 8 | 9 | - `value` - *number* - Number to be formatted 10 | - `currencyCode?` - *string* - Currency code (defaults to currency set on `GlobalizeProvider`) 11 | - [`options?`](#options) - *object* - Formatting options 12 | 13 | ## Example 14 | 15 | ```js 16 | import { useGlobalize } from 'react-native-globalize'; 17 | 18 | const ExampleComponent = () => { 19 | const { formatCurrency } = useGlobalize(); 20 | 21 | formatCurrency(1000.99, 'USD'); 22 | // $1,000.99 23 | }; 24 | ``` 25 | 26 | ## Options 27 | 28 | - [`compact`](#compact) 29 | - [`maximumFractionDigits`](#maximumfractiondigits) 30 | - [`maximumSignificantDigits`](#maximumsignificantdigits) 31 | - [`minimumFractionDigits`](#minimumfractiondigits) 32 | - [`minimumIntegerDigits`](#minimumintegerdigits) 33 | - [`minimumSignificantDigits`](#minimumsignificantdigits) 34 | - [`round`](#round) 35 | - [`style`](#style) 36 | - [`symbolForm`](#symbolform) 37 | - [`useGrouping`](#usegrouping) 38 | 39 | ### `compact` 40 | 41 | | Type | Required | Default | Description | 42 | | :----: | :------: | :-----: | :---------: | 43 | | string | No | none | Use compact number format. Possible values: `short`, `long`. | 44 | 45 | ```js 46 | formatCurrency(1000.99, 'USD', { compact: 'short' }); 47 | // $1K 48 | ``` 49 | 50 | ### `maximumFractionDigits` 51 | 52 | | Type | Required | Default | Description | 53 | | :----: | :------: | :-----: | :---------: | 54 | | number | No | none | Override maximum fraction digits. Numbers will be rounded if needed based on [`round`](#round) option. | 55 | 56 | ```js 57 | formatCurrency(1000.99, 'USD', { maximumFractionDigits: 0 }); 58 | // $1,001 59 | ``` 60 | 61 | ### `maximumSignificantDigits` 62 | 63 | | Type | Required | Default | Description | 64 | | :----: | :------: | :-----: | :---------: | 65 | | number | No | none | Override maximum significant (integer + fraction) digits. Numbers will be rounded if needed based on [`round`](#round) option. Must also specify [`minimumSignificantDigits`](#minimumsignificantdigits). | 66 | 67 | ```js 68 | formatCurrency(1000.99, 'USD', { minimumSignificantDigits: 2, maximumSignificantDigits: 4 }); 69 | // $1,001 70 | ``` 71 | 72 | ### `minimumFractionDigits` 73 | 74 | | Type | Required | Default | Description | 75 | | :----: | :------: | :-----: | :---------: | 76 | | number | No | none | Override minimum fraction digits. Numbers will be rounded based on [`round`](#round) option or padded if needed. | 77 | 78 | ```js 79 | formatCurrency(1000.99, 'USD', { minimumFractionDigits: 4 }); 80 | // $1,000.9900 81 | ``` 82 | 83 | ### `minimumIntegerDigits` 84 | 85 | | Type | Required | Default | Description | 86 | | :----: | :------: | :-----: | :---------: | 87 | | number | No | none | Override minimum integer digits. Numbers will be padded if needed. | 88 | 89 | ```js 90 | formatCurrency(1000.99, 'USD', { minimumIntegerDigits: 6 }); 91 | // $001,000.99 92 | ``` 93 | 94 | ### `minimumSignificantDigits` 95 | 96 | | Type | Required | Default | Description | 97 | | :----: | :------: | :-----: | :---------: | 98 | | number | No | none | Override minimum significant (integer + fraction) digits. Numbers will be padded if needed. Must also specify [`maximumSignificantDigits`](#maximumsignificantdigits). | 99 | 100 | ```js 101 | formatCurrency(1000.99, 'USD', { minimumSignificantDigits: 8, maximumSignificantDigits: 10 }); 102 | // $1,000.9900 103 | ``` 104 | 105 | ### `round` 106 | 107 | | Type | Required | Default | Description | 108 | | :----: | :------: | :-----: | :---------: | 109 | | string | No | `round` | Specify rounding behavior. Possible values: `ceil`, `floor`, `round`, `truncate`. | 110 | 111 | ```js 112 | formatCurrency(1000.99, 'USD', { maximumFractionDigits: 0, round: 'floor' }); 113 | // $1,000 114 | ``` 115 | 116 | ### `style` 117 | 118 | | Type | Required | Default | Description | 119 | | :----: | :------: | :-----: | :---------: | 120 | | string | No | `symbol` | Change display style. Possible values: `symbol`, `accounting`, `code`, `name`. | 121 | 122 | ```js 123 | formatCurrency(-1000.99, 'USD', { style: 'accounting' }); 124 | // ($1,000.99) 125 | formatCurrency(1000.99, 'USD', { style: 'code' }); 126 | // 1,000.99 USD 127 | formatCurrency(1000.99, 'USD', { style: 'name' }); 128 | // 1,000.99 US dollars 129 | ``` 130 | 131 | ### `symbolForm` 132 | 133 | | Type | Required | Default | Description | 134 | | :----: | :------: | :-----: | :---------: | 135 | | string | No | none | Use alternative narrow currency symbol. Possible values: `narrow`. | 136 | 137 | ```js 138 | formatCurrency(1000.99, 'CAD'); 139 | // CA$1,000.99 140 | formatCurrency(1000.99, 'CAD', { symbolForm: 'narrow' }); 141 | // $1,000.99 142 | ``` 143 | 144 | ### `useGrouping` 145 | 146 | | Type | Required | Default | Description | 147 | | :----: | :------: | :-----: | :---------: | 148 | | boolean | No | `true` | Whether to use grouping separator. | 149 | 150 | ```js 151 | formatCurrency(1000.99, 'USD', { useGrouping: false }); 152 | // $1000.99 153 | ``` 154 | -------------------------------------------------------------------------------- /docs/api/formatDate.md: -------------------------------------------------------------------------------- 1 | # `formatDate` 2 | 3 | Formats a date based on the locale and options. 4 | 5 | `formatDate(value, options?)` 6 | 7 | ## Arguments 8 | 9 | - `value` - *Date* - Date object to be formatted 10 | - [`options?`](#options) - *object* - Formatting options 11 | 12 | ## Example 13 | 14 | ```js 15 | import { useGlobalize } from 'react-native-globalize'; 16 | 17 | const ExampleComponent = () => { 18 | const { formatDate } = useGlobalize(); 19 | 20 | formatDate(new Date(2020, 0, 1), { skeleton: 'yMd' }); 21 | // 1/1/2020 22 | }; 23 | ``` 24 | 25 | ## Options 26 | 27 | - [`date`](#date) 28 | - [`datetime`](#datetime) 29 | - [`skeleton`](#skeleton) 30 | - [`time`](#time) 31 | 32 | **Note**: Specify one of `date`, `datetime`, `skeleton`, or `time`. 33 | 34 | ### `date` 35 | 36 | | Type | Required | Default | Description | 37 | | :----: | :------: | :-----: | :---------: | 38 | | string | No | none | Shorthand date-only formatting specification. Possible values: `full`, `long`, `medium`, `short`. | 39 | 40 | ```js 41 | formatDate(new Date(2020, 0, 1), { date: 'full' }); 42 | // Wednesday, January 1, 2020 43 | ``` 44 | 45 | ### `datetime` 46 | 47 | | Type | Required | Default | Description | 48 | | :----: | :------: | :-----: | :---------: | 49 | | string | No | none | Shorthand date and time formatting specification. Possible values: `full`, `long`, `medium`, `short`. | 50 | 51 | ```js 52 | formatDate(new Date(2020, 0, 1), { datetime: 'full' }); 53 | // Wednesday, January 1, 2020 at 12:00:00 AM GMT-08:00 54 | ``` 55 | 56 | ### `skeleton` 57 | 58 | | Type | Required | Default | Description | 59 | | :----: | :------: | :-----: | :---------: | 60 | | string | No | none | Flexible formatting mechanism using a pattern with fields in canonical order. See [list](http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) of skeleton patterns (not all options supported). | 61 | 62 | ```js 63 | formatDate(new Date(2020, 0, 1), { skeleton: 'yMMMdhm' }); 64 | // Jan 1, 2020, 12:00 AM 65 | ``` 66 | 67 | ### `time` 68 | 69 | | Type | Required | Default | Description | 70 | | :----: | :------: | :-----: | :---------: | 71 | | string | No | none | Shorthand time-only formatting specification. Possible values: `full`, `long`, `medium`, `short`. | 72 | 73 | ```js 74 | formatDate(new Date(2020, 0, 1), { time: 'full' }); 75 | // 12:00:00 AM GMT-08:00 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/api/formatMessage.md: -------------------------------------------------------------------------------- 1 | # `formatMessage` 2 | 3 | Formats a message based on the locale, variables and options. 4 | 5 | `formatMessage(id, values?, options?)` 6 | 7 | Messages must be loaded before they can be used by `formatMessage`. This is done using the [`loadMessages`](utilities.md#loadmessages) utility available as an import from React Native Globalize or from the object returned by `useGlobalize`. 8 | 9 | Messages are formatted using the ICU message format pattern and support powerful logic, such as pluralization and gender inflections. Check out the [advanced examples](#advancedexamples) at the bottom of this page, and if you want more information, take a look at the [ICU message format spec](http://userguide.icu-project.org/formatparse/messages). 10 | 11 | ## Arguments 12 | 13 | - `id` - *string* or *string[]* - Message identifier 14 | - `values` - *object* - Variables for replacement in message 15 | - [`options?`](#options) - *object* - Formatting options 16 | 17 | ## Example 18 | 19 | ```js 20 | import { loadMessages, useGlobalize } from 'react-native-globalize'; 21 | 22 | loadMessages({ 23 | de: { 24 | welcome: 'Hallo, heute ist der {date}', 25 | }, 26 | en: { 27 | welcome: 'Hello, today is {date}', 28 | }, 29 | es: { 30 | welcome: 'Hola, hoy es {date}', 31 | }, 32 | }) 33 | 34 | const ExampleComponent = () => { 35 | const { formatDate, formatMessage } = useGlobalize(); 36 | 37 | formatMessage('welcome', { 38 | date: formatDate(new Date(2020, 0, 1), { date: 'long' }), 39 | // Variable value can also be a string 40 | date: 'awesome', 41 | // Or a React component 42 | date: cool, 43 | }); 44 | // Hello, today is January 1, 2020 45 | // Hello, today is awesome 46 | // Hello, today is cool 47 | }; 48 | ``` 49 | 50 | ## Options 51 | 52 | - [`defaultMessage`](#defaultMessage) 53 | 54 | ### `defaultMessage` 55 | 56 | | Type | Required | Default | Description | 57 | | :----: | :------: | :-----: | :---------: | 58 | | string | No | none | A default string used if a message with the specified `id` has not been loaded (useful when dynamically loading messages). | 59 | 60 | ```js 61 | formatMessage('unknown/key', {}, { defaultMessage: 'Oops' }); 62 | // Oops 63 | ``` 64 | 65 | **Note:** By default, a error is emitted and logged to the console in development mode, so you may still see a React Native error/red screen when developing. However, if you dismiss the error screen, you'll see the `defaultMessage` value used as expected. You can override the default error logging behavior with the `onError` prop on [`GlobalizeProvider`](../components/GlobalizeProvider.md#onerror). 66 | 67 | ## Advanced Examples 68 | 69 | **Gender Inflections**: Use `select` to format message variations similar to a `switch` statement: 70 | 71 | ```js 72 | import { loadMessages, useGlobalize } from 'react-native-globalize'; 73 | 74 | loadMessages({ 75 | // Advanced examples only in english for brevity 76 | en: { 77 | party: { 78 | invite: ` 79 | {gender, select, 80 | female {{host} invites {guest} to her party} 81 | male {{host} invites {guest} to his party} 82 | other {{host} invites {guest} to their party} 83 | } 84 | `, 85 | } 86 | }, 87 | }) 88 | 89 | const ExampleComponent = () => { 90 | const { formatMessage } = useGlobalize(); 91 | 92 | formatMessage('party/invite', { gender: 'male', host: 'Morpheus', guest: 'Neo' }); 93 | // Morpheus invites Neo to his party 94 | formatMessage('party/invite', { gender: 'female', host: 'Trinity', guest: 'Neo' }); 95 | // Trinity invites Neo to her party 96 | formatMessage('party/invite', { gender: '', host: 'Someone', guest: 'Neo' }); 97 | // Someone invites Neo to their party 98 | }; 99 | ``` 100 | 101 | **Plural Inflections**: Use `plural` to format messages based on plural groups: `zero`, `one`, `two`, `few`, `many`, `other` (required). Some languages only use some of these groups (e.g. English uses `one` and `other`). For more information about plural groups, check out [`formatPlural`](formatPlural.md). You can also use literal numberic keys (e.g. `=12` in the example below) to match specific numbers. 102 | 103 | ```js 104 | import { loadMessages, useGlobalize } from 'react-native-globalize'; 105 | 106 | loadMessages({ 107 | // Advanced examples only in english for brevity 108 | en: { 109 | party: { 110 | guests: ` 111 | There {count, plural, 112 | =12 {are a dozen guests} 113 | one {is one guest} 114 | other {are {formattedCount} guests} 115 | } attending the party 116 | `, 117 | } 118 | }, 119 | }) 120 | 121 | const ExampleComponent = () => { 122 | const { formatMessage, formatNumber } = useGlobalize(); 123 | 124 | formatMessage('party/guests', { 125 | count: 1, 126 | formattedCount: formatNumber(1), 127 | }); 128 | // There is one guest attending the party 129 | formatMessage('party/guests', { 130 | count: 1000, 131 | formattedCount: formatNumber(1000), 132 | }); 133 | // There are 1,000 guests attending the party 134 | formatMessage('party/guests', { 135 | count: 12, 136 | formattedCount: formatNumber(12), 137 | }); 138 | // There are a dozen guests attending the party 139 | }; 140 | ``` 141 | 142 | **Plural offset**: Sometimes it's useful to calculate the plural group with an offset applied. You can even use the calculated number in the message using `#`. *Note*: Literal numeric keys (e.g. `=0` and `=1` in the example below) do NOT take offset into account - they match the number literally. 143 | 144 | ```js 145 | import { loadMessages, useGlobalize } from 'react-native-globalize'; 146 | 147 | loadMessages({ 148 | // Advanced examples only in english for brevity 149 | en: { 150 | party: { 151 | register: ` 152 | {count, plural, offset:1 153 | =0 {Be the first to register} 154 | =1 {You have registered} 155 | one {You and someone else have registered} 156 | other {You and # others have registered} 157 | } to attend 158 | `, 159 | } 160 | }, 161 | }) 162 | 163 | const ExampleComponent = () => { 164 | const { formatMessage, formatNumber } = useGlobalize(); 165 | 166 | formatMessage('party/register', { count: 0 }); 167 | // Be the first to register to attend 168 | formatMessage('party/register', { count: 1 }); 169 | // You have registered to attend 170 | formatMessage('party/register', { count: 2 }); 171 | // You and someone else have registered to attend 172 | formatMessage('party/register', { count: 3 }); 173 | // You and 2 others have registered to attend 174 | }; 175 | ``` 176 | -------------------------------------------------------------------------------- /docs/api/formatNumber.md: -------------------------------------------------------------------------------- 1 | # `formatNumber` 2 | 3 | Formats a number based on the locale and options. 4 | 5 | `formatNumber(value, options?)` 6 | 7 | ## Arguments 8 | 9 | - `value` - *number* - Number to be formatted 10 | - [`options?`](#options) - *object* - Formatting options 11 | 12 | ## Example 13 | 14 | ```js 15 | import { useGlobalize } from 'react-native-globalize'; 16 | 17 | const ExampleComponent = () => { 18 | const { formatNumber } = useGlobalize(); 19 | 20 | formatNumber(100000); 21 | // 100,000 22 | }; 23 | ``` 24 | 25 | ## Options 26 | 27 | - [`compact`](#compact) 28 | - [`maximumFractionDigits`](#maximumfractiondigits) 29 | - [`maximumSignificantDigits`](#maximumsignificantdigits) 30 | - [`minimumFractionDigits`](#minimumfractiondigits) 31 | - [`minimumIntegerDigits`](#minimumintegerdigits) 32 | - [`minimumSignificantDigits`](#minimumsignificantdigits) 33 | - [`round`](#round) 34 | - [`style`](#style) 35 | - [`useGrouping`](#usegrouping) 36 | 37 | ### `compact` 38 | 39 | | Type | Required | Default | Description | 40 | | :----: | :------: | :-----: | :---------: | 41 | | string | No | none | Use compact number format. Possible values: `short`, `long`. | 42 | 43 | ```js 44 | formatNumber(100000, { compact: 'short' }); 45 | // 100K 46 | ``` 47 | 48 | ### `maximumFractionDigits` 49 | 50 | | Type | Required | Default | Description | 51 | | :----: | :------: | :-----: | :---------: | 52 | | number | No | none | Override maximum fraction digits. Numbers will be rounded if needed based on [`round`](#round) option. | 53 | 54 | ```js 55 | formatNumber(10.45, { maximumFractionDigits: 0 }); 56 | // 10 57 | ``` 58 | 59 | ### `maximumSignificantDigits` 60 | 61 | | Type | Required | Default | Description | 62 | | :----: | :------: | :-----: | :---------: | 63 | | number | No | none | Override maximum significant (integer + fraction) digits. Numbers will be rounded if needed based on [`round`](#round) option. Must also specify [`minimumSignificantDigits`](#minimumsignificantdigits). | 64 | 65 | ```js 66 | formatNumber(10.45, { minimumSignificantDigits: 1, maximumSignificantDigits: 3 }); 67 | // 10.5 68 | ``` 69 | 70 | ### `minimumFractionDigits` 71 | 72 | | Type | Required | Default | Description | 73 | | :----: | :------: | :-----: | :---------: | 74 | | number | No | none | Override minimum fraction digits. Numbers will be rounded based on [`round`](#round) option or padded if needed. | 75 | 76 | ```js 77 | formatNumber(10.45, { minimumFractionDigits: 4 }); 78 | // 10.4500 79 | ``` 80 | 81 | ### `minimumIntegerDigits` 82 | 83 | | Type | Required | Default | Description | 84 | | :----: | :------: | :-----: | :---------: | 85 | | number | No | none | Override minimum integer digits. Numbers will be padded if needed. | 86 | 87 | ```js 88 | formatNumber(10.45, { minimumIntegerDigits: 3 }); 89 | // 010.45 90 | ``` 91 | 92 | ### `minimumSignificantDigits` 93 | 94 | | Type | Required | Default | Description | 95 | | :----: | :------: | :-----: | :---------: | 96 | | number | No | none | Override minimum significant (integer + fraction) digits. Numbers will be padded if needed. Must also specify [`maximumSignificantDigits`](#maximumsignificantdigits). | 97 | 98 | ```js 99 | formatNumber(10.45, { minimumSignificantDigits: 6, maximumSignificantDigits: 8 }); 100 | // 10.4500 101 | ``` 102 | 103 | ### `round` 104 | 105 | | Type | Required | Default | Description | 106 | | :----: | :------: | :-----: | :---------: | 107 | | string | No | `round` | Specify rounding behavior. Possible values: `ceil`, `floor`, `round`, `truncate`. | 108 | 109 | ```js 110 | formatNumber(10.45, { maximumFractionDigits: 0, round: 'ceil' }); 111 | // 11 112 | ``` 113 | 114 | ### `style` 115 | 116 | | Type | Required | Default | Description | 117 | | :----: | :------: | :-----: | :---------: | 118 | | string | No | `decimal` | Change display style. Possible values: `decimal`, `percent`. | 119 | 120 | ```js 121 | formatNumber(0.45, { style: 'decimal' }); 122 | // 0.45 123 | formatNumber(0.45, { style: 'percent' }); 124 | // 45% 125 | ``` 126 | 127 | ### `useGrouping` 128 | 129 | | Type | Required | Default | Description | 130 | | :----: | :------: | :-----: | :---------: | 131 | | boolean | No | `true` | Whether to use grouping separator. | 132 | 133 | ```js 134 | formatNumber(100000, { useGrouping: false }); 135 | // 100000 136 | ``` 137 | -------------------------------------------------------------------------------- /docs/api/formatPlural.md: -------------------------------------------------------------------------------- 1 | # `formatPlural` 2 | 3 | Returns the corresponding plural group based on the locale and options. 4 | 5 | `formatPlural(value, options?)` 6 | 7 | Possible plural groups are: `zero`, `one`, `two`, `few`, `many`, `other`. However, some languages only use some of these groups (e.g. English uses `one` (1) and `other` (=/= 1) for cardinal numbers and `one` (1st), `two` (2nd), `few` (3rd), and `other` (0,4-9th) for ordinal numbers). 8 | 9 | ## Arguments 10 | 11 | - `value` - *number* - Number to be formatted 12 | - [`options?`](#options) - *object* - Formatting options 13 | 14 | ## Example 15 | 16 | ```js 17 | import { useGlobalize } from 'react-native-globalize'; 18 | 19 | const ExampleComponent = () => { 20 | const { formatPlural } = useGlobalize(); 21 | 22 | formatPlural(2); 23 | // other 24 | }; 25 | ``` 26 | 27 | ## Options 28 | 29 | - [`type`](#type) 30 | 31 | ### `type` 32 | 33 | | Type | Required | Default | Description | 34 | | :----: | :------: | :--------: | :---------: | 35 | | string | No | `cardinal` | Whether to use cardinal (1, 2, 3, 4...) or cardinal (1st, 2nd, 3rd, 4th...) groupings. Possible values: `cardinal`, `ordinal`. | 36 | 37 | ```js 38 | formatPlural(2, { type: 'ordinal' }); 39 | // two 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/api/formatRelativeTime.md: -------------------------------------------------------------------------------- 1 | # `formatRelativeTime` 2 | 3 | Formats a relative time based on the locale and options. 4 | 5 | `formatRelativeTime(value, unit, options?)` 6 | 7 | ## Arguments 8 | 9 | - `value` - *Date* or *number* - Date to be formatted 10 | - `unit` - *string* - Unit of time to use (see below) 11 | - [`options?`](#options) - *object* - Formatting options 12 | 13 | When `value` is a number, unit can be: `second`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, `year`. When `value` is a Date, `auto` is also supported, which will determine the best unit automatically. 14 | 15 | ## Example 16 | 17 | ```js 18 | import { useGlobalize } from 'react-native-globalize'; 19 | 20 | const ExampleComponent = () => { 21 | const { formatRelativeTime } = useGlobalize(); 22 | 23 | formatRelativeTime(new Date(), 'auto'); 24 | // now 25 | 26 | formatRelativeTime(-30, 'minute'); 27 | // 30 minutes ago 28 | }; 29 | ``` 30 | 31 | ## Options 32 | 33 | - [`form`](#form) 34 | 35 | ### `form` 36 | 37 | | Type | Required | Default | Description | 38 | | :----: | :------: | :-----: | :---------: | 39 | | string | No | none | Use alternate display format. Possible values: `short`, `narrow`. | 40 | 41 | ```js 42 | formatRelativeTime(-30, 'minute', { form: 'short' }); 43 | // 30 min. ago 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/api/formatUnit.md: -------------------------------------------------------------------------------- 1 | # `formatUnit` 2 | 3 | Formats a unit based on the locale and options. 4 | 5 | `formatUnit(value, unit, options?)` 6 | 7 | ## Arguments 8 | 9 | - `value` - *number* - Number to be formatted 10 | - `unit` - *string* - Unit to use (see below) 11 | - [`options?`](#options) - *object* - Formatting options 12 | 13 | The `unit` argument can be a unit of time (e.g. `second`, `day`, etc.), a unit of measurement (e.g. `mile`, `meter`, `gigabyte`), or a compound unit (e.g. `mile-per-hour`, `kilowatt-hour`). 14 | 15 | ## Example 16 | 17 | ```js 18 | import { useGlobalize } from 'react-native-globalize'; 19 | 20 | const ExampleComponent = () => { 21 | const { formatUnit } = useGlobalize(); 22 | 23 | formatUnit(50, 'mile-per-hour'); 24 | // 50 miles per hour 25 | 26 | formatUNit(50, 'kilometer-per-hour'); 27 | // 50 kilometers per hour 28 | }; 29 | ``` 30 | 31 | ## Options 32 | 33 | - [`form`](#form) 34 | - [`numberFormatter`](#numberformatter) 35 | 36 | ### `form` 37 | 38 | | Type | Required | Default | Description | 39 | | :----: | :------: | :-----: | :---------: | 40 | | string | No | none | Use alternate display format. Possible values: `short`, `narrow`. | 41 | 42 | ```js 43 | formatUnit(50, 'mile-per-hour', { form: 'short' }); 44 | // 50 mph 45 | ``` 46 | 47 | ### `numberFormatter` 48 | 49 | | Type | Required | Default | Description | 50 | | :------: | :------: | :--------------------: | :---------: | 51 | | function | No | `getNumberFormatter()` | Customize the number formatting function. | 52 | 53 | ```js 54 | const { getNumberFormatter } = useGlobalize(); 55 | 56 | formatUnit(5000, 'gigabyte', { 57 | numberFormatter: getNumberFormatter({ 58 | minimumFractionDigits: 2, 59 | useGrouping: false, 60 | }), 61 | }); 62 | // 5000.00 gigabytes 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/api/parseDate.md: -------------------------------------------------------------------------------- 1 | # `parseDate` 2 | 3 | Parses a date based on the locale and options. 4 | 5 | `parseDate(value, options?)` 6 | 7 | ## Arguments 8 | 9 | - `value` - *string* - Date string to be parsed 10 | - [`options?`](#options) - *object* - Parsing options 11 | 12 | ## Example 13 | 14 | ```js 15 | import { useGlobalize } from 'react-native-globalize'; 16 | 17 | const ExampleComponent = () => { 18 | const { parseDate } = useGlobalize(); 19 | 20 | parseDate('1/1/2020', { skeleton: 'yMd' }); 21 | // Date(2020, 0, 1) 22 | }; 23 | ``` 24 | 25 | ## Options 26 | 27 | - [`date`](#date) 28 | - [`datetime`](#datetime) 29 | - [`skeleton`](#skeleton) 30 | - [`time`](#time) 31 | 32 | **Note**: Specify one of `date`, `datetime`, `skeleton`, or `time`. 33 | 34 | ### `date` 35 | 36 | | Type | Required | Default | Description | 37 | | :----: | :------: | :-----: | :---------: | 38 | | string | No | none | Shorthand date-only parsing specification. Possible values: `full`, `long`, `medium`, `short`. | 39 | 40 | ```js 41 | parseDate('January 1, 2020', { date: 'long' }); 42 | // Date(2020, 0, 1) 43 | ``` 44 | 45 | ### `datetime` 46 | 47 | | Type | Required | Default | Description | 48 | | :----: | :------: | :-----: | :---------: | 49 | | string | No | none | Shorthand date and time parsing specification. Possible values: `full`, `long`, `medium`, `short`. | 50 | 51 | ```js 52 | parseDate('1/1/20, 12:00 AM', { datetime: 'short' }); 53 | // Date(2020, 0, 1) 54 | ``` 55 | 56 | ### `skeleton` 57 | 58 | | Type | Required | Default | Description | 59 | | :----: | :------: | :-----: | :---------: | 60 | | string | No | none | Flexible parsing mechanism using a pattern with fields in canonical order. See [list](http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) of skeleton patterns (not all options supported). | 61 | 62 | ```js 63 | parseDate('Jan 1, 2020, 12:00 AM', { skeleton: 'yMMMdhm' }); 64 | // Date(2020, 0, 1) 65 | ``` 66 | 67 | ### `time` 68 | 69 | | Type | Required | Default | Description | 70 | | :----: | :------: | :-----: | :---------: | 71 | | string | No | none | Shorthand time-only parsing specification. Possible values: `full`, `long`, `medium`, `short`. | 72 | 73 | ```js 74 | parseDate('12:00 AM', { time: 'full' }); 75 | // Date(today @ 00:00:00) 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/api/parseNumber.md: -------------------------------------------------------------------------------- 1 | # `parseNumber` 2 | 3 | Parses a number based on the locale and options. 4 | 5 | `parseNumber(value, options?)` 6 | 7 | ## Arguments 8 | 9 | - `value` - *string* - Number string to be parsed 10 | - [`options?`](#options) - *object* - Parsing options 11 | 12 | ## Example 13 | 14 | ```js 15 | import { useGlobalize } from 'react-native-globalize'; 16 | 17 | const ExampleComponent = () => { 18 | const { parseNumber } = useGlobalize(); 19 | 20 | parseNumber('100,000.00'); 21 | // Number(100000) 22 | }; 23 | ``` 24 | 25 | ## Options 26 | 27 | - [`style`](#style) 28 | 29 | ### `style` 30 | 31 | | Type | Required | Default | Description | 32 | | :----: | :------: | :-----: | :---------: | 33 | | string | No | `decimal` | Change parsing style. Possible values: `decimal`, `percent`. | 34 | 35 | ```js 36 | parseNumber('0.45', { style: 'decimal' }); 37 | // Number(0.45) 38 | formatNumber('45%', { style: 'percent' }); 39 | // Number(0.45) 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/api/useGlobalize.md: -------------------------------------------------------------------------------- 1 | # `useGlobalize` 2 | 3 | `useGlobalize` is a hook which gives access to the Globalize object. You can then destructure any formatters you need within the component. 4 | 5 | ## Example 6 | 7 | ```js 8 | import { useGlobalize } from 'react-native-globalize'; 9 | 10 | const ExampleComponent = () => { 11 | const { formatDate, formatMessage } = useGlobalize(); 12 | 13 | return ( 14 | 15 | {formatMessage('welcome', { 16 | date: formatDate(new Date(), { date: 'long' }), 17 | })} 18 | 19 | ) 20 | }; 21 | ``` 22 | 23 | ## Reference 24 | 25 | The following functions and utilities are available on the Globalize object returned by `useGlobalize`: 26 | 27 | - [`formatCurrency`](formatCurrency.md) 28 | - [`formatDate`](formatDate.md) 29 | - [`formatMessage`](formatMessage.md) 30 | - [`formatNumber`](formatNumber.md) 31 | - [`formatPlural`](formatPlural.md) 32 | - [`formatRelativeTime`](formatRelativeTime.md) 33 | - [`formatUnit`](formatUnit.md) 34 | - [`parseDate`](parseDate.md) 35 | - [`parseNumber`](parseNumber.md) 36 | - [Utilities](utilities.md) 37 | - [`getAvailableLocales`](utilities.md#getavailablelocales) 38 | - [`getCurrencySymbol`](utilities.md#getcurrencysymbol) 39 | - [`loadCldr`](utilities.md#loadcldr) 40 | - [`loadMessages`](utilities.md#loadmessages) 41 | - [`localeIsLoaded`](utilities.md#localeisloaded) 42 | -------------------------------------------------------------------------------- /docs/api/utilities.md: -------------------------------------------------------------------------------- 1 | # Utilities 2 | 3 | Various utility functions for working with locale data and messages. 4 | 5 | - [`getAvailableLocales`](#getavailablelocales) 6 | - [`getCurrencySymbol`](#getcurrencysymbol) 7 | - [`loadCldr`](#loadcldr) 8 | - [`loadMessages`](#loadmessages) 9 | - [`localeIsLoaded`](#localeisloaded) 10 | 11 | ## `getAvailableLocales` 12 | 13 | `getAvailableLocales()` 14 | 15 | Returns an array of locales that have been loaded via [`loadCldr`](#loadcldr). Available as an export from React Native Globalize and on the Globalize object returned by `useGlobalize`. 16 | 17 | ### Example 18 | 19 | ```js 20 | import { getAvailableLocales } from 'react-native-globalize'; 21 | 22 | getAvailableLocales(); 23 | // ['en'] 24 | ``` 25 | 26 | ```js 27 | import { useGlobalize } from 'react-native-globalize'; 28 | 29 | const ExampleComponent = () => { 30 | const { getAvailableLocales } = useGlobalize(); 31 | 32 | getAvailableLocales(); 33 | // ['en'] 34 | }; 35 | ``` 36 | 37 | ## `getCurrencySymbol` 38 | 39 | `getCurrencySymbol(currencyCode, altNarrow?)` 40 | 41 | Returns just the symbol for a particular currency. This can be useful in situations where you don't need to format a number as a currency, but you want to show localized currency codes. 42 | 43 | ### Arguments 44 | 45 | - `currencyCode` - *string* - Currency code to get symbol for 46 | - `altNarrow?` - *boolean* - Retrieve alternative narrow symbol (e.g. $ instead of CA$) 47 | 48 | ### Example 49 | 50 | ```js 51 | import { useGlobalize } from 'react-native-globalize'; 52 | 53 | const ExampleComponent = () => { 54 | const { getCurrencySymbol } = useGlobalize(); 55 | 56 | getCurrencySymbol('EUR'); 57 | // € 58 | }; 59 | ``` 60 | 61 | `getCurrencySymbol` is also available as an export from React Native Globalize. However, it requires an additional `locale` string argument since it has not been bound to a Globalize object with a specific locale set. 62 | 63 | ```js 64 | import { getCurrencySymbol } from 'react-native-globalize'; 65 | 66 | getCurrencySymbol('en', 'EUR'); 67 | // € 68 | ``` 69 | 70 | ## `loadCldr` 71 | 72 | `loadCldr(cldrData)` 73 | 74 | Loads (CLDR) data for one or more locales into Globalize to prepare for formatting. Most apps should call this utility function with the locales they support at the application root. This ensures that locale data is loaded and available when formatting. However, `loadCldr` can be called anywhere in an app, as long as it's called before formatting is attempted for a particular locale. Check out the [Performance](../performace.md) and [Advanced](../advanced.md) guides for some additional info and a recipe for situations where more than a handful of locales are supported. 75 | 76 | ### Arguments 77 | 78 | - `cldrData` - *object* - An object containing CLDR data for one or more locales (e.g. data from the React Native Globalize `locale-data` folder) 79 | 80 | ### Example 81 | 82 | ```js 83 | import { loadCldr } from 'react-native-globalize'; 84 | 85 | loadCldr( 86 | require('react-native-globalize/locale-data/de'), 87 | require('react-native-globalize/locale-data/en'), 88 | require('react-native-globalize/locale-data/es'), 89 | ); 90 | ``` 91 | 92 | ## `loadMessages` 93 | 94 | `loadMessages(messageData)` 95 | 96 | Loads message data for one or more locales into Globalize to prepare for message formatting. The `loadMessages` function can be called once with all messages and all locales, or bit by bit throughout the application. 97 | 98 | ### Arguments 99 | 100 | - `messageData` - *object* - An object containing message data, keyed by locale 101 | 102 | ### Example 103 | 104 | ```js 105 | import { loadMessages } from 'react-native-globalize'; 106 | 107 | loadMessages({ 108 | de: { 109 | welcome: 'Hallo, heute ist der {date}', 110 | }, 111 | en: { 112 | welcome: 'Welcome, today is {date}', 113 | }, 114 | es: { 115 | welcome: 'Hola, hoy es {date}', 116 | }, 117 | }); 118 | ``` 119 | 120 | ## `localeIsLoaded` 121 | 122 | `localeIsLoaded(locale)` 123 | 124 | Returns a boolean indicating whether a particular locale has been loaded via [`loadCldr`](#loadcldr). Available as an export from React Native Globalize and on the Globalize object returned by `useGlobalize`. 125 | 126 | ### Arguments 127 | 128 | - `locale` - *string* - A locale identifier 129 | 130 | ### Example 131 | 132 | ```js 133 | import { localeIsLoaded } from 'react-native-globalize'; 134 | 135 | localeIsLoaded('en'); 136 | // true 137 | ``` 138 | 139 | ```js 140 | import { useGlobalize } from 'react-native-globalize'; 141 | 142 | const ExampleComponent = () => { 143 | const { localeIsLoaded } = useGlobalize(); 144 | 145 | localeIsLoaded('en'); 146 | // true 147 | }; 148 | ``` 149 | -------------------------------------------------------------------------------- /docs/components/FormattedCurrency.md: -------------------------------------------------------------------------------- 1 | # `FormattedCurrency` 2 | 3 | Formats a number as currency based on the locale and options. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import { FormattedCurrency } from 'react-native-globalize'; 9 | 10 | const ExampleComponent = () => ( 11 | 12 | ); 13 | // $1,000.99; 14 | ``` 15 | 16 | ## Props 17 | 18 | - [`accessible`](https://facebook.github.io/react-native/docs/text#accessible) 19 | - [`accessibilityLabel`](https://facebook.github.io/react-native/docs/text#accessibilitylabel) 20 | - [`adjustsFontSizeToFit`](https://facebook.github.io/react-native/docs/text#adjustsfontsizetofit) 21 | - [`allowFontScaling`](https://facebook.github.io/react-native/docs/text#allowfontscaling) 22 | - [`compact`](#compact) 23 | - [`currency`](#currency) 24 | - [`maximumFractionDigits`](#maximumfractiondigits) 25 | - [`maximumSignificantDigits`](#maximumsignificantdigits) 26 | - [`minimumFractionDigits`](#minimumfractiondigits) 27 | - [`minimumIntegerDigits`](#minimumintegerdigits) 28 | - [`minimumSignificantDigits`](#minimumsignificantdigits) 29 | - [`numberStyle`](#numberStyle) 30 | - [`round`](#round) 31 | - [`style`](https://facebook.github.io/react-native/docs/text#style) 32 | - [`symbolForm`](#symbolform) 33 | - [`useGrouping`](#usegrouping) 34 | - [`value`](#value) 35 | 36 | ### `currency` 37 | 38 | | Type | Required | Default | Description | 39 | | :----: | :------: | :-----: | :---------: | 40 | | string | No | GlobalizeProvider, fallback to USD | Provides currency code for formatter. | 41 | 42 | ```js 43 | 47 | // ¥1000.99 48 | 49 | 53 | // $1000.99 54 | 55 | ``` 56 | 57 | ### `compact` 58 | 59 | | Type | Required | Default | Description | 60 | | :----: | :------: | :-----: | :---------: | 61 | | string | No | none | Use compact number format. Possible values: `short`, `long`. | 62 | 63 | ```js 64 | 68 | // $1K 69 | ``` 70 | 71 | ### `maximumFractionDigits` 72 | 73 | | Type | Required | Default | Description | 74 | | :----: | :------: | :-----: | :---------: | 75 | | number | No | none | Override maximum fraction digits. Numbers will be rounded if needed based on [`round`](#round) option. | 76 | 77 | ```js 78 | 82 | // $1,001 83 | ``` 84 | 85 | ### `maximumSignificantDigits` 86 | 87 | | Type | Required | Default | Description | 88 | | :----: | :------: | :-----: | :---------: | 89 | | number | No | none | Override maximum significant (integer + fraction) digits. Numbers will be rounded if needed based on [`round`](#round) option. Must also specify [`minimumSignificantDigits`](#minimumsignificantdigits). | 90 | 91 | ```js 92 | 97 | // $1,001 98 | ``` 99 | 100 | ### `minimumFractionDigits` 101 | 102 | | Type | Required | Default | Description | 103 | | :----: | :------: | :-----: | :---------: | 104 | | number | No | none | Override minimum fraction digits. Numbers will be rounded based on [`round`](#round) option or padded if needed. | 105 | 106 | ```js 107 | 111 | // $1,000.9900 112 | ``` 113 | 114 | ### `minimumIntegerDigits` 115 | 116 | | Type | Required | Default | Description | 117 | | :----: | :------: | :-----: | :---------: | 118 | | number | No | none | Override minimum integer digits. Numbers will be padded if needed. | 119 | 120 | ```js 121 | 125 | // $001,000.99 126 | ``` 127 | 128 | ### `minimumSignificantDigits` 129 | 130 | | Type | Required | Default | Description | 131 | | :----: | :------: | :-----: | :---------: | 132 | | number | No | none | Override minimum significant (integer + fraction) digits. Numbers will be padded if needed. Must also specify [`maximumSignificantDigits`](#maximumsignificantdigits). | 133 | 134 | ```js 135 | 140 | // $1,000.9900 141 | ``` 142 | 143 | ### `numberStyle` 144 | 145 | | Type | Required | Default | Description | 146 | | :----: | :------: | :-----: | :---------: | 147 | | string | No | `symbol` | Change display style. Possible values: `symbol`, `accounting`, `code`, `name`. | 148 | 149 | ```js 150 | 154 | // ($1,000.99) 155 | 159 | // 1,000.99 USD 160 | 164 | // 1,000.99 US dollars 165 | ``` 166 | 167 | ### `round` 168 | 169 | | Type | Required | Default | Description | 170 | | :----: | :------: | :-----: | :---------: | 171 | | string | No | `round` | Specify rounding behavior. Possible values: `ceil`, `floor`, `round`, `truncate`. | 172 | 173 | ```js 174 | 179 | // $1,000 180 | ``` 181 | 182 | ### `symbolForm` 183 | 184 | | Type | Required | Default | Description | 185 | | :----: | :------: | :-----: | :---------: | 186 | | string | No | none | Use alternative narrow currency symbol. Possible values: `narrow`. | 187 | 188 | ```js 189 | 193 | // CA$1,000.99 194 | 199 | // $1,000.99 200 | ``` 201 | 202 | ### `useGrouping` 203 | 204 | | Type | Required | Default | Description | 205 | | :----: | :------: | :-----: | :---------: | 206 | | boolean | No | `true` | Whether to use grouping separator. | 207 | 208 | ```js 209 | 213 | // $1000.99 214 | ``` 215 | 216 | ### `value` 217 | 218 | | Type | Required | Default | Description | 219 | | :----: | :------: | :-----: | :---------: | 220 | | number | Yes | none | Number to be formatted. | 221 | -------------------------------------------------------------------------------- /docs/components/FormattedDate.md: -------------------------------------------------------------------------------- 1 | # `FormattedDate` 2 | 3 | Formats a date based on the locale and options. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import { FormattedDate } from 'react-native-globalize'; 9 | 10 | const ExampleComponent = () => ( 11 | 15 | ); 16 | // 1/1/2020 17 | ``` 18 | 19 | ## Props 20 | 21 | - [`accessible`](https://facebook.github.io/react-native/docs/text#accessible) 22 | - [`accessibilityLabel`](https://facebook.github.io/react-native/docs/text#accessibilitylabel) 23 | - [`adjustsFontSizeToFit`](https://facebook.github.io/react-native/docs/text#adjustsfontsizetofit) 24 | - [`allowFontScaling`](https://facebook.github.io/react-native/docs/text#allowfontscaling) 25 | - [`date`](#date) 26 | - [`datetime`](#datetime) 27 | - [`skeleton`](#skeleton) 28 | - [`style`](https://facebook.github.io/react-native/docs/text#style) 29 | - [`time`](#time) 30 | - [`value`](#value) 31 | 32 | **Note**: Specify one of `date`, `datetime`, `skeleton`, or `time`. 33 | 34 | ### `date` 35 | 36 | | Type | Required | Default | Description | 37 | | :----: | :------: | :-----: | :---------: | 38 | | string | No | none | Shorthand date-only formatting specification. Possible values: `full`, `long`, `medium`, `short`. | 39 | 40 | ```js 41 | 45 | // Wednesday, January 1, 2020 46 | ``` 47 | 48 | ### `datetime` 49 | 50 | | Type | Required | Default | Description | 51 | | :----: | :------: | :-----: | :---------: | 52 | | string | No | none | Shorthand date and time formatting specification. Possible values: `full`, `long`, `medium`, `short`. | 53 | 54 | ```js 55 | 59 | // Wednesday, January 1, 2020 at 12:00:00 AM GMT-08:00 60 | ``` 61 | 62 | ### `skeleton` 63 | 64 | | Type | Required | Default | Description | 65 | | :----: | :------: | :-----: | :---------: | 66 | | string | No | none | Flexible formatting mechanism using a pattern with fields in canonical order. See [list](http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) of skeleton patterns (not all options supported). | 67 | 68 | ```js 69 | 73 | // Jan 1, 2020, 12:00 AM 74 | ``` 75 | 76 | ### `time` 77 | 78 | | Type | Required | Default | Description | 79 | | :----: | :------: | :-----: | :---------: | 80 | | string | No | none | Shorthand time-only formatting specification. Possible values: `full`, `long`, `medium`, `short`. | 81 | 82 | ```js 83 | 87 | // 12:00:00 AM GMT-08:00 88 | ``` 89 | 90 | ### `value` 91 | 92 | | Type | Required | Default | Description | 93 | | :----: | :------: | :-----: | :---------: | 94 | | Date | Yes | none | Date object to be formatted. | 95 | -------------------------------------------------------------------------------- /docs/components/FormattedMessage.md: -------------------------------------------------------------------------------- 1 | # `FormattedMessage` 2 | 3 | Formats a message based on the locale, variables and options. 4 | 5 | Messages must be loaded before they can be used by `FormattedMessage`. This is done using the [`loadMessages`](utilities.md#loadmessages) utility available as an import from React Native Globalize or from the object returned by `useGlobalize`. 6 | 7 | Messages are formatted using the ICU message format pattern and support powerful logic, such as pluralization and gender inflections. Check out the [advanced examples](#advancedexamples) at the bottom of this page, and if you want more information, take a look at the [ICU message format spec](http://userguide.icu-project.org/formatparse/messages). 8 | 9 | ## Usage 10 | 11 | ```js 12 | import { loadMessages, FormattedMessage } from 'react-native-globalize'; 13 | 14 | loadMessages({ 15 | de: { 16 | welcome: 'Hallo, heute ist der {date}', 17 | }, 18 | en: { 19 | welcome: 'Hello, today is {date}', 20 | }, 21 | es: { 22 | welcome: 'Hola, hoy es {date}', 23 | }, 24 | }) 25 | 26 | const ExampleComponent = () => ( 27 | , 31 | // Variable value can also be a string 32 | date: 'awesome', 33 | }} 34 | /> 35 | ); 36 | // Hello, today is January 1, 2020 37 | // Hello, today is awesome 38 | ``` 39 | 40 | ## Props 41 | 42 | - [`accessible`](https://facebook.github.io/react-native/docs/text#accessible) 43 | - [`accessibilityLabel`](https://facebook.github.io/react-native/docs/text#accessibilitylabel) 44 | - [`adjustsFontSizeToFit`](https://facebook.github.io/react-native/docs/text#adjustsfontsizetofit) 45 | - [`allowFontScaling`](https://facebook.github.io/react-native/docs/text#allowfontscaling) 46 | - [`defaultMessage`](#defaultMessage) 47 | - [`id`](#id) 48 | - [`style`](https://facebook.github.io/react-native/docs/text#style) 49 | - [`values`](#values) 50 | 51 | **Note**: All other props are treated as variables and are merged into `values`. However, using `values` is recommended in case props change in the future. For now, `` is equivalent to ``. 52 | 53 | ### `defaultMessage` 54 | 55 | | Type | Required | Default | Description | 56 | | :----: | :------: | :-----: | :---------: | 57 | | string | No | none | A default string used if a message with the specified `id` has not been loaded (useful when dynamically loading messages). | 58 | 59 | ```js 60 | 64 | // Oops 65 | ``` 66 | 67 | **Note:** By default, a error is emitted and logged to the console in development mode, so you may still see a React Native error/red screen when developing. However, if you dismiss the error screen, you'll see the `defaultMessage` value used as expected. You can override the default error logging behavior with the `onError` prop on [`GlobalizeProvider`](../components/GlobalizeProvider.md#onerror). 68 | 69 | ### `id` 70 | 71 | | Type | Required | Default | Description | 72 | | :----: | :------: | :-----: | :---------: | 73 | | string | Yes | none | Message identifier. | 74 | 75 | ### `values` 76 | 77 | | Type | Required | Default | Description | 78 | | :----: | :------: | :-----: | :---------: | 79 | | object | No | `{}` | Variables for replacement in message. | 80 | 81 | ## Advanced Examples 82 | 83 | See more advanced message examples with gender and plural inflections in the [`formatMessage`](../api/formatMessage.md#advancedexamples) docs. 84 | -------------------------------------------------------------------------------- /docs/components/FormattedNumber.md: -------------------------------------------------------------------------------- 1 | # `FormattedNumber` 2 | 3 | Formats a number based on the locale and options. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import { FormattedNumber } from 'react-native-globalize'; 9 | 10 | const ExampleComponent = () => ( 11 | 12 | ); 13 | // 100,000 14 | ``` 15 | 16 | ## Props 17 | 18 | - [`accessible`](https://facebook.github.io/react-native/docs/text#accessible) 19 | - [`accessibilityLabel`](https://facebook.github.io/react-native/docs/text#accessibilitylabel) 20 | - [`adjustsFontSizeToFit`](https://facebook.github.io/react-native/docs/text#adjustsfontsizetofit) 21 | - [`allowFontScaling`](https://facebook.github.io/react-native/docs/text#allowfontscaling) 22 | - [`compact`](#compact) 23 | - [`maximumFractionDigits`](#maximumfractiondigits) 24 | - [`maximumSignificantDigits`](#maximumsignificantdigits) 25 | - [`minimumFractionDigits`](#minimumfractiondigits) 26 | - [`minimumIntegerDigits`](#minimumintegerdigits) 27 | - [`minimumSignificantDigits`](#minimumsignificantdigits) 28 | - [`numberStyle`](#numberstyle) 29 | - [`round`](#round) 30 | - [`style`](https://facebook.github.io/react-native/docs/text#style) 31 | - [`useGrouping`](#usegrouping) 32 | - [`value`](#value) 33 | 34 | ### `compact` 35 | 36 | | Type | Required | Default | Description | 37 | | :----: | :------: | :-----: | :---------: | 38 | | string | No | none | Use compact number format. Possible values: `short`, `long`. | 39 | 40 | ```js 41 | 45 | // 100K 46 | ``` 47 | 48 | ### `maximumFractionDigits` 49 | 50 | | Type | Required | Default | Description | 51 | | :----: | :------: | :-----: | :---------: | 52 | | number | No | none | Override maximum fraction digits. Numbers will be rounded if needed based on [`round`](#round) option. | 53 | 54 | ```js 55 | 59 | // 10 60 | ``` 61 | 62 | ### `maximumSignificantDigits` 63 | 64 | | Type | Required | Default | Description | 65 | | :----: | :------: | :-----: | :---------: | 66 | | number | No | none | Override maximum significant (integer + fraction) digits. Numbers will be rounded if needed based on [`round`](#round) option. Must also specify [`minimumSignificantDigits`](#minimumsignificantdigits). | 67 | 68 | ```js 69 | 74 | // 10.5 75 | ``` 76 | 77 | ### `minimumFractionDigits` 78 | 79 | | Type | Required | Default | Description | 80 | | :----: | :------: | :-----: | :---------: | 81 | | number | No | none | Override minimum fraction digits. Numbers will be rounded based on [`round`](#round) option or padded if needed. | 82 | 83 | ```js 84 | 88 | // 10.4500 89 | ``` 90 | 91 | ### `minimumIntegerDigits` 92 | 93 | | Type | Required | Default | Description | 94 | | :----: | :------: | :-----: | :---------: | 95 | | number | No | none | Override minimum integer digits. Numbers will be padded if needed. | 96 | 97 | ```js 98 | 102 | // 010.45 103 | ``` 104 | 105 | ### `minimumSignificantDigits` 106 | 107 | | Type | Required | Default | Description | 108 | | :----: | :------: | :-----: | :---------: | 109 | | number | No | none | Override minimum significant (integer + fraction) digits. Numbers will be padded if needed. Must also specify [`maximumSignificantDigits`](#maximumsignificantdigits). | 110 | 111 | ```js 112 | 117 | // 10.4500 118 | ``` 119 | 120 | ### `numberStyle` 121 | 122 | | Type | Required | Default | Description | 123 | | :----: | :------: | :-----: | :---------: | 124 | | string | No | `decimal` | Change display style. Possible values: `decimal`, `percent`. | 125 | 126 | ```js 127 | 131 | // 0.45 132 | 136 | // 45% 137 | ``` 138 | 139 | ### `round` 140 | 141 | | Type | Required | Default | Description | 142 | | :----: | :------: | :-----: | :---------: | 143 | | string | No | `round` | Specify rounding behavior. Possible values: `ceil`, `floor`, `round`, `truncate`. | 144 | 145 | ```js 146 | 151 | // 11 152 | ``` 153 | 154 | ### `useGrouping` 155 | 156 | | Type | Required | Default | Description | 157 | | :----: | :------: | :-----: | :---------: | 158 | | boolean | No | `true` | Whether to use grouping separator. | 159 | 160 | ```js 161 | 165 | // 100000 166 | ``` 167 | 168 | ### `value` 169 | 170 | | Type | Required | Default | Description | 171 | | :----: | :------: | :-----: | :---------: | 172 | | number | Yes | none | Number to be formatted. | 173 | -------------------------------------------------------------------------------- /docs/components/FormattedPlural.md: -------------------------------------------------------------------------------- 1 | # `FormattedPlural` 2 | 3 | Returns the corresponding plural group based on the locale and options. 4 | 5 | Possible plural groups are: `zero`, `one`, `two`, `few`, `many`, `other`. However, some languages only use some of these groups (e.g. English uses `one` (1) and `other` (=/= 1) for cardinal numbers and `one` (1st), `two` (2nd), `few` (3rd), and `other` (0,4-9th) for ordinal numbers). 6 | 7 | ## Usage 8 | 9 | ```js 10 | import { FormattedPlural } from 'react-native-globalize'; 11 | 12 | const ExampleComponent = () => ( 13 | Show this for 1} 16 | other={Show this for other} 17 | /> 18 | ); 19 | ``` 20 | 21 | ## Props 22 | 23 | - [`accessible`](https://facebook.github.io/react-native/docs/text#accessible) 24 | - [`accessibilityLabel`](https://facebook.github.io/react-native/docs/text#accessibilitylabel) 25 | - [`adjustsFontSizeToFit`](https://facebook.github.io/react-native/docs/text#adjustsfontsizetofit) 26 | - [`allowFontScaling`](https://facebook.github.io/react-native/docs/text#allowfontscaling) 27 | - [`style`](https://facebook.github.io/react-native/docs/text#style) 28 | - [`type`](#type) 29 | - [`value`](#value) 30 | 31 | **Note**: The `other` prop is *required*. All other groups are optional. All accept a string or a React component. 32 | 33 | ### `type` 34 | 35 | | Type | Required | Default | Description | 36 | | :----: | :------: | :--------: | :---------: | 37 | | string | No | `cardinal` | Whether to use cardinal (1, 2, 3, 4...) or cardinal (1st, 2nd, 3rd, 4th...) groupings. Possible values: `cardinal`, `ordinal`. | 38 | 39 | ```js 40 | Show this for 1} 43 | two={Show this for 2} 44 | other={Show this for other} 45 | /> 46 | // Show this for 2 47 | ``` 48 | 49 | ### `value` 50 | 51 | | Type | Required | Default | Description | 52 | | :----: | :------: | :-----: | :---------: | 53 | | number | Yes | none | Number to use for plural formatting. | 54 | -------------------------------------------------------------------------------- /docs/components/FormattedRelativeTime.md: -------------------------------------------------------------------------------- 1 | # `FormattedRelativeTime` 2 | 3 | Formats a relative time based on the locale and options. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import { FormattedRelativeTime } from 'react-native-globalize'; 9 | 10 | const ExampleComponent = () => ( 11 | 15 | ); 16 | // now 17 | 18 | const ExampleComponent2 = () => ( 19 | 23 | ); 24 | // 30 minutes ago 25 | ``` 26 | 27 | ## Props 28 | 29 | - [`accessible`](https://facebook.github.io/react-native/docs/text#accessible) 30 | - [`accessibilityLabel`](https://facebook.github.io/react-native/docs/text#accessibilitylabel) 31 | - [`adjustsFontSizeToFit`](https://facebook.github.io/react-native/docs/text#adjustsfontsizetofit) 32 | - [`allowFontScaling`](https://facebook.github.io/react-native/docs/text#allowfontscaling) 33 | - [`form`](#form) 34 | - [`style`](https://facebook.github.io/react-native/docs/text#style) 35 | - [`unit`](#unit) 36 | - [`value`](#value) 37 | 38 | ### `form` 39 | 40 | | Type | Required | Default | Description | 41 | | :----: | :------: | :-----: | :---------: | 42 | | string | No | none | Use alternate display format. Possible values: `short`, `narrow`. | 43 | 44 | ```js 45 | 50 | // 30 min. ago 51 | ``` 52 | 53 | ### `unit` 54 | 55 | | Type | Required | Default | Description | 56 | | :-----: | :------: | :-----: | :---------: | 57 | | string | Yes | none | Unit of time to use (see note below). | 58 | 59 | When `value` is a number, unit can be: `second`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, `year`. When `value` is a Date, `auto` is also supported, which will determine the best unit automatically. 60 | 61 | ### `value` 62 | 63 | | Type | Required | Default | Description | 64 | | :-------------: | :------: | :-----: | :---------: | 65 | | Date or number | Yes | none | Date or number to be formatted. | 66 | -------------------------------------------------------------------------------- /docs/components/FormattedUnit.md: -------------------------------------------------------------------------------- 1 | # `FormattedUnit` 2 | 3 | Formats a unit based on the locale and options. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import { FormattedUnit } from 'react-native-globalize'; 9 | 10 | const ExampleComponent = () => ( 11 | 15 | ); 16 | // 50 miles per hour 17 | 18 | const ExampleComponent2 = () => ( 19 | 23 | ); 24 | // 50 kilometers per hour 25 | ``` 26 | 27 | ## Props 28 | 29 | - [`accessible`](https://facebook.github.io/react-native/docs/text#accessible) 30 | - [`accessibilityLabel`](https://facebook.github.io/react-native/docs/text#accessibilitylabel) 31 | - [`adjustsFontSizeToFit`](https://facebook.github.io/react-native/docs/text#adjustsfontsizetofit) 32 | - [`allowFontScaling`](https://facebook.github.io/react-native/docs/text#allowfontscaling) 33 | - [`form`](#form) 34 | - [`numberFormatter`](#numberformatter) 35 | - [`style`](https://facebook.github.io/react-native/docs/text#style) 36 | - [`unit`](#unit) 37 | - [`value`](#value) 38 | 39 | ### `form` 40 | 41 | | Type | Required | Default | Description | 42 | | :----: | :------: | :-----: | :---------: | 43 | | string | No | none | Use alternate display format. Possible values: `short`, `narrow`. | 44 | 45 | ```js 46 | 51 | // 50 mph 52 | ``` 53 | 54 | ### `numberFormatter` 55 | 56 | | Type | Required | Default | Description | 57 | | :------: | :------: | :--------------------: | :---------: | 58 | | function | No | `getNumberFormatter()` | Customize the number formatting function. | 59 | 60 | ```js 61 | const { getNumberFormatter } = useGlobalize(); 62 | 63 | 71 | // 5000.00 gigabytes 72 | ``` 73 | 74 | ### `unit` 75 | 76 | | Type | Required | Default | Description | 77 | | :-----: | :------: | :-----: | :---------: | 78 | | string | Yes | none | Unit to use (see note below). | 79 | 80 | The `unit` argument can be a unit of time (e.g. `second`, `day`, etc.), a unit of measurement (e.g. `mile`, `meter`, `gigabyte`), or a compound unit (e.g. `mile-per-hour`, `kilowatt-hour`). 81 | 82 | ### `value` 83 | 84 | | Type | Required | Default | Description | 85 | | :-----: | :------: | :-----: | :---------: | 86 | | number | Yes | none | Number to be formatted. | 87 | -------------------------------------------------------------------------------- /docs/components/GlobalizeProvider.md: -------------------------------------------------------------------------------- 1 | # `GlobalizeProvider` 2 | 3 | Propagates the Globalize object and formatting functionality throughout the app using React Context. 4 | 5 | The `GlobalizeProvider` component makes it simple to enable formatting in all your other components and to change the locale or default currency on demand. Simply set the `locale` and `currency` props appropriately (ensuring that any required locale and message data is loaded using [`loadCldr`](../api/utilities.md#loadcldr) and [`loadMessages`](../api/utilities.md#loadmessages)) to get started. Then update the props to propagate those changes throughout your app. 6 | 7 | One recommended recipe is to keep `locale` and `currency` in your application's global state (e.g. Redux). Configured correctly, you can then fire an action in response to a user's selection and update the locale used throughout the app. 8 | 9 | To determine the default locale of a user's device, [react-native-localize](https://github.com/react-native-community/react-native-localize) is recommended. 10 | 11 | ## Usage 12 | 13 | ```js 14 | import React from 'react'; 15 | import { GlobalizeProvider } from 'react-native-globalize'; 16 | import App from './App'; 17 | 18 | const Root = () => ( 19 | 20 | 21 | 22 | ); 23 | 24 | export default Root; 25 | ``` 26 | 27 | ## Props 28 | 29 | - [`currency`](#currency) 30 | - [`defaultLocale`](#defaultLocale) 31 | - [`locale`](#locale) 32 | - [`localeFallback`](#localeFallback) 33 | - [`onError`](#onerror) 34 | 35 | ### `currency` 36 | 37 | | Type | Required | Default | Description | 38 | | :----: | :------: | :-----: | :---------: | 39 | | string | No | `USD` | Default currency to use when formatting. | 40 | 41 | ### `defaultLocale` 42 | 43 | | Type | Required | Default | Description | 44 | | :----: | :------: | :-----: | :---------: | 45 | | string | No | none | Default locale to use when specified locale not available. | 46 | 47 | ### `locale` 48 | 49 | | Type | Required | Default | Description | 50 | | :----: | :------: | :-----: | :---------: | 51 | | string | No | `en` | Locale to use for formatting. | 52 | 53 | ### `localeFallback` 54 | 55 | | Type | Required | Default | Description | 56 | | :-----: | :------: | :--------: | :---------: | 57 | | boolean | No | `false` | Whether to fall back to a similar locale if specified locale is not loaded (e.g. `en-GB` -> `en`) | 58 | 59 | ### `onError` 60 | 61 | | Type | Required | Default | Description | 62 | | :------: | :------: | :-------------: | :---------: | 63 | | function | No | `console.error` | Error logging/handling function. Function signature is `onError(message: string, exception?: Error)`. The default handler logs messages to `console.error` in development mode only. | 64 | -------------------------------------------------------------------------------- /docs/components/withGlobalize.md: -------------------------------------------------------------------------------- 1 | # `withGlobalize` 2 | 3 | Higher-order component (HOC) to inject the Globalize object as a prop. 4 | 5 | This component is mainly included to maintain backwards compatibility as the `useGlobalize` hook is now the recommended way to access React Native Globalize's formatting functionality. This component itself uses `useGlobalize` to get access to the Globalize object for injecting. However, while it is not favored compared to the hook, `withGlobalize` is NOT currently deprecated. 6 | 7 | ## Usage 8 | 9 | ```js 10 | import React from 'react'; 11 | import { withGlobalize } from 'react-native-globalize'; 12 | 13 | const ExampleComponent = ({ globalize }) => { 14 | // Prop `globalize` is the same as returned by `useGlobalize` 15 | const { formatCurrency } = globalize; 16 | }; 17 | 18 | export default withGlobalize(ExampleComponent); 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | The goal of React Native Globalize is to provide an out-of-the-box solution for internationalization (i18n) / localization (l10n) in React Native apps. The data for over 350 locales and 150 currencies is bundled with the package so there's no need for polyfills or complicated configurations. React Native Globalize removes the hassle from internationalizing your apps and lets you start formatting currencies, dates, messages, numbers, relative times, and units right away. 4 | 5 | ## Installation 6 | 7 | React Native Globalize requires **React 16.8.0 or later** and **React Native 0.59.0 or later** 8 | 9 | ``` 10 | yarn add react-native-globalize 11 | 12 | # Or using npm 13 | npm i react-native-globalize --save 14 | ``` 15 | 16 | ## Locale Data 17 | 18 | React Native Globalize bundles the locale data for 350 locales, but none of them are loaded by default. This enables you to fully customize React Native Globalize to your usage needs and avoid wasting time loading anything you don't need. 19 | 20 | In order to support the formatting functions for a given locale, simply load the data using the `loadCldr` utility at the root of your app. 21 | 22 | ```js 23 | import { loadCldr } from 'react-native-globalize'; 24 | 25 | // Example: loading German, English, and Spanish 26 | loadCldr( 27 | require('react-native-globalize/locale-data/de'), 28 | require('react-native-globalize/locale-data/en'), 29 | require('react-native-globalize/locale-data/es'), 30 | ); 31 | ``` 32 | 33 | ## `GlobalizeProvider` 34 | 35 | React Native Globalize provides the `GlobalizeProvider` component, which makes the formatting functionality available throughout your app. 36 | 37 | ```js 38 | import React from 'react'; 39 | import { GlobalizeProvider } from 'react-native-globalize'; 40 | import App from './App'; 41 | 42 | const Root = () => ( 43 | 44 | 45 | 46 | ); 47 | 48 | export default Root; 49 | ``` 50 | 51 | Updating the locale or currency props on `GlobalizeProvider` will propagate those changes throughout your app. Read more in the [`GlobalizeProvider` docs](components/GlobalizeProvider.md). 52 | 53 | ## Usage 54 | 55 | Now that you've got React Native Globalize configured, you're ready to use the various formatting functions throughout your app. Below are a couple of examples using both the `useGlobalize` hook and the `Formatted` components. However, there's much more power at your disposal so please take some time and explore the various functions and components included in React Native Globalize. 56 | 57 | ### `useGlobalize` Hook 58 | 59 | The `useGlobalize` hook injects the Globalize object propagated from the `GlobalizeProvider` into your component. Since it's just an object with functions, you can simply destructure the formatters you need in that particular component. 60 | 61 | ```js 62 | import React from 'react'; 63 | import { Text, View } from 'react-native'; 64 | import { loadMessages, useGlobalize } from 'react-native-globalize'; 65 | 66 | // You can load messages in your components or load 67 | // them all at once at the root of your app 68 | loadMessages({ 69 | de: { 70 | welcome: 'Hallo, heute ist der {date}', 71 | }, 72 | en: { 73 | welcome: 'Hello, today is {date}', 74 | }, 75 | es: { 76 | welcome: 'Hola, hoy es {date}', 77 | }, 78 | }); 79 | 80 | const ExampleComponent = () => { 81 | const { formatDate, formatMessage } = useGlobalize(); 82 | 83 | return ( 84 | 85 | 86 | {formatMessage('welcome', { 87 | date: formatDate(new Date(), { date: 'long' }), 88 | })} 89 | 90 | 91 | ); 92 | }; 93 | ``` 94 | 95 | **Note:** For more information about messages, see [`formatMessage`](api/formatMessage.md) and [`loadMessages`](api/utilities.md#loadmessages). 96 | 97 | ### `Formatted` Components 98 | 99 | To make React Native Globalize as flexible as possible, `Formatted` components are also available for all the different formatters. These work the same way as the formatting functions and accept the same options as props. In fact, they use the `useGlobalize` hook and formatting functions internally. No matter what you use, you'll get the same results. 100 | 101 | ```js 102 | import React from 'react'; 103 | import { Text, View } from 'react-native'; 104 | import { loadMessages, FormattedDate, FormattedMessage } from 'react-native-globalize'; 105 | 106 | // You can load messages in your components or load 107 | // them all at once at the root of your app 108 | loadMessages({ 109 | de: { 110 | welcome: 'Hallo, heute ist der {date}', 111 | }, 112 | en: { 113 | welcome: 'Welcome, today is {date}', 114 | }, 115 | es: { 116 | welcome: 'Hola, hoy es {date}', 117 | }, 118 | }); 119 | 120 | const ExampleComponent = () => { 121 | return ( 122 | 123 | , 127 | }} 128 | /> 129 | 130 | ); 131 | }; 132 | ``` 133 | 134 | Now you know the basics for using React Native Globalize. Check out the rest of docs for details on each function and component. If you want to keep reading, check out the details on [performance](performance.md) or learn how to use formatting functions outside the component hierarchy [using refs](ref-usage.md). 135 | -------------------------------------------------------------------------------- /docs/migration-v4.md: -------------------------------------------------------------------------------- 1 | # Migration Guide (v4) 2 | 3 | React Native Globalize v4 has been in the making for a while, and now that it's out, here are just a few things you need to do in order to upgrade your existing React Native Globalize installation. Thanks in advance for your patience in dealing with the handful of breaking changes! 4 | 5 | ## Intro 6 | 7 | Before diving into the changes for apps using v3, let's quickly discuss the improvements that v4 brings: 8 | 9 | - **Hooks support**: The new `useGlobalize` hook provides easy access to all the formatting functions within any component - and all the `Formatted` components have been rewritten to use the new hook 10 | - **Typescript types**: React Native Globalize has been rewritten in TypeScript, which provides a better development experience and generates perfect up-to-date types that are bundled with the package 11 | - **Locale data**: CLDR data for over 350 locales and over 150 currencies is now bundled with the package, allowing app developers to load the locales they need, and only the locales they need 12 | - **Simplified API**: In conjunction with the new `useGlobalize` hook, new simpler formatting functions (e.g. `formatDate`) are included on the Globalize object to let you jump right into formatting without needing to create formatters with the `getFormatter` functions (which are still included of course) 13 | 14 | ## Requirements 15 | 16 | Since v4 is built around hooks, **React 16.8.0** and **React Native 0.59.0** are now the minimum supported versions! 17 | 18 | ## Breaking Changes 19 | 20 | ### Locale Data 21 | 22 | The `cldr.json` file that contained 54 locales and a few currencies has been removed entirely so **no locale data is loaded by default**. Instead, you'll need to load the locales you need from the `locale-data` folder. These files also contain over 150 currencies, so you should be covered. Thankfully, there should no longer be a need to fork and generate CLDR files in order to customize anymore! 23 | 24 | To migrate, call `loadCldr` at the root of your app with the relevent locales. For example: 25 | 26 | ```js 27 | import { loadCldr, loadMessages, GlobalizeProvider } from 'react-native-globalize'; 28 | import App from './App'; 29 | import messages from './messages'; 30 | 31 | loadCldr( 32 | // Load the locales you actually need 33 | require('react-native-globalize/locale-data/de'), 34 | require('react-native-globalize/locale-data/en'), 35 | require('react-native-globalize/locale-data/es'), 36 | ); 37 | 38 | loadMessages(messages); 39 | 40 | const Root = () => ( 41 | 42 | 43 | 44 | ); 45 | ``` 46 | 47 | ### `FormattedProvider` 48 | 49 | The `FormattedProvider` component has been renamed `GlobalizeProvider`, but it's still exported under the old name as well so you don't need to change. However, the component no longer accepts the `cldr` and `messages` props as they were unreliable with regards to updates of their values. You should use the `loadCldr` and `loadMessages` functions directly if you were using either of these props (see the previous example). 50 | 51 | ### `Globalize` 52 | 53 | To enable the new simpler API, Globalize is no longer a class. It is instead an object with all the formatting functions, loaders, etc. still included. This shouldn't cause issues for most apps unless you were doing something more advanced with the Globalize class itself. Creating an instance of Globalize is now done using the `createGlobalize(options)` function. 54 | 55 | For an example that shows why this change was made, check out how simple formatting a date is now using the `useGlobalize` hook: 56 | 57 | ```js 58 | import React from 'react'; 59 | import { useGlobalize } from 'react-native-globalize'; 60 | 61 | const ExampleComponent = () => { 62 | const { formatDate } = useGlobalize(); 63 | 64 | return ( 65 | 66 | {formatDate(new Date())} 67 | 68 | ); 69 | }; 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/performance.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | 3 | React Native Globalize is designed with performance in mind. Intelligent defaults and careful optimizations generally make it you can focus on your app instead of on performance optimization. However, there are a few key concepts to understand so that you don't run into issues while using React Native Globalize. 4 | 5 | ## Loading Locale Data 6 | 7 | As mentioned in the [Getting Started](getting-started.md) guide, no locale data (also known as CLDR data) is loaded by React Native Globalize by default. Selecting locales and loading the data from the `locale-data` folder is left entirely up to you. This is great because you can load as much or as little as you need for your particular use case, without wasting time on processing data for locales you're never going to use. 8 | 9 | However, there is one important lesson regarding locale data: ***only load what you need***. 10 | 11 | When you invoke `loadCldr` with locale data, the data must be unpacked, parsed, and stored in memory by the library. Thus, there is a small additional cost (both in terms of startup time and memory usage) for each locale that is loaded. In general, this is completely negligible for most use cases, but definitely something to be aware of before you try to load all 350 locales just in case. 12 | 13 | If you need to support a large number of different locales in your app, check out the [advanced](advanced.md) guide for a strategy that keeps loading to a minimum on startup. 14 | 15 | ## Formatting Functions 16 | 17 | This is another area where sensible defaults will cover you in almost every case. However, knowing more about the costs associated with building the formatters can help you avoid getting into trouble in more complex situations. In order to create a formatting function, the locale data for that particular type of value must be combined with the data regarding the various options specified. Then a function is created such that a value can be formatted according to those rules. 18 | 19 | This leads to the important lesson for this section: ***creating a formatting function takes MUCH longer than formatting a value with that function***. 20 | 21 | To avoid slowing apps down to a crawl, React Native Globalizes memoizes all the formatter creator functions. Therefore, calling `formatDate()`, for example, causes a new formatting function to be created, which takes a bit of time. However, if you call `formatDate()` again with the same options, React Native Globalize uses the previously built formatter and outputs the result in a flash. 22 | 23 | As a result, you really shouldn't need to worry about your usage of the formatting functions. But it could come in handy to know that each time you change the options, even slightly, a new function must be constructed, which could make your component slow if you're using a lot of different formatters with different options. 24 | 25 | If you need peak performance in a particular component that uses particular formatters repeatedly, check out the [advanced](advanced.md) guide to learn how to get direct access to a formatter function. 26 | -------------------------------------------------------------------------------- /docs/ref-usage.md: -------------------------------------------------------------------------------- 1 | # Accessing Globalize via ref 2 | 3 | Occasionally, you may need to access the formatting functions available via `useGlobalize` outside of your React components. Generally, you should avoid this as much as possible, but sometimes it's necessary (e.g. displaying an alert message directly from a saga with the appropriate translations applied). 4 | 5 | For such instances, you can attach a ref to the `GlobalizeProvider` in order to access the current Globalize instance and its formatting functions. **Do not** overuse this approach as it does not handle locale or currency changes at all (that functionality requires the React Context). That said, it's great for quick messages that are only visible for a period of time or for situations where the locale cannot be changed. 6 | 7 | To get started, we'll create a `Globalize` module that contains the ref and the functions we want to use: 8 | 9 | ```javascript 10 | // Globalize.js 11 | import React from 'react'; 12 | 13 | export const globalizeRef = React.createRef(); 14 | 15 | export function formatCurrency(value, currencyCode, options) { 16 | return globalizeRef.current?.formatCurrency(value, currencyCode, options); 17 | } 18 | 19 | // ...other formatting functions 20 | ``` 21 | 22 | Then, we'll attach that ref to the `GlobalizeProvider` at the root of our app: 23 | 24 | ```javascript 25 | // Root.js 26 | import { GlobalizeProvider } from 'react-native-globalize'; 27 | import { globalizeRef } from './Globalize'; 28 | 29 | const Root = () => ( 30 | 31 | 32 | 33 | ); 34 | 35 | export default Root; 36 | ``` 37 | 38 | And finally, we can access the formatting functions on any module throughout the app: 39 | 40 | ```javascript 41 | import * as Globalize from './path/to/Globalize'; 42 | 43 | // ... 44 | 45 | Globalize.formatCurrency(75); 46 | ``` 47 | 48 | One final note: the ref is only set once `GlobalizeProvider` actually renders. So don't expect to do any formatting during the initial moments when the application is still booting/loading. But once the provider has rendered, you'll have access to formatting wherever you need it. 49 | -------------------------------------------------------------------------------- /docs/web-usage.md: -------------------------------------------------------------------------------- 1 | # Web Usage 2 | 3 | React Native Globalize can be used on the web just like on mobile using [react-native-web](https://github.com/necolas/react-native-web). The following guide will illustrate how to set up a website with React Native Globalize using create-react-app. 4 | 5 | In general, React Native Globalize is compatible out-of-the-box with an RN website using react-native-web. One change needs to be made to the Webpack configuration to ensure components load correctly. If you already have a website using react-native-web, skip straight to Step 2. 6 | 7 | ## 1. Basic Setup 8 | 9 | Create a fresh project using create-react-app: 10 | 11 | ```shell 12 | create-react-app my-app 13 | cd my-app 14 | ``` 15 | 16 | Add React Native Globalize and other dependencies: 17 | 18 | ```shell 19 | yarn add react-native-globalize react-native-web 20 | 21 | yarn add --dev customize-cra react-app-rewired 22 | ``` 23 | 24 | We'll use [customize-cra](https://github.com/arackaf/customize-cra) and [react-app-rewired](https://github.com/timarney/react-app-rewired) to customize the default configuration used by CRA. To do so, let's change the scripts in `package.json`: 25 | 26 | ```diff 27 | "scripts": { 28 | - "start": "react-scripts start", 29 | + "start": "react-app-rewired start", 30 | - "build": "react-scripts build", 31 | + "build": "react-app-rewired build", 32 | - "test": "react-scripts test", 33 | + "test": "react-app-rewired test", 34 | "eject": "react-scripts eject" 35 | } 36 | ``` 37 | 38 | ## 2. Webpack Config 39 | 40 | To customize CRA, create a `config-overrides.js` in the project root. 41 | 42 | ```js 43 | const { override, addWebpackModuleRule } = require('customize-cra'); 44 | 45 | module.exports = override( 46 | addWebpackModuleRule({ 47 | test: /globalize/, 48 | parser: { amd: false }, 49 | }), 50 | ); 51 | ``` 52 | 53 | If you aren't using CRA, simply place the rule above in the `module.rules` section of your Webpack config: 54 | 55 | ```js 56 | { 57 | module: { 58 | rules: [ 59 | { test: /globalize/, parser: { amd: false } } 60 | ] 61 | } 62 | } 63 | ``` 64 | 65 | ## 3. Globalize 66 | 67 | At this point, React Native Globalize is ready to use on the web! Follow the steps in the [Getting Started](getting-started.md) to set up React Native Globalize and start formatting. 68 | 69 | For web usage, it is recommended to load locale (CLDR) data only as needed rather than bundling all possible locales into your web bundle. This keeps the bundle smaller and speeds up the initial load of your site. Check out the section on loading locale data on-demand in the [Advanced](advanced.md) guide. In general, using `import()` to dynamically load the files from the `locale-data` folder is recommended: 70 | 71 | ```js 72 | const localeLoaders = { 73 | de: () => import('react-native-globalize/locale-data/de'), 74 | en: () => import('react-native-globalize/locale-data/en'), 75 | es: () => import('react-native-globalize/locale-data/es'), 76 | }; 77 | 78 | const onChangeLocale = async (locale) => { 79 | // Load locale data dynamically 80 | const data = await localeLoaders[locale](); 81 | 82 | // Once downloaded, load data into Globalize 83 | loadCldr(data); 84 | 85 | // Update locale on using 86 | // state, redux action, etc. 87 | switchLocale(locale); 88 | }; 89 | ``` 90 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | const del = require('del'); 12 | const { dest, parallel, series, src } = require('gulp'); 13 | const merge = require('gulp-merge-json'); 14 | const jsonpack = require('jsonpack/main'); 15 | const through = require('through2'); 16 | const stream = require('merge-stream'); 17 | const { reduceLocale, reduceSupplemental } = require('./utils/cldr'); 18 | 19 | const DIR = 'locale-data'; 20 | 21 | const compress = () => 22 | through.obj((file, _, cb) => { 23 | if (file.isBuffer()) { 24 | const content = file.contents.toString('utf8'); 25 | // eslint-disable-next-line no-param-reassign 26 | file.contents = Buffer.from( 27 | `module.exports=require('jsonpack').unpack('${jsonpack 28 | .pack(content) 29 | .replace(/'/g, "\\'")}')`, 30 | ); 31 | } 32 | 33 | cb(null, file); 34 | }); 35 | 36 | function clean() { 37 | return del([`${DIR}/**/*`]); 38 | } 39 | 40 | function supplemental() { 41 | const files = [ 42 | 'currencyData', 43 | 'likelySubtags', 44 | 'numberingSystems', 45 | 'ordinals', 46 | 'plurals', 47 | 'timeData', 48 | 'weekData', 49 | ]; 50 | 51 | return src(files.map((file) => `node_modules/cldr-data/supplemental/${file}.json`)) 52 | .pipe( 53 | merge({ 54 | fileName: 'core.js', 55 | transform: reduceSupplemental, 56 | }), 57 | ) 58 | .pipe(compress()) 59 | .pipe(dest(DIR)); 60 | } 61 | 62 | function locales() { 63 | const files = ['ca-gregorian', 'currencies', 'dateFields', 'numbers', 'timeZoneNames', 'units']; 64 | const dir = 'node_modules/cldr-data/main/'; 65 | const ignore = ['en-US-POSIX', 'root']; 66 | const codes = fs 67 | .readdirSync(dir) 68 | .filter((file) => fs.statSync(path.join(dir, file)).isDirectory() && !ignore.includes(file)); 69 | 70 | return stream( 71 | codes.map((locale) => 72 | src(files.map((file) => `node_modules/cldr-data/main/${locale}/${file}.json`)) 73 | .pipe( 74 | merge({ 75 | fileName: `${locale}.js`, 76 | transform: reduceLocale, 77 | jsonSpace: '', 78 | }), 79 | ) 80 | .pipe(compress()) 81 | .pipe(dest(DIR)), 82 | ), 83 | ); 84 | } 85 | 86 | exports.default = series(clean, parallel(supplemental, locales)); 87 | -------------------------------------------------------------------------------- /locale-data/zh-Hans-MO.js: -------------------------------------------------------------------------------- 1 | module.exports=require('jsonpack').unpack('main|zh-Hans-MO|dates|calendars|gregorian|months|format|abbreviated|1|1月|2|2月|3|3月|4|4月|5|5月|6|6月|7|7月|8|8月|9|9月|10|10月|11|11月|12|12月|narrow|wide|一月|二月|三月|四月|五月|六月|七月|八月|九月|十月|十一月|十二月|stand-alone|days|sun|周日|mon|周一|tue|周二|wed|周三|thu|周四|fri|周五|sat|周六|日|一|二|三|四|五|六|short|星期日|星期一|星期二|星期三|星期四|星期五|星期六|quarters|1季度|2季度|3季度|4季度|第一季度|第二季度|第三季度|第四季度|dayPeriods|midnight|午夜|am|上午|pm|下午|morning1|早上|morning2|afternoon1|中午|afternoon2|evening1|晚上|night1|凌晨|清晨|eras|eraNames|0|公元前|公元|0-alt-variant|BCE|1-alt-variant|CE|eraAbbr|eraNarrow|dateFormats|full|y年M月d日EEEE|long|y年M月d日|medium|d/M/yy|timeFormats|zzzz+ah:mm:ss|z+ah:mm:ss|ah:mm:ss|ah:mm|dateTimeFormats|{1}+{0}|{1}+{0}|{1}+{0}|{1}+{0}|availableFormats|Bh|Bh时|Bhm|Bh:mm|Bhms|Bh:mm:ss|d|d日|E|ccc|EBhm|EBh:mm|EBhms|EBh:mm:ss|Ed|d日E|Ehm|Eah:mm|EHm|EHH:mm|Ehms|Eah:mm:ss|EHms|EHH:mm:ss|Gy|Gy年|GyMMM|Gy年M月|GyMMMd|Gy年M月d日|GyMMMEd|Gy年M月d日E|h|ah时|H|H时|hm|Hm|HH:mm|hms|Hms|HH:mm:ss|hmsv|v+ah:mm:ss|Hmsv|v+HH:mm:ss|hmv|v+ah:mm|Hmv|v+HH:mm|M|M月|Md|d/M|MEd|E,+d/M|MMdd|dd/MM|MMM|LLL|MMMd|M月d日|MMMEd|M月d日E|MMMMd|MMMMdd|MMMMW-count-other|MMMM第W周|ms|mm:ss|y|y年|yM|y年M月|yMd|yMEd|y年M月d日,E|yMM|yMMM|yMMMd|yMMMEd|y年M月d日E|yMMMM|yQQQ|y年第Q季度|yQQQQ|yw-count-other|Y年第w周|appendItems|Day|{0}+({2}:+{1})|Day-Of-Week|{0}+{1}|Era|{1}+{0}|Hour|{0}+({2}:+{1})|Minute|{0}+({2}:+{1})|Month|{0}+({2}:+{1})|Quarter|{0}+({2}:+{1})|Second|{0}+({2}:+{1})|Timezone|{1}{0}|Week|{0}+({2}:+{1})|Year|{1}+{0}|intervalFormats|intervalFormatFallback|{0}–{1}|B|Bh时至Bh时|Bh时至h时|Bh:mm至Bh:mm|Bh:mm至h:mm|m|d日至d日|G|G+y+–+G+y|G+y–y|GyM|GGGGG+y-MM+–+GGGGG+y-MM|GGGGG+y-MM+–+y-MM|GGGGG+y-MM+–+y-MM|GyMd|GGGGG+y-MM-dd+–+y-MM-dd|GGGGG+y-MM-dd+–+GGGGG+y-MM-dd|GGGGG+y-MM-dd+–+y-MM-dd|GGGGG+y-MM-dd+–+y-MM-dd|GyMEd|GGGGG+y-MM-dd,+E+–+y-MM-dd,+E|GGGGG+y-MM-dd,+E+–+GGGGG+y-MM-dd,+E|GGGGG+y-MM-dd,+E+–+y-MM-dd,+E|GGGGG+y-MM-dd,+E+–+y-MM-dd,+E|G+y+MMM+–+G+y+MMM|G+y+MMM–MMM|G+y+MMM+–+y+MMM|G+y+MMM+d–d|G+y+MMM+d+–+G+y+MMM+d|G+y+MMM+d+–+MMM+d|G+y+MMM+d+–+y+MMM+d|G+y+MMM+d,+E+–+MMM+d,+E|G+y+MMM+d,+E+–+G+y+MMM+d,+E|G+y+MMM+d,+E+–+MMM+d,+E|G+y+MMM+d,+E+–+y+MMM+d,+E|a|ah时至ah时|ah时至h时|HH–HH|ah:mm至ah:mm|ah:mm至h:mm|HH:mm–HH:mm|vah:mm至ah:mm|vah:mm至h:mm|vHH:mm–HH:mm|hv|vah时至ah时|vah时至h时|Hv|vHH–HH|M–M月|M-d至M-d|M-dE至M-dE|MMM+–+MMM|M月d日至d日|M月d日至M月d日|M月d日E至M月d日E|y–y年|y年M月至y年M月|d/M/y至d/M/y|d/M/yE至d/M/yE|y年M月至M月|y年M月d日至d日|y年M月d日至M月d日|y年M月d日至y年M月d日|y年M月d日E至M月d日E|y年M月d日E至y年M月d日E|fields|era|displayName|纪元|era-short|era-narrow|year|年|relative-type--1|去年|relative-type-0|今年|relative-type-1|明年|relativeTime-type-future|relativeTimePattern-count-other|{0}年后|relativeTime-type-past|{0}年前|year-short|year-narrow|quarter|季度|上季度|本季度|下季度|{0}个季度后|{0}个季度前|quarter-short|季|quarter-narrow|month|月|上个月|本月|下个月|{0}个月后|{0}个月前|month-short|month-narrow|week|周|上周|本周|下周|{0}周后|{0}周前|relativePeriod|{0}这周|week-short|week-narrow|weekOfMonth|月中周|weekOfMonth-short|weekOfMonth-narrow|day|relative-type--2|前天|昨天|今天|明天|relative-type-2|后天|{0}天后|{0}天前|day-short|day-narrow|dayOfYear|年中日|dayOfYear-short|dayOfYear-narrow|weekday|工作日|weekday-short|weekday-narrow|weekdayOfMonth|月中日|weekdayOfMonth-short|weekdayOfMonth-narrow|上周日|本周日|下周日|{0}个周日后|{0}个周日前|sun-short|sun-narrow|上周一|本周一|下周一|{0}个周一后|{0}个周一前|mon-short|mon-narrow|上周二|本周二|下周二|{0}个周二后|{0}个周二前|tue-short|tue-narrow|上周三|本周三|下周三|{0}个周三后|{0}个周三前|wed-short|wed-narrow|上周四|本周四|下周四|{0}个周四后|{0}个周四前|thu-short|thu-narrow|上周五|本周五|下周五|{0}个周五后|{0}个周五前|fri-short|fri-narrow|上周六|本周六|下周六|{0}个周六后|{0}个周六前|sat-short|sat-narrow|dayperiod-short|上午/下午|dayperiod|dayperiod-narrow|hour|小时|这一时间+/+此时|{0}小时后|{0}小时前|hour-short|这一时间+/+此时|hour-narrow|这一时间+/+此时|minute|分钟|此刻|{0}分钟后|{0}分钟前|minute-short|分|minute-narrow|second|秒|现在|{0}秒后|{0}秒前|second-short|second-narrow|zone|时区|zone-short|zone-narrow|timeZoneNames|hourFormat|%2BHH:mm;-HH:mm|gmtFormat|GMT{0}|gmtZeroFormat|GMT|regionFormat|{0}时间|regionFormat-type-daylight|{0}夏令时间|regionFormat-type-standard|{0}标准时间|fallbackFormat|{1}({0})|numbers|defaultNumberingSystem|latn|minimumGroupingDigits|symbols-numberSystem-latn|decimal|.|group|,|list|;|percentSign|%25|plusSign|%2B|minusSign|-|exponential|superscriptingExponent|×|perMille|‰|infinity|∞|nan|NaN|timeSeparator|:|currencyFormats-numberSystem-latn|currencySpacing|beforeCurrency|currencyMatch|[:%5ES:]|surroundingMatch|[:digit:]|insertBetween| |afterCurrency|[:%5ES:]|standard|¤#,##0.00|accounting|¤#,##0.00;(¤#,##0.00)|1000-count-other|10000-count-other|¤0万|100000-count-other|¤00万|1000000-count-other|¤000万|10000000-count-other|¤0000万|100000000-count-other|¤0亿|1000000000-count-other|¤00亿|10000000000-count-other|¤000亿|100000000000-count-other|¤0000亿|1000000000000-count-other|¤0万亿|10000000000000-count-other|¤00万亿|100000000000000-count-other|¤000万亿|unitPattern-count-other|{0}+{1}|decimalFormats-numberSystem-latn|#,##0.###|decimalFormat|0万|00万|000万|0000万|0亿|00亿|000亿|0000亿|0万亿|00万亿|000万亿|percentFormats-numberSystem-latn|#,##0%25|currencies|AED|displayName-count-other|阿联酋迪拉姆|symbol|AFN|阿富汗尼|ALL|阿尔巴尼亚列克|AMD|亚美尼亚德拉姆|AOA|安哥拉宽扎|symbol-alt-narrow|Kz|ARS|阿根廷比索|$|AUD|澳大利亚元|AU$|AWG|阿鲁巴弗罗林|AZN|阿塞拜疆马纳特|BAM|波斯尼亚-黑塞哥维那可兑换马克|KM|BBD|巴巴多斯元|BDT|孟加拉塔卡|৳|BGN|保加利亚新列弗|BHD|巴林第纳尔|BIF|布隆迪法郎|BMD|百慕大元|BND|文莱元|BOB|玻利维亚诺|Bs|BRL|巴西雷亚尔|R$|BSD|巴哈马元|BTN|不丹努尔特鲁姆|BUK|BWP|博茨瓦纳普拉|P|BYN|白俄罗斯卢布|р.|BZD|伯利兹元|CAD|加拿大元|CA$|CDF|刚果法郎|CHF|瑞士法郎|CLP|智利比索|CNY|人民币|CN¥|¥|COP|哥伦比亚比索|CRC|哥斯达黎加科朗|₡|CUP|古巴比索|CVE|佛得角埃斯库多|CZK|捷克克朗|Kč|DJF|吉布提法郎|DKK|丹麦克朗|kr|DOP|多米尼加比索|DZD|阿尔及利亚第纳尔|EGP|埃及镑|E£|ERN|厄立特里亚纳克法|ETB|埃塞俄比亚比尔|EUR|欧元|€|FJD|斐济元|GBP|英镑|£|GEL|格鲁吉亚拉里|₾|symbol-alt-variant|GHS|加纳塞地|GMD|冈比亚达拉西|GNF|几内亚法郎|FG|GTQ|危地马拉格查尔|Q|GYD|圭亚那元|HKD|港元|HK$|HNL|洪都拉斯伦皮拉|L|HRK|克罗地亚库纳|kn|HTG|海地古德|HUF|匈牙利福林|Ft|IDR|印度尼西亚盾|Rp|ILS|以色列新谢克尔|₪|INR|印度卢比|₹|IQD|伊拉克第纳尔|IRR|伊朗里亚尔|ISK|冰岛克朗|JMD|牙买加元|JOD|约旦第纳尔|JPY|日元|JP¥|KES|肯尼亚先令|KGS|吉尔吉斯斯坦索姆|KHR|柬埔寨瑞尔|៛|KMF|科摩罗法郎|CF|KRW|韩元|₩|₩|KWD|科威特第纳尔|KYD|开曼元|KZT|哈萨克斯坦坚戈|₸|LAK|老挝基普|₭|LBP|黎巴嫩镑|L£|LKR|斯里兰卡卢比|Rs|LRD|利比里亚元|LSL|莱索托洛蒂|LVL|拉脱维亚拉特|Ls|LYD|利比亚第纳尔|MAD|摩洛哥迪拉姆|MDL|摩尔多瓦列伊|MGA|马达加斯加阿里亚里|Ar|MKD|马其顿第纳尔|MMK|缅甸元|K|MNT|蒙古图格里克|₮|MOP|澳门元|MOP$|MRU|毛里塔尼亚乌吉亚|MUR|毛里求斯卢比|MVR|马尔代夫卢菲亚|MWK|马拉维克瓦查|MXN|墨西哥比索|MX$|MYR|马来西亚林吉特|RM|MZN|莫桑比克美提卡|NAD|纳米比亚元|NGN|尼日利亚奈拉|₦|NIO|尼加拉瓜科多巴|C$|NOK|挪威克朗|NPR|尼泊尔卢比|NZD|新西兰元|NZ$|OMR|阿曼里亚尔|PAB|巴拿马巴波亚|PEN|秘鲁索尔|PGK|巴布亚新几内亚基那|PHP|菲律宾比索|₱|PKR|巴基斯坦卢比|PLN|波兰兹罗提|zł|PYG|巴拉圭瓜拉尼|₲|QAR|卡塔尔里亚尔|RON|罗马尼亚列伊|lei|RSD|塞尔维亚第纳尔|RUB|俄罗斯卢布|₽|RWF|卢旺达法郎|RF|SAR|沙特里亚尔|SBD|所罗门群岛元|SCR|塞舌尔卢比|SDG|苏丹镑|SEK|瑞典克朗|SGD|新加坡元|SLL|塞拉利昂利昂|SOS|索马里先令|SRD|苏里南元|SSP|南苏丹镑|STN|圣多美和普林西比多布拉|Db|SYP|叙利亚镑|SZL|斯威士兰里兰吉尼|THB|泰铢|฿|TJS|塔吉克斯坦索莫尼|TMT|土库曼斯坦马纳特|TND|突尼斯第纳尔|TOP|汤加潘加|T$|TRY|土耳其里拉|₺|TL|TTD|特立尼达和多巴哥元|TWD|新台币|NT$|TZS|坦桑尼亚先令|UAH|乌克兰格里夫纳|₴|UGX|乌干达先令|USD|美元|US$|UYU|乌拉圭比索|UZS|乌兹别克斯坦苏姆|VES|委内瑞拉玻利瓦尔|VND|越南盾|₫|VUV|瓦努阿图瓦图|WST|萨摩亚塔拉|XAF|中非法郎|FCFA|XCD|东加勒比元|EC$|XOF|西非法郎|CFA|XPF|太平洋法郎|CFPF|YER|也门里亚尔|ZAR|南非兰特|R|ZMW|赞比亚克瓦查|ZK|units|per|compoundUnitPattern|{0}/{1}|times|{0}⋅{1}|acceleration-g-force|{0}G力|acceleration-meter-per-second-squared|每平方秒{0}米|angle-revolution|{0}转|angle-radian|{0}弧度|angle-degree|{0}度|angle-arc-minute|{0}弧分|angle-arc-second|{0}弧秒|area-square-kilometer|{0}平方公里|perUnitPattern|每平方公里{0}|area-hectare|{0}公顷|area-square-meter|{0}平方米|每平方米{0}|area-square-centimeter|{0}平方厘米|每平方厘米{0}|area-square-mile|{0}平方英里|每平方英里{0}|area-acre|{0}英亩|area-square-yard|{0}平方码|area-square-foot|{0}平方英尺|area-square-inch|{0}平方英寸|每平方英寸{0}|area-dunam|{0}杜纳亩|concentr-karat|{0}克拉|concentr-milligram-per-deciliter|每分升{0}毫克|concentr-millimole-per-liter|每升{0}毫摩尔|concentr-part-per-million|百万分之{0}|concentr-percent|{0}%25|concentr-permille|{0}‰|concentr-permyriad|{0}‱|concentr-mole|{0}摩尔|consumption-liter-per-kilometer|每公里{0}升|consumption-liter-per-100kilometers|{0}升/100千米|consumption-mile-per-gallon|每加仑{0}英里|consumption-mile-per-gallon-imperial|每英制加仑{0}英里|digital-petabyte|{0}拍字节|digital-terabyte|{0}太字节|digital-terabit|{0}太比特|digital-gigabyte|{0}吉字节|digital-gigabit|{0}吉比特|digital-megabyte|{0}兆字节|digital-megabit|{0}兆比特|digital-kilobyte|{0}千字节|digital-kilobit|{0}千比特|digital-byte|{0}字节|digital-bit|{0}比特|duration-century|{0}个世纪|duration-decade|{0}年代|duration-year|{0}年|每年{0}|duration-month|{0}个月|每月{0}|duration-week|{0}周|每周{0}|duration-day|{0}天|每天{0}|duration-hour|{0}小时|每小时{0}|duration-minute|{0}分钟|每分钟{0}|duration-second|{0}秒|{0}/秒|duration-millisecond|{0}毫秒|duration-microsecond|{0}微秒|duration-nanosecond|{0}纳秒|electric-ampere|{0}安培|electric-milliampere|{0}毫安|electric-ohm|{0}欧姆|electric-volt|{0}伏特|energy-kilocalorie|{0}千卡|energy-calorie|{0}卡路里|energy-foodcalorie|energy-kilojoule|{0}千焦|energy-joule|{0}焦耳|energy-kilowatt-hour|{0}千瓦时|energy-electronvolt|{0}电子伏特|energy-british-thermal-unit|{0}英热单位|energy-therm-us|{0}美制克卡|force-pound-force|{0}磅力|force-newton|{0}牛顿|frequency-gigahertz|{0}吉赫|frequency-megahertz|{0}兆赫|frequency-kilohertz|{0}千赫|frequency-hertz|{0}赫兹|graphics-em|{0}版式|graphics-pixel|{0}像素|graphics-megapixel|{0}百万像素|graphics-pixel-per-centimeter|{0}像素每厘米|graphics-pixel-per-inch|{0}像素每英寸|graphics-dot-per-centimeter|{0}点每厘米|graphics-dot-per-inch|{0}点每英寸|length-kilometer|{0}公里|每公里{0}|length-meter|{0}米|每米{0}|length-decimeter|{0}分米|length-centimeter|{0}厘米|每厘米{0}|length-millimeter|{0}毫米|length-micrometer|{0}微米|length-nanometer|{0}纳米|length-picometer|{0}皮米|length-mile|{0}英里|length-yard|{0}码|length-foot|{0}英尺|每英尺{0}|length-inch|{0}英寸|每英寸{0}|length-parsec|{0}秒差距|length-light-year|{0}光年|length-astronomical-unit|{0}天文单位|length-furlong|{0}弗隆|length-fathom|{0}英寻|length-nautical-mile|{0}海里|length-mile-scandinavian|{0}斯堪的纳维亚英里|length-point|{0}+pt|length-solar-radius|{0}太阳半径|light-lux|{0}勒克斯|light-solar-luminosity|{0}太阳光度|mass-metric-ton|{0}公吨|mass-kilogram|{0}千克|每千克{0}|mass-gram|{0}克|每克{0}|mass-milligram|{0}毫克|mass-microgram|{0}微克|mass-ton|{0}吨|mass-stone|{0}英石|mass-pound|{0}磅|每磅{0}|mass-ounce|{0}盎司|每盎司{0}|mass-ounce-troy|{0}金衡制盎司|mass-carat|mass-dalton|{0}道尔顿|mass-earth-mass|{0}地球质量|mass-solar-mass|{0}太阳质量|power-gigawatt|{0}吉瓦|power-megawatt|{0}兆瓦|power-kilowatt|{0}千瓦|power-watt|{0}瓦特|power-milliwatt|{0}毫瓦|power-horsepower|{0}马力|pressure-millimeter-of-mercury|{0}毫米汞柱|pressure-pound-per-square-inch|每平方英寸{0}磅|pressure-inch-hg|{0}英寸汞柱|pressure-bar|{0}巴|pressure-millibar|{0}毫巴|pressure-atmosphere|{0}个标准大气压|pressure-pascal|{0}帕斯卡|pressure-hectopascal|{0}百帕斯卡|pressure-kilopascal|{0}千帕斯卡|pressure-megapascal|{0}兆帕斯卡|speed-kilometer-per-hour|每小时{0}公里|speed-meter-per-second|每秒{0}米|speed-mile-per-hour|每小时{0}英里|speed-knot|{0}节|temperature-generic|{0}°|temperature-celsius|{0}摄氏度|temperature-fahrenheit|{0}华氏度|temperature-kelvin|{0}开氏度|torque-pound-foot|{0}磅英尺|torque-newton-meter|{0}牛顿米|volume-cubic-kilometer|{0}立方千米|volume-cubic-meter|{0}立方米|每立方米{0}|volume-cubic-centimeter|{0}立方厘米|每立方厘米{0}|volume-cubic-mile|{0}立方英里|volume-cubic-yard|{0}立方码|volume-cubic-foot|{0}立方英尺|volume-cubic-inch|{0}立方英寸|volume-megaliter|{0}兆升|volume-hectoliter|{0}公石|volume-liter|{0}升|每升{0}|volume-deciliter|{0}分升|volume-centiliter|{0}厘升|volume-milliliter|{0}毫升|volume-pint-metric|{0}公制品脱|volume-cup-metric|{0}公制杯|volume-acre-foot|{0}英亩英尺|volume-bushel|{0}蒲式耳|volume-gallon|{0}加仑|每加仑{0}|volume-gallon-imperial|{0}英制加仑|每英制加仑{0}|volume-quart|{0}夸脱|volume-pint|{0}品脱|volume-cup|{0}杯|volume-fluid-ounce|{0}液盎司|volume-fluid-ounce-imperial|{0}英制液盎司|volume-tablespoon|{0}汤匙|volume-teaspoon|{0}茶匙|volume-barrel|{0}桶|coordinateUnit|east|东经{0}|north|北纬{0}|south|南纬{0}|west|西经{0}|{0}G|{0}米/秒²|{0}/平方公里|{0}/平方米|{0}/平方厘米|{0}/平方英里|{0}/平方英寸|{0}毫克/分升|{0}毫摩尔/升|{0}ppm|{0}%25|{0}升/公里|{0}英里/加仑|{0}英里/英制加仑|{0}+PB|{0}/年|{0}/月|{0}/周|{0}/天|{0}/小时|{0}/分钟|{0}安|{0}欧|{0}伏|{0}卡|{0}电子伏|{0}牛|{0}赫|{0}+em|{0}+px|{0}+MP|{0}+ppcm|{0}+ppi|{0}+dpcm|{0}+dpi|{0}/公里|{0}/米|{0}/厘米|{0}/英尺|{0}/英寸|{0}+pt|{0}/千克|{0}/克|{0}/磅|{0}/盎司|{0}金衡盎司|{0}瓦|{0}个大气压|{0}帕|{0}百帕|{0}千帕|{0}兆帕|{0}米/秒|{0}英里/小时|{0}°C|{0}°F|{0}°K|{0}牛米|{0}/立方米|{0}/立方厘米|{0}/升|{0}/加仑|{0}/英制加仑|{0}′|{0}″|{0}km²|{0}ha|{0}m²|{0}mi²|{0}ac|{0}ft²|{0}%25|{0}L/100km|{0}+PB|{0}+em|{0}+px|{0}+MP|{0}+ppcm|{0}+ppi|{0}+dpcm|{0}+dpi|{0}pm|{0}mi|{0}yd|{0}ly|{0}smi|{0}+pt|{0}kW|{0}W|{0}hp|{0}\"+Hg|{0}mb|{0}hPa|{0}公里/小时|{0}m/s|{0}mi/h|{0}km³|{0}mi³|{0}E|{0}N|{0}S^^^$0|$1|$2|$3|$4|$5|$6|$7|$8|9|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V]|W|$8|8|A|A|C|C|E|E|G|G|I|I|K|K|M|M|O|O|Q|Q|S|S|U|U]|X|$8|Y|A|Z|C|10|E|11|G|12|I|13|K|14|M|15|O|16|Q|17|S|18|U|19]]|1A|$7|$8|9|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V]|W|$8|8|A|A|C|C|E|E|G|G|I|I|K|K|M|M|O|O|Q|Q|S|S|U|U]|X|$8|Y|A|Z|C|10|E|11|G|12|I|13|K|14|M|15|O|16|Q|17|S|18|U|19]]]|1B|$6|$7|$1C|1D|1E|1F|1G|1H|1I|1J|1K|1L|1M|1N|1O|1P]|W|$1C|1Q|1E|1R|1G|1S|1I|1T|1K|1U|1M|1V|1O|1W]|1X|$1C|1D|1E|1F|1G|1H|1I|1J|1K|1L|1M|1N|1O|1P]|X|$1C|1Y|1E|1Z|1G|20|1I|21|1K|22|1M|23|1O|24]]|1A|$7|$1C|1D|1E|1F|1G|1H|1I|1J|1K|1L|1M|1N|1O|1P]|W|$1C|1Q|1E|1R|1G|1S|1I|1T|1K|1U|1M|1V|1O|1W]|1X|$1C|1D|1E|1F|1G|1H|1I|1J|1K|1L|1M|1N|1O|1P]|X|$1C|1Y|1E|1Z|1G|20|1I|21|1K|22|1M|23|1O|24]]]|25|$6|$7|$8|26|A|27|C|28|E|29]|W|$8|8|A|A|C|C|E|E]|X|$8|2A|A|2B|C|2C|E|2D]]|1A|$7|$8|26|A|27|C|28|E|29]|W|$8|8|A|A|C|C|E|E]|X|$8|2A|A|2B|C|2C|E|2D]]]|2E|$6|$7|$2F|2G|2H|2I|2J|2K|2L|2M|2N|2I|2O|2P|2Q|2K|2R|2S|2T|2U]|W|$2F|2G|2H|2I|2J|2K|2L|2M|2N|2I|2O|2P|2Q|2K|2R|2S|2T|2U]|X|$2F|2G|2H|2I|2J|2K|2L|2V|2N|2I|2O|2P|2Q|2K|2R|2S|2T|2U]]|1A|$7|$2F|2G|2H|2I|2J|2K|2L|2M|2N|2I|2O|2P|2Q|2K|2R|2S|2T|2U]|W|$2F|2G|2H|2I|2J|2K|2L|2M|2N|2I|2O|2P|2Q|2K|2R|2S|2T|2U]|X|$2F|2G|2H|2I|2J|2K|2L|2M|2N|2I|2O|2P|2Q|2K|2R|2S|2T|2U]]]|2W|$2X|$2Y|2Z|8|30|31|32|33|34]|35|$2Y|2Z|8|30|31|32|33|34]|36|$2Y|2Z|8|30|31|32|33|34]]|37|$38|39|3A|3B|3C|3B|1X|3D]|3E|$38|3F|3A|3G|3C|3H|1X|3I]|3J|$38|3K|3A|3L|3C|3M|1X|3N|3O|$3P|3Q|3R|3S|3T|3U|3V|3W|3X|3Y|3Z|40|41|42|43|44|45|46|47|48|49|4A|4B|4C|4D|4E|4F|4G|4H|4I|4J|4K|4L|4M|4N|4O|4P|3I|4Q|4R|4S|3H|4T|4U|4V|4W|4X|4Y|4Z|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|5G|5H|5E|5I|5E|5J|5K|5L|5M|5N|5O|5P|5Q|5R|3B|5S|5T|5U|5Q|5V|5Q|5W|3B|5X|5Y|5Z|5Q|60|61|62|61|63|64]|65|$66|67|68|69|6A|6B|6C|6D|6E|6F|6G|6H|6I|6J|6K|6L|6M|6N|6O|6P|6Q|6R]|6S|$6T|6U|3P|$6V|6W|4L|6X]|3R|$6V|6Y|4L|6Z|70|6Z]|3V|$3V|71]|4D|$72|73|5N|74]|75|$72|76|53|77|5N|78]|79|$3V|7A|72|7B|53|7C|5N|7D]|7E|$3V|7F|72|7G|53|7H|5N|7I]|4F|$72|7J|53|7K|5N|7L]|4H|$3V|7M|72|7N|53|7O|5N|7P]|4J|$3V|7Q|72|7R|53|7S|5N|7T]|4L|$7U|7V|4L|7W]|4N|$4N|7X]|4P|$7U|7Y|4L|7Z|70|7Z]|4Q|$4N|80|70|80]|4Z|$7U|81|4L|82|70|82]|51|$4N|83|70|83]|84|$7U|85|4L|86]|87|$4N|88]|53|$53|89]|55|$3V|8A|53|8A]|57|$3V|8B|53|8B]|5B|$53|8C]|5D|$3V|8D|53|8E]|5F|$3V|8F|53|8F]|5N|$5N|8G]|5P|$53|8H|5N|8H]|5R|$3V|8I|53|8I|5N|8I]|5S|$3V|8J|53|8J|5N|8J]|5V|$53|8K|5N|8H]|5W|$3V|8L|53|8M|5N|8N]|5X|$3V|8O|53|8O|5N|8P]|5Z|$53|8K|5N|8H]]]]]|8Q|$8R|$8S|8T]|8U|$8S|8T]|8V|$8S|8T]|8W|$8S|8X|8Y|8Z|90|91|92|93|94|$95|96]|97|$95|98]]|99|$8S|8X|8Y|8Z|90|91|92|93|94|$95|96]|97|$95|98]]|9A|$8S|8X|8Y|8Z|90|91|92|93|94|$95|96]|97|$95|98]]|9B|$8S|9C|8Y|9D|90|9E|92|9F|94|$95|9G]|97|$95|9H]]|9I|$8S|9J|8Y|9D|90|9E|92|9F|94|$95|9G]|97|$95|9H]]|9K|$8S|9J|8Y|9D|90|9E|92|9F|94|$95|9G]|97|$95|9H]]|9L|$8S|9M|8Y|9N|90|9O|92|9P|94|$95|9Q]|97|$95|9R]]|9S|$8S|9M|8Y|9N|90|9O|92|9P|94|$95|9Q]|97|$95|9R]]|9T|$8S|9M|8Y|9N|90|9O|92|9P|94|$95|9Q]|97|$95|9R]]|9U|$8S|9V|8Y|9W|90|9X|92|9Y|94|$95|9Z]|97|$95|A0]|A1|A2]|A3|$8S|9V|8Y|9W|90|9X|92|9Y|94|$95|9Z]|97|$95|A0]|A1|A2]|A4|$8S|9V|8Y|9W|90|9X|92|9Y|94|$95|9Z]|97|$95|A0]|A1|A2]|A5|$8S|A6]|A7|$8S|A6]|A8|$8S|A6]|A9|$8S|1Q|AA|AB|8Y|AC|90|AD|92|AE|AF|AG|94|$95|AH]|97|$95|AI]]|AJ|$8S|1Q|AA|AB|8Y|AC|90|AD|92|AE|AF|AG|94|$95|AH]|97|$95|AI]]|AK|$8S|1Q|AA|AB|8Y|AC|90|AD|92|AE|AF|AG|94|$95|AH]|97|$95|AI]]|AL|$8S|AM]|AN|$8S|AM]|AO|$8S|AM]|AP|$8S|AQ]|AR|$8S|AQ]|AS|$8S|AQ]|AT|$8S|AU]|AV|$8S|AU]|AW|$8S|AU]|1C|$8Y|AX|90|AY|92|AZ|94|$95|B0]|97|$95|B1]]|B2|$8Y|AX|90|AY|92|AZ|94|$95|B0]|97|$95|B1]]|B3|$8Y|AX|90|AY|92|AZ|94|$95|B0]|97|$95|B1]]|1E|$8Y|B4|90|B5|92|B6|94|$95|B7]|97|$95|B8]]|B9|$8Y|B4|90|B5|92|B6|94|$95|B7]|97|$95|B8]]|BA|$8Y|B4|90|B5|92|B6|94|$95|B7]|97|$95|B8]]|1G|$8Y|BB|90|BC|92|BD|94|$95|BE]|97|$95|BF]]|BG|$8Y|BB|90|BC|92|BD|94|$95|BE]|97|$95|BF]]|BH|$8Y|BB|90|BC|92|BD|94|$95|BE]|97|$95|BF]]|1I|$8Y|BI|90|BJ|92|BK|94|$95|BL]|97|$95|BM]]|BN|$8Y|BI|90|BJ|92|BK|94|$95|BL]|97|$95|BM]]|BO|$8Y|BI|90|BJ|92|BK|94|$95|BL]|97|$95|BM]]|1K|$8Y|BP|90|BQ|92|BR|94|$95|BS]|97|$95|BT]]|BU|$8Y|BP|90|BQ|92|BR|94|$95|BS]|97|$95|BT]]|BV|$8Y|BP|90|BQ|92|BR|94|$95|BS]|97|$95|BT]]|1M|$8Y|BW|90|BX|92|BY|94|$95|BZ]|97|$95|C0]]|C1|$8Y|BW|90|BX|92|BY|94|$95|BZ]|97|$95|C0]]|C2|$8Y|BW|90|BX|92|BY|94|$95|BZ]|97|$95|C0]]|1O|$8Y|C3|90|C4|92|C5|94|$95|C6]|97|$95|C7]]|C8|$8Y|C3|90|C4|92|C5|94|$95|C6]|97|$95|C7]]|C9|$8Y|C3|90|C4|92|C5|94|$95|C6]|97|$95|C7]]|CA|$8S|CB]|CC|$8S|CB]|CD|$8S|CB]|CE|$8S|CF|90|CG|94|$95|CH]|97|$95|CI]]|CJ|$8S|CF|90|CK|94|$95|CH]|97|$95|CI]]|CL|$8S|CF|90|CM|94|$95|CH]|97|$95|CI]]|CN|$8S|CO|90|CP|94|$95|CQ]|97|$95|CR]]|CS|$8S|CT|90|CP|94|$95|CQ]|97|$95|CR]]|CU|$8S|CT|90|CP|94|$95|CQ]|97|$95|CR]]|CV|$8S|CW|90|CX|94|$95|CY]|97|$95|CZ]]|D0|$8S|CW|90|CX|94|$95|CY]|97|$95|CZ]]|D1|$8S|CW|90|CX|94|$95|CY]|97|$95|CZ]]|D2|$8S|D3]|D4|$8S|D3]|D5|$8S|D3]]|D6|$D7|D8|D9|DA|DB|DC|DD|DE|DF|DG|DH|DI|DJ|DK]]|DL|$DM|DN|DO|8|DP|$DQ|DR|DS|DT|DU|DV|DW|DX|DY|DZ|E0|E1|E2|3X|E3|E4|E5|E6|E7|E8|E9|EA|EB|EC]|ED|$EE|$EF|$EG|EH|EI|EJ|EK|EL]|EM|$EG|EN|EI|EJ|EK|EL]]|EO|EP|EQ|ER|1X|$EO|$ES|2Y|ET|EU|EV|EW|EX|EY|EZ|F0|F1|F2|F3|F4|F5|F6|F7|F8|F9|FA|FB|FC|FD|FE]]|FF|FG]|FH|$EO|FI|3A|$FJ|$ES|2Y|ET|FK|EV|FL|EX|FM|EZ|FN|F1|FO|F3|FP|F5|FQ|F7|FR|F9|FS|FB|FT|FD|FU]]|1X|$FJ|$ES|2Y|ET|FK|EV|FL|EX|FM|EZ|FN|F1|FO|F3|FP|F5|FQ|F7|FR|F9|FS|FB|FT|FD|FU]]]|FV|$EO|FW]|FX|$FY|$FZ|G0|G1|FY]|G2|$FZ|G3|G1|G2]|G4|$FZ|G5|G1|G4]|G6|$FZ|G7|G1|G6]|G8|$FZ|G9|G1|G8|GA|GB]|GC|$FZ|GD|G1|GC|GA|GE]|GF|$FZ|GG|G1|GH|GA|GE]|GI|$FZ|GJ|G1|GI]|GK|$FZ|GL|G1|GK]|GM|$FZ|GN|G1|GM|GA|GO]|GP|$FZ|GQ|G1|GP|GA|GE]|GR|$FZ|GS|G1|GR|GA|GT]|GU|$FZ|GV|G1|GU]|GW|$FZ|GX|G1|GW]|GY|$FZ|GZ|G1|GY]|H0|$FZ|H1|G1|H0|GA|GE]|H2|$FZ|H3|G1|H2|GA|GE]|H4|$FZ|H5|G1|H4|GA|H6]|H7|$FZ|H8|G1|H9|GA|H9]|HA|$FZ|HB|G1|HA|GA|GE]|HC|$FZ|HD|G1|HC]|HE|$G1|HE]|HF|$FZ|HG|G1|HF|GA|HH]|HI|$FZ|HJ|G1|HI|GA|HK]|HL|$FZ|HM|G1|HL|GA|GE]|HN|$FZ|HO|G1|HP|GA|GE]|HQ|$FZ|HR|G1|HQ]|HS|$FZ|HT|G1|HS]|HU|$FZ|HV|G1|HU|GA|GE]|HW|$FZ|HX|G1|HY|GA|HZ]|I0|$FZ|I1|G1|I0|GA|GE]|I2|$FZ|I3|G1|I2|GA|I4]|I5|$FZ|I6|G1|I5|GA|GE]|I7|$FZ|I8|G1|I7]|I9|$FZ|IA|G1|I9|GA|IB]|IC|$FZ|ID|G1|IC]|IE|$FZ|IF|G1|IE|GA|IG]|IH|$FZ|II|G1|IH|GA|GE]|IJ|$FZ|IK|G1|IJ]|IL|$FZ|IM|G1|IL|GA|IN]|IO|$FZ|IP|G1|IO]|IQ|$FZ|IR|G1|IQ]|IS|$FZ|IT|G1|IU|GA|IU]|IV|$FZ|IW|G1|IV|GA|GE]|IX|$FZ|IY|G1|IZ|GA|IZ]|J0|$FZ|J1|G1|J0|GA|J2|J3|J2]|J4|$FZ|J5|G1|J4]|J6|$FZ|J7|G1|J6]|J8|$FZ|J9|G1|J8|GA|JA]|JB|$FZ|JC|G1|JB|GA|JD]|JE|$FZ|JF|G1|JE|GA|GE]|JG|$FZ|JH|G1|JI|GA|GE]|JJ|$FZ|JK|G1|JJ|GA|JL]|JM|$FZ|JN|G1|JM|GA|JO]|JP|$FZ|JQ|G1|JP]|JR|$FZ|JS|G1|JR|GA|JT]|JU|$FZ|JV|G1|JU|GA|JW]|JX|$FZ|JY|G1|JZ|GA|JZ]|K0|$FZ|K1|G1|K2|GA|K2]|K3|$FZ|K4|G1|K3]|K5|$FZ|K6|G1|K5]|K7|$FZ|K8|G1|K7|GA|IG]|K9|$FZ|KA|G1|K9|GA|GE]|KB|$FZ|KC|G1|KB]|KD|$FZ|KE|G1|KF|GA|HZ]|KG|$FZ|KH|G1|KG]|KI|$FZ|KJ|G1|KI]|KK|$FZ|KL|G1|KK|GA|KM]|KN|$FZ|KO|G1|KN|GA|KP]|KQ|$FZ|KR|G1|KS|GA|KT]|KU|$FZ|KV|G1|KU]|KW|$FZ|KX|G1|KW|GA|GE]|KY|$FZ|KZ|G1|KY|GA|L0]|L1|$FZ|L2|G1|L1|GA|L3]|L4|$FZ|L5|G1|L4|GA|L6]|L7|$FZ|L8|G1|L7|GA|L9]|LA|$FZ|LB|G1|LA|GA|GE]|LC|$FZ|LD|G1|LC]|LE|$FZ|LF|G1|LE|GA|LG]|LH|$FZ|LI|G1|LH]|LJ|$FZ|LK|G1|LJ]|LL|$FZ|LM|G1|LL]|LN|$FZ|LO|G1|LN|GA|LP]|LQ|$FZ|LR|G1|LQ]|LS|$FZ|LT|G1|LS|GA|LU]|LV|$FZ|LW|G1|LV|GA|LX]|LY|$FZ|LZ|G1|M0]|M1|$FZ|M2|G1|M1]|M3|$FZ|M4|G1|M3|GA|L9]|M5|$FZ|M6|G1|M5]|M7|$FZ|M8|G1|M7]|M9|$FZ|MA|G1|MB|GA|GE]|MC|$FZ|MD|G1|MC|GA|ME]|MF|$FZ|MG|G1|MF]|MH|$FZ|MI|G1|MH|GA|GE]|MJ|$FZ|MK|G1|MJ|GA|ML]|MM|$FZ|MN|G1|MM|GA|MO]|MP|$FZ|MQ|G1|MP|GA|IG]|MR|$FZ|MS|G1|MR|GA|L9]|MT|$FZ|MU|G1|MV|GA|GE]|MW|$FZ|MX|G1|MW]|MY|$FZ|MZ|G1|MY]|N0|$FZ|N1|G1|N0]|N2|$FZ|N3|G1|N2]|N4|$FZ|N5|G1|N4|GA|N6]|N7|$FZ|N8|G1|N7|GA|L9]|N9|$FZ|NA|G1|N9|GA|NB]|NC|$FZ|ND|G1|NC|GA|NE]|NF|$FZ|NG|G1|NF]|NH|$FZ|NI|G1|NH|GA|NJ]|NK|$FZ|NL|G1|NK]|NM|$FZ|NN|G1|NM|GA|NO]|NP|$FZ|NQ|G1|NP|GA|NR]|NS|$FZ|NT|G1|NS]|NU|$FZ|NV|G1|NU|GA|GE]|NW|$FZ|NX|G1|NW]|NY|$FZ|NZ|G1|NY]|O0|$FZ|O1|G1|O0|GA|IG]|O2|$FZ|O3|G1|O2|GA|GE]|O4|$FZ|O5|G1|O4]|O6|$FZ|O7|G1|O6]|O8|$FZ|O9|G1|O8|GA|GE]|OA|$FZ|OB|G1|OA|GA|IZ]|OC|$FZ|OD|G1|OC|GA|OE]|OF|$FZ|OG|G1|OF|GA|IZ]|OH|$FZ|OI|G1|OH]|OJ|$FZ|OK|G1|OJ|GA|OL]|OM|$FZ|ON|G1|OM]|OO|$FZ|OP|G1|OO]|OQ|$FZ|OR|G1|OQ]|OS|$FZ|OT|G1|OS|GA|OU]|OV|$FZ|OW|G1|OV|GA|OX|J3|OY]|OZ|$FZ|P0|G1|OZ|GA|GE]|P1|$FZ|P2|G1|P3|GA|P3]|P4|$FZ|P5|G1|P4]|P6|$FZ|P7|G1|P6|GA|P8]|P9|$FZ|PA|G1|P9]|PB|$FZ|PC|G1|PD|GA|GE]|PE|$FZ|PF|G1|PE|GA|GE]|PG|$FZ|PH|G1|PG]|PI|$FZ|PJ|G1|PI]|PK|$FZ|PL|G1|PM|GA|PM]|PN|$FZ|PO|G1|PN]|PP|$FZ|PQ|G1|PP]|PR|$FZ|PS|G1|PT]|PU|$FZ|PV|G1|PW|GA|GE]|PX|$FZ|PY|G1|PZ]|Q0|$FZ|Q1|G1|Q2]|Q3|$FZ|Q4|G1|Q3]|Q5|$FZ|Q6|G1|Q5|GA|Q7]|Q8|$FZ|Q9|G1|Q8|GA|QA]]]|QB|$3A|$QC|$QD|QE]|QF|$QD|QG]|QH|$FF|QI]|QJ|$FF|QK]|QL|$FF|QM]|QN|$FF|QO]|QP|$FF|QQ]|QR|$FF|QS]|QT|$FF|QU]|QV|$FF|QW|QX|QY]|QZ|$FF|R0]|R1|$FF|R2|QX|R3]|R4|$FF|R5|QX|R6]|R7|$FF|R8|QX|R9]|RA|$FF|RB]|RC|$FF|RD]|RE|$FF|RF]|RG|$FF|RH|QX|RI]|RJ|$FF|RK]|RL|$FF|RM]|RN|$FF|RO]|RP|$FF|RQ]|RR|$FF|RS]|RT|$FF|RU]|RV|$FF|RW]|RX|$FF|RY]|RZ|$FF|S0]|S1|$FF|S2]|S3|$FF|S4]|S5|$FF|S6]|S7|$FF|S8]|S9|$FF|SA]|SB|$FF|SC]|SD|$FF|SE]|SF|$FF|SG]|SH|$FF|SI]|SJ|$FF|SK]|SL|$FF|SM]|SN|$FF|SO]|SP|$FF|SQ]|SR|$FF|SS]|ST|$FF|SU]|SV|$FF|SW]|SX|$FF|SY]|SZ|$FF|T0|QX|T1]|T2|$FF|T3|QX|T4]|T5|$FF|T6|QX|T7]|T8|$FF|T9|QX|TA]|TB|$FF|TC|QX|TD]|TE|$FF|TF|QX|TG]|TH|$FF|TI|QX|TJ]|TK|$FF|TL]|TM|$FF|TN]|TO|$FF|TP]|TQ|$FF|TR]|TS|$FF|TT]|TU|$FF|TV]|TW|$FF|TX]|TY|$FF|TZ]|U0|$FF|U1]|U2|$FF|U1]|U3|$FF|U4]|U5|$FF|U6]|U7|$FF|U8]|U9|$FF|UA]|UB|$FF|UC]|UD|$FF|UE]|UF|$FF|UG]|UH|$FF|UI]|UJ|$FF|UK]|UL|$FF|UM]|UN|$FF|UO]|UP|$FF|UQ]|UR|$FF|US]|UT|$FF|UU]|UV|$FF|UW]|UX|$FF|UY]|UZ|$FF|V0]|V1|$FF|V2]|V3|$FF|V4]|V5|$FF|V6|QX|V7]|V8|$FF|V9|QX|VA]|VB|$FF|VC]|VD|$FF|VE|QX|VF]|VG|$FF|VH]|VI|$FF|VJ]|VK|$FF|VL]|VM|$FF|VN]|VO|$FF|VP]|VQ|$FF|VR]|VS|$FF|VT|QX|VU]|VV|$FF|VW|QX|VX]|VY|$FF|VZ]|W0|$FF|W1]|W2|$FF|W3]|W4|$FF|W5]|W6|$FF|W7]|W8|$FF|W9]|WA|$FF|WB]|WC|$FF|WD]|WE|$FF|WF]|WG|$FF|WH]|WI|$FF|WJ]|WK|$FF|WL]|WM|$FF|WN|QX|WO]|WP|$FF|WQ|QX|WR]|WS|$FF|WT]|WU|$FF|WV]|WW|$FF|WX]|WY|$FF|WZ]|X0|$FF|X1|QX|X2]|X3|$FF|X4|QX|X5]|X6|$FF|X7]|X8|$FF|RM]|X9|$FF|XA]|XB|$FF|XC]|XD|$FF|XE]|XF|$FF|XG]|XH|$FF|XI]|XJ|$FF|XK]|XL|$FF|XM]|XN|$FF|XO]|XP|$FF|XQ]|XR|$FF|XS]|XT|$FF|XU]|XV|$FF|XW]|XX|$FF|XY]|XZ|$FF|Y0]|Y1|$FF|Y2]|Y3|$FF|Y4]|Y5|$FF|Y6]|Y7|$FF|Y8]|Y9|$FF|YA]|YB|$FF|YC]|YD|$FF|YE]|YF|$FF|YG]|YH|$FF|YI]|YJ|$FF|YK]|YL|$FF|YM]|YN|$FF|YO]|YP|$FF|YQ]|YR|$FF|YS]|YT|$FF|YU]|YV|$FF|YW]|YX|$FF|YY|QX|YZ]|Z0|$FF|Z1|QX|Z2]|Z3|$FF|Z4]|Z5|$FF|Z6]|Z7|$FF|Z8]|Z9|$FF|ZA]|ZB|$FF|ZC]|ZD|$FF|ZE]|ZF|$FF|ZG|QX|ZH]|ZI|$FF|ZJ]|ZK|$FF|ZL]|ZM|$FF|ZN]|ZO|$FF|ZP]|ZQ|$FF|ZR]|ZS|$FF|ZT]|ZU|$FF|ZV]|ZW|$FF|ZX|QX|ZY]|ZZ|$FF|100|QX|101]|102|$FF|103]|104|$FF|105]|106|$FF|107]|108|$FF|109]|10A|$FF|10B]|10C|$FF|10D]|10E|$FF|10F]|10G|$FF|10H]|10I|$10J|10K|10L|10M|10N|10O|10P|10Q]]|1X|$QC|$QD|QE]|QF|$QD|QG]|QH|$FF|10R]|QJ|$FF|10S]|QL|$FF|QM]|QN|$FF|QO]|QP|$FF|YK]|QR|$FF|QS]|QT|$FF|QU]|QV|$FF|QW|QX|10T]|QZ|$FF|R0]|R1|$FF|R2|QX|10U]|R4|$FF|R5|QX|10V]|R7|$FF|R8|QX|10W]|RA|$FF|RB]|RC|$FF|RD]|RE|$FF|RF]|RG|$FF|RH|QX|10X]|RJ|$FF|RK]|RL|$FF|RM]|RN|$FF|10Y]|RP|$FF|10Z]|RR|$FF|110]|RT|$FF|111]|RV|$FF|RW]|RX|$FF|RY]|RZ|$FF|S0]|S1|$FF|112]|S3|$FF|S4]|S5|$FF|113]|S7|$FF|114]|S9|$FF|115]|SB|$FF|SC]|SD|$FF|SE]|SF|$FF|SG]|SH|$FF|SI]|SJ|$FF|SK]|SL|$FF|SM]|SN|$FF|SO]|SP|$FF|SQ]|SR|$FF|SS]|ST|$FF|SU]|SV|$FF|SW]|SX|$FF|SY]|SZ|$FF|T0|QX|116]|T2|$FF|T3|QX|117]|T5|$FF|T6|QX|118]|T8|$FF|T9|QX|119]|TB|$FF|TC|QX|11A]|TE|$FF|TF|QX|11B]|TH|$FF|TI|QX|TJ]|TK|$FF|TL]|TM|$FF|TN]|TO|$FF|TP]|TQ|$FF|11C]|TS|$FF|TT]|TU|$FF|11D]|TW|$FF|11E]|TY|$FF|TZ]|U0|$FF|11F]|U2|$FF|11F]|U3|$FF|U4]|U5|$FF|U6]|U7|$FF|U8]|U9|$FF|11G]|UB|$FF|UC]|UD|$FF|UE]|UF|$FF|UG]|UH|$FF|11H]|UJ|$FF|UK]|UL|$FF|UM]|UN|$FF|UO]|UP|$FF|11I]|UR|$FF|11J]|UT|$FF|11K]|UV|$FF|11L]|UX|$FF|11M]|UZ|$FF|11N]|V1|$FF|11O]|V3|$FF|11P]|V5|$FF|V6|QX|11Q]|V8|$FF|V9|QX|11R]|VB|$FF|VC]|VD|$FF|VE|QX|11S]|VG|$FF|VH]|VI|$FF|VJ]|VK|$FF|VL]|VM|$FF|VN]|VO|$FF|VP]|VQ|$FF|VR]|VS|$FF|VT|QX|11T]|VV|$FF|VW|QX|11U]|VY|$FF|VZ]|W0|$FF|W1]|W2|$FF|W3]|W4|$FF|W5]|W6|$FF|W7]|W8|$FF|W9]|WA|$FF|WB]|WC|$FF|11V]|WE|$FF|WF]|WG|$FF|WH]|WI|$FF|WJ]|WK|$FF|WL]|WM|$FF|WN|QX|11W]|WP|$FF|WQ|QX|11X]|WS|$FF|WT]|WU|$FF|WV]|WW|$FF|WX]|WY|$FF|WZ]|X0|$FF|X1|QX|11Y]|X3|$FF|X4|QX|11Z]|X6|$FF|120]|X8|$FF|RM]|X9|$FF|XA]|XB|$FF|XC]|XD|$FF|XE]|XF|$FF|XG]|XH|$FF|XI]|XJ|$FF|XK]|XL|$FF|121]|XN|$FF|XO]|XP|$FF|XQ]|XR|$FF|XS]|XT|$FF|XU]|XV|$FF|XW]|XX|$FF|XY]|XZ|$FF|Y0]|Y1|$FF|122]|Y3|$FF|123]|Y5|$FF|124]|Y7|$FF|125]|Y9|$FF|126]|YB|$FF|YC]|YD|$FF|127]|YF|$FF|128]|YH|$FF|YI]|YJ|$FF|YK]|YL|$FF|129]|YN|$FF|12A]|YP|$FF|12B]|YR|$FF|YS]|YT|$FF|12C]|YV|$FF|YW]|YX|$FF|YY|QX|12D]|Z0|$FF|Z1|QX|12E]|Z3|$FF|Z4]|Z5|$FF|Z6]|Z7|$FF|Z8]|Z9|$FF|ZA]|ZB|$FF|ZC]|ZD|$FF|ZE]|ZF|$FF|ZG|QX|12F]|ZI|$FF|ZJ]|ZK|$FF|ZL]|ZM|$FF|ZN]|ZO|$FF|ZP]|ZQ|$FF|ZR]|ZS|$FF|ZT]|ZU|$FF|ZV]|ZW|$FF|ZX|QX|12G]|ZZ|$FF|100|QX|12H]|102|$FF|103]|104|$FF|105]|106|$FF|107]|108|$FF|109]|10A|$FF|10B]|10C|$FF|10D]|10E|$FF|10F]|10G|$FF|10H]|10I|$10J|10K|10L|10M|10N|10O|10P|10Q]]|W|$QC|$QD|QE]|QF|$QD|QG]|QH|$FF|10R]|QJ|$FF|10S]|QL|$FF|QM]|QN|$FF|QO]|QP|$FF|YK]|QR|$FF|12I]|QT|$FF|12J]|QV|$FF|12K|QX|10T]|QZ|$FF|12L]|R1|$FF|12M|QX|10U]|R4|$FF|R5|QX|10V]|R7|$FF|12N|QX|10W]|RA|$FF|12O]|RC|$FF|RD]|RE|$FF|12P]|RG|$FF|RH|QX|10X]|RJ|$FF|RK]|RL|$FF|RM]|RN|$FF|10Y]|RP|$FF|10Z]|RR|$FF|110]|RT|$FF|12Q]|RV|$FF|RW]|RX|$FF|RY]|RZ|$FF|S0]|S1|$FF|112]|S3|$FF|12R]|S5|$FF|113]|S7|$FF|114]|S9|$FF|12S]|SB|$FF|SC]|SD|$FF|SE]|SF|$FF|SG]|SH|$FF|SI]|SJ|$FF|SK]|SL|$FF|SM]|SN|$FF|SO]|SP|$FF|SQ]|SR|$FF|SS]|ST|$FF|SU]|SV|$FF|SW]|SX|$FF|SY]|SZ|$FF|T0|QX|116]|T2|$FF|T3|QX|117]|T5|$FF|T6|QX|118]|T8|$FF|T9|QX|119]|TB|$FF|TC|QX|11A]|TE|$FF|TF|QX|11B]|TH|$FF|TI|QX|TJ]|TK|$FF|TL]|TM|$FF|TN]|TO|$FF|TP]|TQ|$FF|11C]|TS|$FF|TT]|TU|$FF|11D]|TW|$FF|11E]|TY|$FF|TZ]|U0|$FF|11F]|U2|$FF|11F]|U3|$FF|U4]|U5|$FF|U6]|U7|$FF|U8]|U9|$FF|11G]|UB|$FF|UC]|UD|$FF|UE]|UF|$FF|UG]|UH|$FF|11H]|UJ|$FF|UK]|UL|$FF|UM]|UN|$FF|UO]|UP|$FF|11I]|UR|$FF|12T]|UT|$FF|12U]|UV|$FF|12V]|UX|$FF|12W]|UZ|$FF|12X]|V1|$FF|12Y]|V3|$FF|12Z]|V5|$FF|V6|QX|11Q]|V8|$FF|V9|QX|11R]|VB|$FF|VC]|VD|$FF|VE|QX|11S]|VG|$FF|VH]|VI|$FF|VJ]|VK|$FF|VL]|VM|$FF|130]|VO|$FF|131]|VQ|$FF|132]|VS|$FF|12I|QX|11T]|VV|$FF|12J|QX|11U]|VY|$FF|VZ]|W0|$FF|133]|W2|$FF|W3]|W4|$FF|W5]|W6|$FF|W7]|W8|$FF|W9]|WA|$FF|134]|WC|$FF|135]|WE|$FF|WF]|WG|$FF|WH]|WI|$FF|WJ]|WK|$FF|WL]|WM|$FF|WN|QX|11W]|WP|$FF|WQ|QX|11X]|WS|$FF|WT]|WU|$FF|WV]|WW|$FF|WX]|WY|$FF|WZ]|X0|$FF|X1|QX|11Y]|X3|$FF|X4|QX|11Z]|X6|$FF|120]|X8|$FF|RM]|X9|$FF|XA]|XB|$FF|XC]|XD|$FF|XE]|XF|$FF|XG]|XH|$FF|XI]|XJ|$FF|136]|XL|$FF|137]|XN|$FF|XO]|XP|$FF|138]|XR|$FF|XS]|XT|$FF|XU]|XV|$FF|139]|XX|$FF|XY]|XZ|$FF|13A]|Y1|$FF|122]|Y3|$FF|123]|Y5|$FF|13B]|Y7|$FF|125]|Y9|$FF|126]|YB|$FF|13C]|YD|$FF|13D]|YF|$FF|13E]|YH|$FF|YI]|YJ|$FF|YK]|YL|$FF|129]|YN|$FF|12A]|YP|$FF|12B]|YR|$FF|YS]|YT|$FF|12C]|YV|$FF|13F]|YX|$FF|YY|QX|12D]|Z0|$FF|Z1|QX|12E]|Z3|$FF|13G]|Z5|$FF|Z6]|Z7|$FF|Z8]|Z9|$FF|ZA]|ZB|$FF|ZC]|ZD|$FF|ZE]|ZF|$FF|ZG|QX|12F]|ZI|$FF|ZJ]|ZK|$FF|ZL]|ZM|$FF|ZN]|ZO|$FF|ZP]|ZQ|$FF|ZR]|ZS|$FF|ZT]|ZU|$FF|ZV]|ZW|$FF|ZX|QX|12G]|ZZ|$FF|100|QX|12H]|102|$FF|103]|104|$FF|105]|106|$FF|107]|108|$FF|109]|10A|$FF|10B]|10C|$FF|10D]|10E|$FF|10F]|10G|$FF|10H]|10I|$10J|13H|10L|13I|10N|13J|10P|137]]]]]]') -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-globalize", 3 | "version": "4.5.1", 4 | "description": "Internationalization (i18n) for React Native", 5 | "author": "Josh Swan ", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/joshswan/react-native-globalize" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/joshswan/react-native-globalize/issues" 14 | }, 15 | "license": "MIT", 16 | "keywords": [ 17 | "react native", 18 | "internationalization", 19 | "globalization", 20 | "localization", 21 | "i18n", 22 | "l10n" 23 | ], 24 | "files": [ 25 | "dist/", 26 | "locale-data/", 27 | "LICENSE", 28 | "README.md" 29 | ], 30 | "scripts": { 31 | "build": "tsc", 32 | "cldr": "gulp", 33 | "lint": "tsc --noEmit && eslint src test --ext .ts,.tsx", 34 | "release": "release-it", 35 | "test": "jest --coverage" 36 | }, 37 | "dependencies": { 38 | "@types/globalize": "^1.5.0", 39 | "cldrjs": "^0.5.5", 40 | "dayjs": "^1.10.1", 41 | "globalize": "^1.6.0", 42 | "hoist-non-react-statics": "^3.3.2", 43 | "jsonpack": "^1.1.5" 44 | }, 45 | "devDependencies": { 46 | "@babel/core": "^7.12.10", 47 | "@babel/preset-typescript": "^7.12.7", 48 | "@commitlint/cli": "^11.0.0", 49 | "@commitlint/config-conventional": "^11.0.0", 50 | "@release-it/conventional-changelog": "^2.0.0", 51 | "@testing-library/react-native": "^7.1.0", 52 | "@types/cldrjs": "^0.4.22", 53 | "@types/hoist-non-react-statics": "^3.3.1", 54 | "@types/jest": "^26.0.19", 55 | "@types/node": "^14.14.19", 56 | "@types/react": "^16.9.49", 57 | "@types/react-native": "^0.63.43", 58 | "@types/react-test-renderer": "^17.0.0", 59 | "@typescript-eslint/eslint-plugin": "^4.11.1", 60 | "@typescript-eslint/parser": "^4.11.1", 61 | "cldr-data": "^36.0.0", 62 | "coveralls": "^3.1.0", 63 | "del": "^6.0.0", 64 | "eslint": "^7.17.0", 65 | "eslint-config-airbnb": "^18.2.1", 66 | "eslint-config-prettier": "^7.1.0", 67 | "eslint-plugin-import": "^2.22.1", 68 | "eslint-plugin-jsx-a11y": "^6.4.1", 69 | "eslint-plugin-prettier": "^3.3.0", 70 | "eslint-plugin-react": "^7.22.0", 71 | "eslint-plugin-react-hooks": "^4.2.0", 72 | "gulp": "^4.0.2", 73 | "gulp-merge-json": "^2.1.1", 74 | "husky": "^4.3.6", 75 | "jest": "^26.6.3", 76 | "jest-junit": "^12.0.0", 77 | "lodash": "^4.17.20", 78 | "merge-stream": "^2.0.0", 79 | "metro-react-native-babel-preset": "^0.63.0", 80 | "mockdate": "^3.0.2", 81 | "prettier": "^2.2.1", 82 | "react": "16.13.1", 83 | "react-native": "^0.63.2", 84 | "react-test-renderer": "^16.13.1", 85 | "release-it": "^14.2.2", 86 | "rimraf": "^3.0.2", 87 | "through2": "^4.0.2", 88 | "typescript": "^4.1.3" 89 | }, 90 | "peerDependencies": { 91 | "react": ">=16.8.0", 92 | "react-native": ">=0.59.0" 93 | }, 94 | "resolutions": { 95 | "@types/react": "^16.9.49", 96 | "@types/react-native": "^0.63.43" 97 | }, 98 | "husky": { 99 | "hooks": { 100 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 101 | } 102 | }, 103 | "jest": { 104 | "preset": "react-native", 105 | "roots": [ 106 | "/src" 107 | ], 108 | "collectCoverageFrom": [ 109 | "/src/**/*.{ts,tsx}" 110 | ], 111 | "setupFilesAfterEnv": [ 112 | "/test/setup.test.js" 113 | ] 114 | }, 115 | "jest-junit": { 116 | "outputDirectory": "./artifacts/jest", 117 | "outputName": "results.xml" 118 | }, 119 | "release-it": { 120 | "git": { 121 | "commitMessage": "chore: release ${version}", 122 | "tagName": "v${version}" 123 | }, 124 | "npm": { 125 | "publish": true 126 | }, 127 | "github": { 128 | "release": true, 129 | "releaseName": "${version}" 130 | }, 131 | "hooks": { 132 | "after:bump": "rimraf dist && yarn build" 133 | }, 134 | "plugins": { 135 | "@release-it/conventional-changelog": { 136 | "preset": "conventionalcommits" 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/components/FormattedPlural.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { Text } from 'react-native'; 11 | import { Globalize, PluralGeneratorOptions } from '../globalize'; 12 | import { useGlobalize } from '../hooks'; 13 | import { createPropFilter, TextProps } from './utils'; 14 | 15 | type Props = PluralGeneratorOptions & 16 | TextProps & { 17 | value: Parameters[0]; 18 | other: React.ReactNode; 19 | zero?: React.ReactNode; 20 | one?: React.ReactNode; 21 | two?: React.ReactNode; 22 | few?: React.ReactNode; 23 | many?: React.ReactNode; 24 | }; 25 | 26 | const filterProps = createPropFilter>( 27 | ({ type, value }) => [value, { type }], 28 | ); 29 | 30 | export const FormattedPlural: React.FC = ({ children, ...props }) => { 31 | const { formatPlural } = useGlobalize(); 32 | const [args, textProps] = filterProps(props); 33 | const plural = formatPlural(...args); 34 | const { [plural]: formatted, other } = props; 35 | 36 | return ( 37 | 38 | {formatted || other} 39 | {children} 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/GlobalizeProvider.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React, { useEffect, useImperativeHandle, useState } from 'react'; 10 | import { createGlobalize, Globalize } from '../globalize'; 11 | import { GlobalizeContext } from '../context'; 12 | 13 | export interface Props { 14 | children: React.ReactNode; 15 | currency?: string; 16 | defaultLocale?: string; 17 | locale?: string; 18 | localeFallback?: boolean; 19 | onError?(message: string, exception?: Error): void; 20 | } 21 | 22 | export const GlobalizeProvider = React.forwardRef( 23 | ( 24 | { 25 | children, 26 | currency: currencyCode = 'USD', 27 | defaultLocale, 28 | locale = 'en', 29 | localeFallback: fallback = false, 30 | ...options 31 | }, 32 | ref, 33 | ) => { 34 | const [globalize, setGlobalize] = useState(() => 35 | createGlobalize({ 36 | ...options, 37 | locale, 38 | currencyCode, 39 | defaultLocale, 40 | fallback, 41 | }), 42 | ); 43 | 44 | useEffect(() => { 45 | setGlobalize( 46 | createGlobalize({ 47 | ...options, 48 | locale, 49 | currencyCode, 50 | defaultLocale, 51 | fallback, 52 | }), 53 | ); 54 | }, [currencyCode, defaultLocale, locale, fallback]); // eslint-disable-line react-hooks/exhaustive-deps 55 | 56 | useImperativeHandle(ref, () => globalize); 57 | 58 | return {children}; 59 | }, 60 | ); 61 | -------------------------------------------------------------------------------- /src/components/__tests__/FormattedCurrency.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 11 | import { FormattedCurrency } from '..'; 12 | 13 | describe('', () => { 14 | test('renders correctly', () => { 15 | const tree = createWithGlobalize().toJSON(); 16 | 17 | expect(tree).toMatchSnapshot(); 18 | }); 19 | 20 | describe('symbolForm', () => { 21 | test('supports symbolForm prop to use alt narrow currency symbol', () => { 22 | const tree = createWithGlobalize( 23 | , 24 | ).toJSON(); 25 | 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/__tests__/FormattedDate.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 11 | import { FormattedDate } from '..'; 12 | 13 | const date = new Date(2019, 0, 1); 14 | 15 | describe('', () => { 16 | test('renders correctly', () => { 17 | const tree = createWithGlobalize().toJSON(); 18 | 19 | expect(tree).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/__tests__/FormattedMessage.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { Text } from 'react-native'; 11 | import { loadMessages } from '../../globalize'; 12 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 13 | import { FormattedMessage } from '..'; 14 | 15 | const messages = { 16 | en: { 17 | date: 'Todays date is {date}', 18 | hello: 'Hey {name}!', 19 | test: 'Test', 20 | }, 21 | }; 22 | 23 | describe('', () => { 24 | beforeAll(() => loadMessages(messages)); 25 | 26 | test('renders correctly', () => { 27 | const tree = createWithGlobalize(, { 28 | currency: 'USD', 29 | locale: 'en', 30 | }).toJSON(); 31 | 32 | expect(tree).toMatchSnapshot(); 33 | }); 34 | 35 | describe('values', () => { 36 | test('replaces variables using values prop', () => { 37 | const tree = createWithGlobalize(, { 38 | currency: 'USD', 39 | locale: 'en', 40 | }).toJSON(); 41 | 42 | expect(tree).toMatchSnapshot(); 43 | }); 44 | 45 | test('uses props passed directly to component', () => { 46 | const tree = createWithGlobalize(, { 47 | currency: 'USD', 48 | locale: 'en', 49 | }).toJSON(); 50 | 51 | expect(tree).toMatchSnapshot(); 52 | }); 53 | 54 | test('renders component values', () => { 55 | const tree = createWithGlobalize( 56 | 1/1/2019} />, 57 | { currency: 'USD', locale: 'en' }, 58 | ).toJSON(); 59 | 60 | expect(tree).toMatchSnapshot(); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/components/__tests__/FormattedNumber.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 11 | import { FormattedNumber } from '..'; 12 | 13 | describe('', () => { 14 | test('renders correctly', () => { 15 | const tree = createWithGlobalize().toJSON(); 16 | 17 | expect(tree).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/components/__tests__/FormattedPlural.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 11 | import { FormattedPlural } from '../FormattedPlural'; 12 | 13 | describe('', () => { 14 | test('renders correctly', () => { 15 | const tree = createWithGlobalize().toJSON(); 16 | 17 | expect(tree).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/components/__tests__/FormattedRelativeTime.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import MockDate from 'mockdate'; 10 | import React from 'react'; 11 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 12 | import { FormattedRelativeTime } from '..'; 13 | 14 | describe('', () => { 15 | const date1 = new Date('2017-01-01T00:00:00Z'); 16 | const date2 = new Date('2019-01-01T00:00:00Z'); 17 | 18 | beforeEach(() => { 19 | MockDate.set('2020-01-01'); 20 | }); 21 | 22 | afterEach(() => { 23 | MockDate.reset(); 24 | }); 25 | 26 | test('renders correctly', () => { 27 | const tree = createWithGlobalize().toJSON(); 28 | 29 | expect(tree).toMatchSnapshot(); 30 | }); 31 | 32 | describe('unit', () => { 33 | test('supports auto unit option', () => { 34 | const tree = createWithGlobalize( 35 | , 36 | ).toJSON(); 37 | 38 | expect(tree).toMatchSnapshot(); 39 | }); 40 | 41 | test('renders when auto unit selected and non-date value used', () => { 42 | const tree = createWithGlobalize().toJSON(); 43 | 44 | expect(tree).toMatchSnapshot(); 45 | }); 46 | }); 47 | 48 | describe('value', () => { 49 | test('supports date instances for value', () => { 50 | const tree = createWithGlobalize().toJSON(); 51 | 52 | expect(tree).toMatchSnapshot(); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/components/__tests__/FormattedUnit.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 11 | import { createGlobalize } from '../../globalize'; 12 | import { FormattedUnit } from '..'; 13 | 14 | describe('', () => { 15 | test('renders correctly', () => { 16 | const tree = createWithGlobalize().toJSON(); 17 | 18 | expect(tree).toMatchSnapshot(); 19 | }); 20 | 21 | test('uses a custom number formatter function', () => { 22 | const globalize = createGlobalize({ locale: 'en' }); 23 | const numberFormatter = globalize.getNumberFormatter({ 24 | minimumFractionDigits: 2, 25 | useGrouping: false, 26 | }); 27 | 28 | const tree = createWithGlobalize( 29 | , 35 | ).toJSON(); 36 | 37 | expect(tree).toMatchSnapshot(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/components/__tests__/GlobalizeProvider.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import renderer from 'react-test-renderer'; 11 | import type { Globalize } from '../../globalize'; 12 | import { FormattedCurrency, GlobalizeProvider, FormattedUnit } from '..'; 13 | 14 | describe('', () => { 15 | jest.useFakeTimers(); 16 | 17 | test('renders correctly', () => { 18 | const tree = renderer 19 | .create( 20 | 21 | 22 | , 23 | ) 24 | .toJSON(); 25 | 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | test('updates globalize when props change', () => { 30 | let root; 31 | 32 | renderer.act(() => { 33 | root = renderer.create( 34 | 35 | 36 | 37 | , 38 | ); 39 | }); 40 | 41 | expect(root.toJSON()).toMatchSnapshot(); 42 | 43 | renderer.act(() => { 44 | root = renderer.create( 45 | 46 | 47 | 48 | , 49 | ); 50 | }); 51 | 52 | expect(root.toJSON()).toMatchSnapshot(); 53 | 54 | renderer.act(() => { 55 | root = renderer.create( 56 | 57 | 58 | 59 | , 60 | ); 61 | }); 62 | 63 | expect(root.toJSON()).toMatchSnapshot(); 64 | }); 65 | 66 | test('exposes Globalize instance via ref', () => { 67 | const ref = React.createRef(); 68 | 69 | renderer.create( 70 | 71 | 72 | , 73 | ); 74 | 75 | expect(ref.current).toHaveProperty('locale', 'en'); 76 | expect(ref.current?.formatUnit(75, 'mile-per-hour')).toBe('75 miles per hour'); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/FormattedCurrency.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | $10.00 11 | 12 | `; 13 | 14 | exports[` symbolForm supports symbolForm prop to use alt narrow currency symbol 1`] = ` 15 | 21 | $10.00 22 | 23 | `; 24 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/FormattedDate.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | 1/1/2019 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/FormattedMessage.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | Test 11 | 12 | `; 13 | 14 | exports[` values renders component values 1`] = ` 15 | 21 | Todays date is 22 | 23 | 1/1/2019 24 | 25 | 26 | `; 27 | 28 | exports[` values replaces variables using values prop 1`] = ` 29 | 35 | Hey Josh! 36 | 37 | `; 38 | 39 | exports[` values uses props passed directly to component 1`] = ` 40 | 46 | Hey Josh! 47 | 48 | `; 49 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/FormattedNumber.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | 1,000 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/FormattedPlural.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | :) 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/FormattedRelativeTime.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | in 5 minutes 11 | 12 | `; 13 | 14 | exports[` unit renders when auto unit selected and non-date value used 1`] = ` 15 | 21 | in 10 seconds 22 | 23 | `; 24 | 25 | exports[` unit supports auto unit option 1`] = ` 26 | 32 | 3 years ago 33 | 34 | `; 35 | 36 | exports[` value supports date instances for value 1`] = ` 37 | 43 | 365 days ago 44 | 45 | `; 46 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/FormattedUnit.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | 75 miles per hour 11 | 12 | `; 13 | 14 | exports[` uses a custom number formatter function 1`] = ` 15 | 21 | 5000.00mi² 22 | 23 | `; 24 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/GlobalizeProvider.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders correctly 1`] = ` 4 | 10 | 75 miles per hour 11 | 12 | `; 13 | 14 | exports[` updates globalize when props change 1`] = ` 15 | Array [ 16 | 22 | $10.00 23 | , 24 | 30 | 75 miles per hour 31 | , 32 | ] 33 | `; 34 | 35 | exports[` updates globalize when props change 2`] = ` 36 | Array [ 37 | 43 | 10,00 US$ 44 | , 45 | 51 | 75 millas por hora 52 | , 53 | ] 54 | `; 55 | 56 | exports[` updates globalize when props change 3`] = ` 57 | Array [ 58 | 64 | 10,00 € 65 | , 66 | 72 | 75 millas por hora 73 | , 74 | ] 75 | `; 76 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/withGlobalize.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`withGlobalize() injects Globalize instance 1`] = ` 4 | 5 | $10.00 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/components/__tests__/withGlobalize.test.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { Text } from 'react-native'; 11 | import { createWithGlobalize } from '../../../test/createWithGlobalize'; 12 | import { withGlobalize, WithGlobalizeProps } from '../withGlobalize'; 13 | 14 | describe('withGlobalize()', () => { 15 | test('injects Globalize instance', () => { 16 | const Component: React.FC = ({ globalize }) => { 17 | expect(typeof globalize).toBe('object'); 18 | expect(globalize).toHaveProperty('getCurrencyFormatter'); 19 | expect(globalize).toHaveProperty('formatCurrency'); 20 | 21 | const formatter = globalize.getCurrencyFormatter(); 22 | 23 | return {formatter(10)}; 24 | }; 25 | 26 | const WrappedComponent = withGlobalize(Component); 27 | 28 | const tree = createWithGlobalize().toJSON(); 29 | 30 | expect(tree).toMatchSnapshot(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/components/index.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { createFormattedComponent } from './utils'; 10 | 11 | export const FormattedCurrency = createFormattedComponent('formatCurrency'); 12 | export const FormattedDate = createFormattedComponent('formatDate'); 13 | export const FormattedMessage = createFormattedComponent('formatMessage'); 14 | export const FormattedNumber = createFormattedComponent('formatNumber'); 15 | export const FormattedRelativeTime = createFormattedComponent('formatRelativeTime'); 16 | export const FormattedUnit = createFormattedComponent('formatUnit'); 17 | export const FormattedTime = FormattedDate; 18 | 19 | export * from './FormattedPlural'; 20 | export * from './GlobalizeProvider'; 21 | export * from './withGlobalize'; 22 | export { GlobalizeProvider as FormattedProvider } from './GlobalizeProvider'; 23 | export { TextProps } from './utils'; 24 | -------------------------------------------------------------------------------- /src/components/utils.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React, { useContext } from 'react'; 10 | import { Text, TextProps as RNTextProps } from 'react-native'; 11 | import { 12 | CurrencyFormatterOptions, 13 | DateFormatterOptions, 14 | Globalize, 15 | NumberFormatterOptions, 16 | RelativeTimeFormatterOptions, 17 | UnitFormatterOptions, 18 | MessageFormatterOptions, 19 | } from '../globalize'; 20 | import { GlobalizeContext } from '../context'; 21 | 22 | export type TextProps = Pick< 23 | RNTextProps, 24 | 'accessible' | 'accessibilityLabel' | 'adjustsFontSizeToFit' | 'allowFontScaling' | 'style' 25 | >; 26 | 27 | export const filterTextProps: ( 28 | props: T, 29 | ) => [Omit, TextProps] = ({ 30 | accessible = true, 31 | accessibilityLabel = '', 32 | adjustsFontSizeToFit = false, 33 | allowFontScaling = true, 34 | style, 35 | ...props 36 | }) => [ 37 | props, 38 | { 39 | accessible, 40 | accessibilityLabel, 41 | adjustsFontSizeToFit, 42 | allowFontScaling, 43 | style, 44 | }, 45 | ]; 46 | 47 | export const createPropFilter: ( 48 | fn: (props: Omit) => P, 49 | ) => (props: T) => [P, TextProps] = (fn) => (props) => { 50 | const [filtered, textProps] = filterTextProps(props); 51 | return [fn(filtered), textProps]; 52 | }; 53 | 54 | // eslint-disable-next-line no-shadow 55 | enum DisplayName { 56 | formatCurrency = 'FormattedCurrency', 57 | formatDate = 'FormattedDate', 58 | formatMessage = 'FormattedMessage', 59 | formatNumber = 'FormattedNumber', 60 | formatRelativeTime = 'FormattedRelativeTime', 61 | formatUnit = 'FormattedUnit', 62 | } 63 | 64 | type Formatted = { 65 | formatCurrency: Omit & { 66 | currency?: string; 67 | numberStyle?: CurrencyFormatterOptions['style']; 68 | value: Parameters[0]; 69 | }; 70 | formatDate: DateFormatterOptions & { 71 | value: Parameters[0]; 72 | }; 73 | formatMessage: MessageFormatterOptions & { 74 | id: Parameters[0]; 75 | values?: Parameters[1]; 76 | [value: string]: any; 77 | }; 78 | formatNumber: Omit & { 79 | numberStyle?: NumberFormatterOptions['style']; 80 | value: Parameters[0]; 81 | }; 82 | formatRelativeTime: RelativeTimeFormatterOptions & { 83 | unit: Parameters[1]; 84 | value: Date | number; 85 | }; 86 | formatUnit: UnitFormatterOptions & { 87 | unit: Parameters[1]; 88 | value: Parameters[0]; 89 | }; 90 | }; 91 | 92 | const extractArgs: { 93 | [Name in keyof Formatted]: ( 94 | props: Formatted[Name] & TextProps & { value: Parameters[0] }, 95 | ) => [Parameters, TextProps]; 96 | } = { 97 | formatCurrency: createPropFilter(({ currency, numberStyle = 'symbol', value, ...options }) => [ 98 | value, 99 | currency, 100 | { 101 | useGrouping: true, 102 | ...options, 103 | style: numberStyle as CurrencyFormatterOptions['style'], 104 | }, 105 | ]), 106 | formatDate: createPropFilter(({ value, ...options }) => [value, options]), 107 | formatMessage: createPropFilter(({ defaultMessage, id, message, values = {}, ...rest }) => [ 108 | /** 109 | * @deprecated Use "id" - "message" still works for backwards compatibility 110 | */ 111 | id || message, 112 | { ...values, ...rest }, 113 | { defaultMessage }, 114 | ]), 115 | formatNumber: createPropFilter(({ numberStyle = 'decimal', value, ...options }) => [ 116 | value, 117 | { 118 | useGrouping: true, 119 | ...options, 120 | style: numberStyle as NumberFormatterOptions['style'], 121 | }, 122 | ]), 123 | formatRelativeTime: createPropFilter(({ form, unit, value }) => [value, unit, { form }]), 124 | formatUnit: createPropFilter(({ unit, value, ...options }) => [value, unit, options]), 125 | }; 126 | 127 | export function createFormattedComponent( 128 | name: Name, 129 | ): React.FC { 130 | type Props = Formatted[Name] & TextProps; 131 | 132 | const Component: React.FC = ({ children, ...props }) => { 133 | const globalize = useContext(GlobalizeContext); 134 | const [args, textProps] = extractArgs[name](props as any); 135 | // @ts-ignore 136 | const formatted = globalize[name](...args); 137 | 138 | return ( 139 | 140 | {formatted} 141 | {children} 142 | 143 | ); 144 | }; 145 | Component.displayName = DisplayName[name]; 146 | 147 | return Component; 148 | } 149 | -------------------------------------------------------------------------------- /src/components/withGlobalize.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React, { useContext } from 'react'; 10 | import hoistStatics from 'hoist-non-react-statics'; 11 | import { Globalize } from '../globalize'; 12 | import { GlobalizeContext } from '../context'; 13 | 14 | export interface WithGlobalizeProps { 15 | globalize: Globalize; 16 | } 17 | 18 | export const withGlobalize =

( 19 | Component: React.ComponentType

, 20 | ): React.FC> => 21 | hoistStatics((props) => { 22 | const globalize = useContext(GlobalizeContext); 23 | 24 | return ; 25 | }, Component); 26 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { createContext } from 'react'; 10 | import { Globalize } from './globalize'; 11 | 12 | export const GlobalizeContext = createContext(null!); 13 | -------------------------------------------------------------------------------- /src/globalize/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import MockDate from 'mockdate'; 10 | import { 11 | createGlobalize, 12 | getAvailableLocales, 13 | getCurrencySymbol, 14 | loadMessages, 15 | localeIsLoaded, 16 | } from '..'; 17 | import { Globalize } from '../types'; 18 | 19 | describe('core', () => { 20 | describe('createGlobalize()', () => { 21 | test('creates globalize object', () => { 22 | const globalize = createGlobalize({ locale: 'en' }); 23 | 24 | expect(typeof globalize).toBe('object'); 25 | expect(globalize.locale).toBe('en'); 26 | expect(globalize.currencyCode).toBe('USD'); 27 | expect(globalize).toHaveProperty('getCurrencyFormatter'); 28 | expect(globalize).toHaveProperty('formatCurrency'); 29 | }); 30 | 31 | test('memoizes formatter creators', () => { 32 | const globalize = createGlobalize({ locale: 'en' }); 33 | const dateFormatter1 = globalize.getDateFormatter(); 34 | const dateFormatter2 = globalize.getDateFormatter(); 35 | 36 | expect(dateFormatter2).toBe(dateFormatter1); 37 | }); 38 | 39 | test('selects closest locale match when fallback option enabled', () => { 40 | const globalize = createGlobalize({ locale: 'en-AU', fallback: true }); 41 | 42 | expect(globalize.locale).toBe('en'); 43 | }); 44 | 45 | test('uses defaultLocale if specified and locale not found', () => { 46 | expect(() => { 47 | createGlobalize({ locale: 'ga', defaultLocale: 'en' }); 48 | }).not.toThrow(); 49 | 50 | const globalize = createGlobalize({ locale: 'ga', defaultLocale: 'en' }); 51 | expect(globalize.locale).toBe('en'); 52 | }); 53 | 54 | test('throws when locale not found and no defaultLocale specified', () => { 55 | const message = 56 | '[RNGlobalize] CLDR data for the selected language/locale has not been loaded!'; 57 | 58 | expect(() => { 59 | createGlobalize({ locale: 'ga' }); 60 | }).toThrowError(message); 61 | 62 | expect(() => { 63 | createGlobalize({ locale: 'ga', fallback: true }); 64 | }).toThrowError(message); 65 | }); 66 | 67 | test('logs errors to console', () => { 68 | const globalize = createGlobalize({ locale: 'en' }); 69 | 70 | const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); 71 | // @ts-ignore 72 | globalize.formatDate(0); 73 | 74 | expect(spy).toHaveBeenCalledTimes(1); 75 | expect(spy.mock.calls[0][0]).toMatch( 76 | '[RNGlobalize] Error formatting date. Value must be a Date object.', 77 | ); 78 | 79 | spy.mockRestore(); 80 | }); 81 | 82 | test('does not log errors if onError overridden', () => { 83 | const globalize = createGlobalize({ locale: 'en', onError: () => {} }); 84 | 85 | const spy = jest.spyOn(console, 'error'); 86 | // @ts-ignore 87 | globalize.formatDate(0); 88 | 89 | expect(spy).not.toHaveBeenCalled(); 90 | 91 | spy.mockRestore(); 92 | }); 93 | }); 94 | 95 | describe('getAvailableLocales()', () => { 96 | test('returns array of loaded locales', () => { 97 | const globalize = createGlobalize({ locale: 'en' }); 98 | 99 | expect(globalize.getAvailableLocales()).toEqual(['de', 'en', 'es']); 100 | expect(getAvailableLocales()).toEqual(globalize.getAvailableLocales()); 101 | }); 102 | }); 103 | 104 | describe('localeIsLoaded()', () => { 105 | test('returns true when locale is loaded', () => { 106 | const globalize = createGlobalize({ locale: 'en' }); 107 | 108 | expect(globalize.localeIsLoaded('en')).toBe(true); 109 | expect(localeIsLoaded('en')).toBe(true); 110 | }); 111 | 112 | test('returns false when locale is not loaded', () => { 113 | const globalize = createGlobalize({ locale: 'en' }); 114 | 115 | expect(globalize.localeIsLoaded('unknown')).toBe(false); 116 | expect(localeIsLoaded('unknown')).toBe(false); 117 | }); 118 | }); 119 | 120 | describe('getCurrencySymbol()', () => { 121 | test('returns currency symbol based on instance locale', () => { 122 | const globalize = createGlobalize({ locale: 'en' }); 123 | 124 | expect(globalize.getCurrencySymbol('CAD')).toBe('CA$'); 125 | }); 126 | 127 | test('returns currency symbol based on instance currency if no argument passed', () => { 128 | const globalize = createGlobalize({ locale: 'en' }); 129 | expect(globalize.getCurrencySymbol()).toBe('$'); 130 | }); 131 | 132 | test('returns currency symbol based on supplied locale', () => { 133 | expect(getCurrencySymbol('en', 'USD')).toBe('$'); 134 | }); 135 | 136 | test('returns alt narrow currency symbol if specified', () => { 137 | expect(getCurrencySymbol('de', 'CAD', true)).toBe('$'); 138 | }); 139 | 140 | test('returns null for locales that are not loaded', () => { 141 | expect(getCurrencySymbol('unknown', 'CAD')).toBeNull(); 142 | }); 143 | }); 144 | 145 | describe('formatters', () => { 146 | let globalize: Globalize; 147 | let consoleError: jest.SpyInstance; 148 | 149 | beforeEach(() => { 150 | globalize = createGlobalize({ locale: 'en' }); 151 | consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); 152 | }); 153 | 154 | afterEach(() => { 155 | MockDate.reset(); 156 | consoleError.mockRestore(); 157 | }); 158 | 159 | describe('formatCurrency()', () => { 160 | test('outputs formatted currency string', () => { 161 | expect(globalize.formatCurrency(10)).toBe('$10.00'); 162 | }); 163 | 164 | test('errors on non-number value', () => { 165 | // @ts-ignore 166 | expect(globalize.formatCurrency('10')).toBe('10'); 167 | expect(consoleError).toHaveBeenCalled(); 168 | }); 169 | }); 170 | 171 | describe('formatDate()', () => { 172 | test('outputs formatted date string', () => { 173 | expect(globalize.formatDate(new Date(2020, 0, 1))).toBe('1/1/2020'); 174 | }); 175 | 176 | test('errors on non-date value', () => { 177 | // @ts-ignore 178 | expect(globalize.formatDate(0)).toBe('0'); 179 | expect(consoleError).toHaveBeenCalled(); 180 | }); 181 | }); 182 | 183 | describe('formatMessage()', () => { 184 | beforeAll(() => { 185 | loadMessages({ 186 | en: { 187 | test: 'Hello {name}!', 188 | nested: { 189 | key: 'Hello {name}!', 190 | simple: 'Hi!', 191 | }, 192 | }, 193 | }); 194 | }); 195 | 196 | test('outputs formatted message string', () => { 197 | expect(globalize.formatMessage('test', { name: 'world' })).toBe('Hello world!'); 198 | expect(globalize.formatMessage('nested/key', { name: 'world' })).toBe('Hello world!'); 199 | expect(globalize.formatMessage(['nested', 'key'], { name: 'world' })).toBe('Hello world!'); 200 | expect(globalize.formatMessage('nested/simple')).toBe('Hi!'); 201 | }); 202 | 203 | test('errors when message key undefined', () => { 204 | expect(globalize.formatMessage('unknown/key')).toBe('unknown/key'); 205 | expect(consoleError).toHaveBeenCalled(); 206 | }); 207 | 208 | test('errors when invalid values are supplied', () => { 209 | expect(globalize.formatMessage('test', null)).toBe('test'); 210 | expect(consoleError).toHaveBeenCalled(); 211 | }); 212 | 213 | test('returns defaultMessage if supplied and key undefined', () => { 214 | expect(globalize.formatMessage('unknown/key', {}, { defaultMessage: 'Uh oh!' })).toBe( 215 | 'Uh oh!', 216 | ); 217 | expect(consoleError).toHaveBeenCalled(); 218 | }); 219 | }); 220 | 221 | describe('formatNumber()', () => { 222 | test('outputs formatted number string', () => { 223 | expect(globalize.formatNumber(100000)).toBe('100,000'); 224 | }); 225 | 226 | test('errors on non-number value', () => { 227 | // @ts-ignore 228 | expect(globalize.formatNumber('test')).toBe('test'); 229 | expect(consoleError).toHaveBeenCalled(); 230 | }); 231 | }); 232 | 233 | describe('formatPlural()', () => { 234 | test('outputs plural group based on value', () => { 235 | expect(globalize.formatPlural(0)).toBe('other'); 236 | expect(globalize.formatPlural(1)).toBe('one'); 237 | expect(globalize.formatPlural(2)).toBe('other'); 238 | }); 239 | 240 | test('errors on non-number value', () => { 241 | // @ts-ignore 242 | expect(globalize.formatPlural('test')).toBe('other'); 243 | expect(consoleError).toHaveBeenCalled(); 244 | }); 245 | }); 246 | 247 | describe('formatRelativeTime()', () => { 248 | test('outputs formatted relative time string', () => { 249 | expect(globalize.formatRelativeTime(10, 'second')).toBe('in 10 seconds'); 250 | expect(globalize.formatRelativeTime(-10, 'minute')).toBe('10 minutes ago'); 251 | }); 252 | 253 | test('handle values of type Date', () => { 254 | const date = new Date(2020, 0, 2); 255 | MockDate.set('2020-01-01'); 256 | 257 | expect(globalize.formatRelativeTime(date, 'day')).toBe('tomorrow'); 258 | }); 259 | 260 | test('finds appropriate unit if "auto" specified', () => { 261 | const date = new Date(2020, 1, 1); 262 | MockDate.set('2020-01-01'); 263 | 264 | expect(globalize.formatRelativeTime(date, 'auto')).toBe('next month'); 265 | }); 266 | 267 | test('errors on non-number value', () => { 268 | // @ts-ignore 269 | expect(globalize.formatRelativeTime('test', 'second')).toBe('test second'); 270 | expect(consoleError).toHaveBeenCalled(); 271 | }); 272 | }); 273 | 274 | describe('formatUnit()', () => { 275 | test('outputs formatted unit string', () => { 276 | expect(globalize.formatUnit(100, 'mile')).toBe('100 miles'); 277 | }); 278 | 279 | test('errors on non-number value', () => { 280 | // @ts-ignore 281 | expect(globalize.formatUnit('test', 'hour')).toBe('test hour'); 282 | expect(consoleError).toHaveBeenCalled(); 283 | }); 284 | }); 285 | 286 | describe('parseDate()', () => { 287 | test('returns Date parsed from string', () => { 288 | expect(globalize.parseDate('1/1/2020')).toEqual(new Date(2020, 0, 1)); 289 | }); 290 | 291 | test('errors on non-string value', () => { 292 | MockDate.set('2020-01-01'); 293 | 294 | // @ts-ignore 295 | expect(globalize.parseDate(0)).toEqual(new Date()); 296 | expect(consoleError).toHaveBeenCalled(); 297 | }); 298 | }); 299 | 300 | describe('parseNumber()', () => { 301 | test('returns number parsed from string', () => { 302 | expect(globalize.parseNumber('100,000')).toBe(100000); 303 | }); 304 | 305 | test('errors on non-string value', () => { 306 | // @ts-ignore 307 | expect(globalize.parseNumber({})).toBe(NaN); 308 | expect(consoleError).toHaveBeenCalled(); 309 | }); 310 | }); 311 | }); 312 | }); 313 | -------------------------------------------------------------------------------- /src/globalize/cache.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { 10 | CurrencyFormatter, 11 | DateFormatter, 12 | DateParser, 13 | MessageFormatter, 14 | NumberFormatter, 15 | NumberParser, 16 | PluralGenerator, 17 | RelativeTimeFormatter, 18 | UnitFormatter, 19 | } from './types'; 20 | 21 | export interface Cache { 22 | currency: Record; 23 | date: Record; 24 | dateParsers: Record; 25 | message: Record; 26 | number: Record; 27 | numberParsers: Record; 28 | plural: Record; 29 | relativeTime: Record; 30 | unit: Record; 31 | } 32 | 33 | export type CacheValue = 34 | | CurrencyFormatter 35 | | DateFormatter 36 | | DateParser 37 | | MessageFormatter 38 | | NumberFormatter 39 | | NumberParser 40 | | PluralGenerator 41 | | RelativeTimeFormatter 42 | | UnitFormatter 43 | | any; 44 | 45 | export function createCache(): Cache { 46 | return { 47 | currency: {}, 48 | date: {}, 49 | dateParsers: {}, 50 | message: {}, 51 | number: {}, 52 | numberParsers: {}, 53 | plural: {}, 54 | relativeTime: {}, 55 | unit: {}, 56 | }; 57 | } 58 | 59 | function orderedProps(obj: Record) { 60 | return Object.keys(obj) 61 | .sort() 62 | .map((key) => ({ [key]: obj[key] })); 63 | } 64 | 65 | function getCacheKey(args: any[]) { 66 | return JSON.stringify( 67 | args.map((arg) => (!!arg && typeof arg === 'object' ? orderedProps(arg) : arg)), 68 | ); 69 | } 70 | 71 | interface MemoizeFormatterFn { 72 | (builder: T, cache: Record): ( 73 | ...args: Parameters 74 | ) => any; 75 | } 76 | 77 | export const memoizeFormatter: MemoizeFormatterFn = ( 78 | formatterCreator: any, 79 | cache: Record, 80 | ) => (...args: any[]) => { 81 | const cacheId = getCacheKey(args); 82 | 83 | if (!cache[cacheId]) { 84 | // eslint-disable-next-line no-param-reassign 85 | cache[cacheId] = formatterCreator(...args); 86 | } 87 | 88 | return cache[cacheId]; 89 | }; 90 | -------------------------------------------------------------------------------- /src/globalize/formatters/currency.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { currencyFormatter } from 'globalize'; 10 | import { CurrencyFormatter, CurrencyFormatterOptions, Formatters, GlobalizeConfig } from '../types'; 11 | 12 | export function enhanceCurrencyFormatter( 13 | getCurrencyFormatter: typeof currencyFormatter, 14 | config: GlobalizeConfig, 15 | ): (currency: string, options?: CurrencyFormatterOptions) => CurrencyFormatter { 16 | return (currency?: string, options?: CurrencyFormatterOptions) => 17 | getCurrencyFormatter(currency || config.currencyCode, options); 18 | } 19 | 20 | export function formatCurrency( 21 | config: GlobalizeConfig, 22 | getCurrencyFormatter: Formatters['getCurrencyFormatter'], 23 | value: number, 24 | currencyCode?: string, 25 | options?: CurrencyFormatterOptions, 26 | ): string { 27 | try { 28 | return getCurrencyFormatter(currencyCode || config.currencyCode, options)(value); 29 | } catch (e) { 30 | config.onError('Error formatting currency. Value must be a number.', e); 31 | } 32 | 33 | return String(value); 34 | } 35 | -------------------------------------------------------------------------------- /src/globalize/formatters/date.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { DateFormatterOptions, Formatters, GlobalizeConfig } from '../types'; 10 | 11 | export function formatDate( 12 | config: GlobalizeConfig, 13 | getDateFormatter: Formatters['getDateFormatter'], 14 | value: Date, 15 | options?: DateFormatterOptions, 16 | ) { 17 | try { 18 | return getDateFormatter(options)(value); 19 | } catch (e) { 20 | config.onError('Error formatting date. Value must be a Date object.', e); 21 | } 22 | 23 | return String(value); 24 | } 25 | 26 | export function parseDate( 27 | config: GlobalizeConfig, 28 | getDateParser: Formatters['getDateParser'], 29 | value: string, 30 | options?: DateFormatterOptions, 31 | ) { 32 | try { 33 | return getDateParser(options)(value); 34 | } catch (e) { 35 | config.onError('Error parsing date. Value must be a string.', e); 36 | } 37 | 38 | return new Date(); 39 | } 40 | -------------------------------------------------------------------------------- /src/globalize/formatters/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import GlobalizeCore from 'globalize'; 10 | import { Cache, memoizeFormatter } from '../cache'; 11 | import { Formatters, GlobalizeConfig } from '../types'; 12 | import { enhanceCurrencyFormatter } from './currency'; 13 | import { enhanceMessageFormatter } from './message'; 14 | 15 | export function createFormatters(config: GlobalizeConfig, cache: Cache): Formatters { 16 | // @ts-ignore 17 | const instance = new GlobalizeCore(config.locale); 18 | 19 | return { 20 | getCurrencyFormatter: memoizeFormatter( 21 | enhanceCurrencyFormatter(instance.currencyFormatter.bind(instance), config), 22 | cache.currency, 23 | ), 24 | getDateFormatter: memoizeFormatter(instance.dateFormatter.bind(instance), cache.date), 25 | getDateParser: memoizeFormatter(instance.dateParser.bind(instance), cache.dateParsers), 26 | getMessageFormatter: memoizeFormatter( 27 | enhanceMessageFormatter(instance.messageFormatter.bind(instance), config), 28 | cache.message, 29 | ), 30 | getNumberFormatter: memoizeFormatter(instance.numberFormatter.bind(instance), cache.number), 31 | getNumberParser: memoizeFormatter(instance.numberParser.bind(instance), cache.numberParsers), 32 | getPluralGenerator: memoizeFormatter(instance.pluralGenerator.bind(instance), cache.plural), 33 | getRelativeTimeFormatter: memoizeFormatter( 34 | instance.relativeTimeFormatter.bind(instance), 35 | cache.relativeTime, 36 | ), 37 | getUnitFormatter: memoizeFormatter(instance.unitFormatter.bind(instance), cache.unit), 38 | }; 39 | } 40 | 41 | export { formatCurrency } from './currency'; 42 | export { formatDate, parseDate } from './date'; 43 | export { formatMessage } from './message'; 44 | export { formatNumber, parseNumber } from './number'; 45 | export { formatPlural } from './plural'; 46 | export { formatRelativeTime } from './relativeTime'; 47 | export { formatUnit } from './unit'; 48 | -------------------------------------------------------------------------------- /src/globalize/formatters/message.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { messageFormatter } from 'globalize'; 10 | import { Fragment, ReactElement, createElement, isValidElement } from 'react'; 11 | import { Formatters, GlobalizeConfig, MessageFormatter, MessageFormatterOptions } from '../types'; 12 | 13 | /** 14 | * The globalize core library only supports number/string replacement values when formatting 15 | * messages. To support React Element values, a random UID token that does not conflict with other 16 | * parts of the string is used as a placeholder during message formatting. The formatted message 17 | * can then be broken up into parts based on the tokens and the React Element(s) inserted. 18 | * @param getMessageFormatter The normal Globalize messageFormatter function 19 | */ 20 | export function enhanceMessageFormatter( 21 | getMessageFormatter: typeof messageFormatter, 22 | config: GlobalizeConfig, 23 | ): (id: string | string[], options?: MessageFormatterOptions) => MessageFormatter { 24 | return (id: string | string[], options: MessageFormatterOptions = {}) => { 25 | let formatter: MessageFormatter; 26 | 27 | try { 28 | formatter = getMessageFormatter(id); 29 | } catch (e) { 30 | const msgId = Array.isArray(id) ? id.join('/') : id; 31 | 32 | config.onError(`Error processing message ${msgId}.`, e); 33 | 34 | if (typeof options.defaultMessage === 'string') { 35 | return () => options.defaultMessage; 36 | } 37 | 38 | return () => msgId; 39 | } 40 | 41 | return (values: string[] | Record = {}): any => { 42 | const uid = Math.floor(Math.random() * 0x10000000000).toString(16); 43 | const tokenRegexp = new RegExp(`(@__ELEMENT-${uid}-\\d+__@)`, 'g'); 44 | const generateToken = (() => { 45 | let counter = 0; 46 | return () => `@__ELEMENT-${uid}-${counter++}__@`; // eslint-disable-line no-plusplus 47 | })(); 48 | const tokenizedValues: { [value: string]: string } = {}; 49 | const elements: { [token: string]: any } = {}; 50 | 51 | Object.keys(values).forEach((key) => { 52 | const index = key as keyof typeof values; 53 | const value = values[index]; 54 | 55 | if (isValidElement(value)) { 56 | const token = generateToken(); 57 | tokenizedValues[key] = token; 58 | elements[token] = value; 59 | } else { 60 | tokenizedValues[key] = value as string; 61 | } 62 | }); 63 | 64 | const nodes = formatter(tokenizedValues) 65 | .split(tokenRegexp) 66 | .filter((part) => !!part) 67 | .map((part) => elements[part] || part); 68 | 69 | if (nodes.length === 1 && typeof nodes[0] === 'string') { 70 | return nodes[0] as string; 71 | } 72 | 73 | return createElement(Fragment, null, ...nodes); 74 | }; 75 | }; 76 | } 77 | 78 | export function formatMessage( 79 | config: GlobalizeConfig, 80 | getMessageFormatter: Formatters['getMessageFormatter'], 81 | id: string | string[], 82 | values?: Record, 83 | options?: MessageFormatterOptions, 84 | ): string; 85 | export function formatMessage( 86 | config: GlobalizeConfig, 87 | getMessageFormatter: Formatters['getMessageFormatter'], 88 | id: string | string[], 89 | values: Record = {}, 90 | options?: MessageFormatterOptions, 91 | ): string | ReactElement { 92 | try { 93 | return getMessageFormatter(id, options)(values); 94 | } catch (e) { 95 | const msgId = Array.isArray(id) ? id.join('/') : id; 96 | config.onError(`Error formatting message ${msgId}.`, e); 97 | 98 | return String(msgId); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/globalize/formatters/number.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { Formatters, GlobalizeConfig, NumberFormatterOptions, NumberParserOptions } from '../types'; 10 | 11 | export function formatNumber( 12 | config: GlobalizeConfig, 13 | getNumberFormatter: Formatters['getNumberFormatter'], 14 | value: number, 15 | options?: NumberFormatterOptions, 16 | ): string { 17 | try { 18 | return getNumberFormatter(options)(value); 19 | } catch (e) { 20 | config.onError('Error formatting number. Value must be a number.', e); 21 | } 22 | 23 | return String(value); 24 | } 25 | 26 | export function parseNumber( 27 | config: GlobalizeConfig, 28 | getNumberParser: Formatters['getNumberParser'], 29 | value: string, 30 | options?: NumberParserOptions, 31 | ): number { 32 | try { 33 | return getNumberParser(options)(value); 34 | } catch (e) { 35 | config.onError('Error parsing number. Value must be a string.', e); 36 | } 37 | 38 | return Number(value); 39 | } 40 | -------------------------------------------------------------------------------- /src/globalize/formatters/plural.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { Formatters, GlobalizeConfig, PluralGeneratorOptions, PluralGroup } from '../types'; 10 | 11 | export function formatPlural( 12 | config: GlobalizeConfig, 13 | getPluralGenerator: Formatters['getPluralGenerator'], 14 | value: number, 15 | options?: PluralGeneratorOptions, 16 | ): PluralGroup { 17 | try { 18 | return getPluralGenerator(options)(value); 19 | } catch (e) { 20 | config.onError('Error formatting plural. Value must be a number.', e); 21 | } 22 | 23 | return 'other'; 24 | } 25 | -------------------------------------------------------------------------------- /src/globalize/formatters/relativeTime.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import dayjs from 'dayjs'; 10 | import { Formatters, GlobalizeConfig, RelativeTimeFormatterOptions, Unit } from '../types'; 11 | import { selectTimeUnit } from '../utils'; 12 | 13 | export function formatRelativeTime( 14 | config: GlobalizeConfig, 15 | getRelativeTimeFormatter: Formatters['getRelativeTimeFormatter'], 16 | value: Date | number, 17 | unit: 'auto' | 'best' | Unit, 18 | options?: RelativeTimeFormatterOptions, 19 | ): string { 20 | let resolvedValue: number; 21 | let resolvedUnit: Unit; 22 | 23 | if (unit === 'auto' || unit === 'best') { 24 | [resolvedValue, resolvedUnit] = selectTimeUnit(value); 25 | } else { 26 | resolvedValue = value instanceof Date ? dayjs(value).diff(dayjs(), unit) : value; 27 | resolvedUnit = unit; 28 | } 29 | 30 | try { 31 | return getRelativeTimeFormatter(resolvedUnit, options)(resolvedValue); 32 | } catch (e) { 33 | config.onError( 34 | 'Error formatting relative time. Unit must be a valid time unit and value must be a number', 35 | e, 36 | ); 37 | } 38 | 39 | return `${String(value)} ${String(unit)}`; 40 | } 41 | -------------------------------------------------------------------------------- /src/globalize/formatters/unit.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { UnitFormatterOptions as GlobalizeUnitFormatterOptions } from 'globalize'; 10 | import { Formatters, GlobalizeConfig, UnitFormatterOptions } from '../types'; 11 | 12 | export function formatUnit( 13 | config: GlobalizeConfig, 14 | getUnitFormatter: Formatters['getUnitFormatter'], 15 | value: number, 16 | unit: string, 17 | options?: UnitFormatterOptions, 18 | ) { 19 | try { 20 | return getUnitFormatter(unit, options as GlobalizeUnitFormatterOptions)(value); 21 | } catch (e) { 22 | config.onError('Error formatting unit. Unit must be a string and value must be a number.', e); 23 | } 24 | 25 | return `${String(value)} ${String(unit)}`; 26 | } 27 | -------------------------------------------------------------------------------- /src/globalize/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { createCache } from './cache'; 10 | import { 11 | createFormatters, 12 | formatCurrency, 13 | formatDate, 14 | formatMessage, 15 | formatNumber, 16 | formatPlural, 17 | formatRelativeTime, 18 | formatUnit, 19 | parseDate, 20 | parseNumber, 21 | } from './formatters'; 22 | import { 23 | findFallbackLocale, 24 | getAvailableLocales, 25 | getCurrencySymbol, 26 | getLocaleId, 27 | localeIsLoaded, 28 | logError, 29 | } from './utils'; 30 | import { Globalize, GlobalizeConfig } from './types'; 31 | import * as loaders from './loaders'; 32 | 33 | const configDefaults = { 34 | currencyCode: 'USD', 35 | fallback: false, 36 | onError: logError, 37 | }; 38 | 39 | type Config = Omit & Partial; 40 | 41 | export function createGlobalize(config: Config): Globalize { 42 | const cfg = { 43 | ...configDefaults, 44 | ...config, 45 | locale: getLocaleId(config.locale), 46 | }; 47 | 48 | if (!localeIsLoaded(cfg.locale)) { 49 | cfg.locale = (cfg.fallback && findFallbackLocale(cfg.locale)) || (cfg.defaultLocale as string); 50 | 51 | if (!cfg.locale) { 52 | throw new Error( 53 | '[RNGlobalize] CLDR data for the selected language/locale has not been loaded!', 54 | ); 55 | } 56 | } 57 | 58 | const cache = createCache(); 59 | const formatters = createFormatters(cfg, cache); 60 | 61 | return { 62 | ...cfg, 63 | ...formatters, 64 | ...loaders, 65 | getAvailableLocales, 66 | getCurrencySymbol(currencyCode?: string, altNarrow?: boolean) { 67 | return getCurrencySymbol(cfg.locale, currencyCode || cfg.currencyCode, altNarrow); 68 | }, 69 | localeIsLoaded, 70 | formatCurrency: formatCurrency.bind(null, cfg, formatters.getCurrencyFormatter), 71 | formatDate: formatDate.bind(null, cfg, formatters.getDateFormatter), 72 | formatMessage: formatMessage.bind(null, cfg, formatters.getMessageFormatter), 73 | formatNumber: formatNumber.bind(null, cfg, formatters.getNumberFormatter), 74 | formatPlural: formatPlural.bind(null, cfg, formatters.getPluralGenerator), 75 | formatRelativeTime: formatRelativeTime.bind(null, cfg, formatters.getRelativeTimeFormatter), 76 | formatUnit: formatUnit.bind(null, cfg, formatters.getUnitFormatter), 77 | parseDate: parseDate.bind(null, cfg, formatters.getDateParser), 78 | parseNumber: parseNumber.bind(null, cfg, formatters.getNumberParser), 79 | }; 80 | } 81 | 82 | export * from './loaders'; 83 | export * from './types'; 84 | export * from './utils'; 85 | -------------------------------------------------------------------------------- /src/globalize/loaders.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import GlobalizeCore from 'globalize'; 10 | import { Messages } from './types'; 11 | 12 | export function loadCldr(...cldrData: Record[]): void { 13 | GlobalizeCore.load(cldrData); 14 | } 15 | 16 | export function loadMessages(messageData: Record>): void { 17 | GlobalizeCore.loadMessages(messageData); 18 | } 19 | 20 | // For backwards-compatibility 21 | export const load = loadCldr; 22 | -------------------------------------------------------------------------------- /src/globalize/types.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { 10 | CurrencyFormatterOptions as GlobalizeCurrencyFormatterOptions, 11 | dateFormatter, 12 | DateFormatterOptions, 13 | dateParser, 14 | numberFormatter, 15 | NumberFormatterOptions, 16 | numberParser, 17 | NumberParserOptions, 18 | pluralGenerator, 19 | PluralGeneratorOptions, 20 | relativeTimeFormatter, 21 | RelativeTimeFormatterOptions, 22 | unitFormatter, 23 | UnitFormatterOptions as GlobalizeUnitFormatterOptions, 24 | } from 'globalize'; 25 | import { ReactElement } from 'react'; 26 | 27 | export { 28 | DateFormatterOptions, 29 | NumberFormatterOptions, 30 | NumberParserOptions, 31 | PluralGeneratorOptions, 32 | RelativeTimeFormatterOptions, 33 | }; 34 | 35 | export type Messages = Record; 36 | export type PluralGroup = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'; 37 | export type Unit = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'; 38 | 39 | export interface CurrencyFormatter { 40 | (value: number): string; 41 | } 42 | 43 | export interface CurrencyFormatterOptions extends GlobalizeCurrencyFormatterOptions { 44 | // Add symbolForm option to enable alt-narrow currency symbol 45 | // TODO: Submit PR to DT 46 | symbolForm?: 'narrow'; 47 | } 48 | 49 | export interface DateFormatter { 50 | (value: Date): string; 51 | } 52 | 53 | export interface DateParser { 54 | (value: string): Date; 55 | } 56 | 57 | export interface MessageFormatter { 58 | (values?: string[] | Record): string; 59 | (values: Record): string | ReactElement; 60 | } 61 | 62 | export interface MessageFormatterOptions { 63 | defaultMessage?: string; 64 | } 65 | 66 | export interface NumberFormatter { 67 | (value: number): string; 68 | } 69 | 70 | export interface NumberParser { 71 | (value: string): number; 72 | } 73 | 74 | export interface PluralGenerator { 75 | (value: number): PluralGroup; 76 | } 77 | 78 | export interface RelativeTimeFormatter { 79 | (value: number): string; 80 | } 81 | 82 | export interface UnitFormatter { 83 | (value: number): string; 84 | } 85 | 86 | export interface UnitFormatterOptions 87 | extends Omit { 88 | // Fix numberFormatter option definition 89 | // TODO: Submit PR to DT 90 | numberFormatter?: (value: number) => string; 91 | } 92 | 93 | export interface Formatters { 94 | getCurrencyFormatter(currency?: string, options?: CurrencyFormatterOptions): CurrencyFormatter; 95 | getDateFormatter(...args: Parameters): DateFormatter; 96 | getDateParser(...args: Parameters): DateParser; 97 | getMessageFormatter(id: string | string[], options?: MessageFormatterOptions): MessageFormatter; 98 | getNumberFormatter(...args: Parameters): NumberFormatter; 99 | getNumberParser(...args: Parameters): NumberParser; 100 | getPluralGenerator(...args: Parameters): PluralGenerator; 101 | getRelativeTimeFormatter( 102 | ...args: Parameters 103 | ): RelativeTimeFormatter; 104 | getUnitFormatter(...args: Parameters): UnitFormatter; 105 | } 106 | 107 | export interface GlobalizeConfig { 108 | locale: string; 109 | currencyCode: string; 110 | defaultLocale?: string; 111 | fallback: boolean; 112 | onError(message: string, exception?: Error): void; 113 | } 114 | 115 | export interface GlobalizeHelpers { 116 | getAvailableLocales(): string[]; 117 | getCurrencySymbol(currencyCode?: string, altNarrow?: boolean): string | null; 118 | loadCldr(...cldrData: Record[]): void; 119 | loadMessages(messageData: Record>): void; 120 | localeIsLoaded(locale: string): boolean; 121 | } 122 | 123 | export interface Globalize extends GlobalizeConfig, GlobalizeHelpers, Formatters { 124 | formatCurrency(value: number, currencyCode?: string, options?: CurrencyFormatterOptions): string; 125 | formatDate(value: Date, options?: DateFormatterOptions): string; 126 | formatMessage( 127 | id: string | string[], 128 | values?: string[] | Record, 129 | options?: MessageFormatterOptions, 130 | ): string; 131 | formatMessage( 132 | id: string | string[], 133 | values?: Record, 134 | options?: MessageFormatterOptions, 135 | ): string | ReactElement; 136 | formatNumber(value: number, options?: NumberFormatterOptions): string; 137 | formatPlural(value: number, options?: PluralGeneratorOptions): PluralGroup; 138 | formatRelativeTime(value: number, unit: Unit, options?: RelativeTimeFormatterOptions): string; 139 | formatRelativeTime( 140 | value: Date, 141 | unit: 'auto' | 'best' | Unit, 142 | options?: RelativeTimeFormatterOptions, 143 | ): string; 144 | formatUnit(value: number, unit: string, options?: UnitFormatterOptions): string; 145 | parseDate(value: string, options?: DateFormatterOptions): Date; 146 | parseNumber(value: string, options?: NumberParserOptions): number; 147 | } 148 | -------------------------------------------------------------------------------- /src/globalize/utils.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import Cldr from 'cldrjs'; 10 | import dayjs from 'dayjs'; 11 | import { Unit } from './types'; 12 | 13 | export function getLocaleId(locale: string): string { 14 | // iOS returns system locale info with underscores 15 | return locale.replace(/_/g, '-'); 16 | } 17 | 18 | export function getAvailableLocales(): string[] { 19 | return Object.keys((Cldr as any)._raw?.main || {}); 20 | } 21 | 22 | export function localeIsLoaded(locale: string): boolean { 23 | return !!(Cldr as any)._raw?.main?.[getLocaleId(locale)]; 24 | } 25 | 26 | export function findFallbackLocale(locale: string): string | null { 27 | const locales = getAvailableLocales(); 28 | 29 | for (let i = locale.length - 1; i > 1; i -= 1) { 30 | const key = locale.substring(0, i); 31 | 32 | if (locales.includes(key)) { 33 | return key; 34 | } 35 | } 36 | 37 | return null; 38 | } 39 | 40 | export function getCurrencySymbol( 41 | locale: string, 42 | currencyCode: string, 43 | altNarrow = false, 44 | ): string | null { 45 | // Check whether the locale has been loaded 46 | if (!locale || !localeIsLoaded(locale)) { 47 | return null; 48 | } 49 | 50 | const { currencies } = (Cldr as any)._raw.main[locale].numbers; 51 | const key = altNarrow ? 'symbol-alt-narrow' : 'symbol'; 52 | 53 | return currencies?.[currencyCode][key] || null; 54 | } 55 | 56 | export function logError(message: string, exception?: Error) { 57 | if (process.env.NODE_ENV !== 'production') { 58 | // eslint-disable-next-line no-console 59 | console.error(`[RNGlobalize] ${message}${exception ? `\n${exception.stack}` : ''}`); 60 | } 61 | } 62 | 63 | const UNITS: Unit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second']; 64 | 65 | export function selectTimeUnit(value: any): [number, Unit] { 66 | if (value instanceof Date) { 67 | const date = dayjs(value); 68 | const now = dayjs(); 69 | 70 | for (let i = 0, l = UNITS.length; i < l; i += 1) { 71 | const diff = date.diff(now, UNITS[i]); 72 | 73 | if (diff >= 1 || diff <= -1 || i === l - 1) { 74 | return [diff, UNITS[i]]; 75 | } 76 | } 77 | } 78 | 79 | return [Number(value), 'second']; 80 | } 81 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { useContext } from 'react'; 10 | import { GlobalizeContext } from '../context'; 11 | 12 | export const useGlobalize = () => useContext(GlobalizeContext); 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { loadCldr } from './globalize'; 10 | 11 | // Load CLDR core data 12 | // eslint-disable-next-line @typescript-eslint/no-var-requires 13 | loadCldr(require('../locale-data/core')); 14 | 15 | export * from './components'; 16 | export * from './context'; 17 | export * from './globalize'; 18 | export * from './hooks'; 19 | -------------------------------------------------------------------------------- /test/createWithGlobalize.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import React from 'react'; 10 | import { render } from '@testing-library/react-native'; 11 | import { 12 | GlobalizeProvider, 13 | Props as GlobalizeProviderProps, 14 | } from '../src/components/GlobalizeProvider'; 15 | 16 | export function createWithGlobalize( 17 | children: React.ReactNode, 18 | props: Omit = { currency: 'USD', locale: 'en' }, 19 | ) { 20 | return render({children}); 21 | } 22 | -------------------------------------------------------------------------------- /test/setup.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | import { loadCldr } from '../src'; 10 | 11 | beforeAll(() => { 12 | loadCldr( 13 | require('../locale-data/de'), 14 | require('../locale-data/en'), 15 | require('../locale-data/es'), 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "ESNEXT", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": ["ES2017"], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | "strictNullChecks": true, /* Enable strict null checks. */ 29 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "include": [ 67 | "src/**/*.ts", 68 | "src/**/*.tsx" 69 | ], 70 | "exclude": [ 71 | "node_modules", 72 | "**/*.test.ts", 73 | "**/*.test.tsx" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /utils/cldr.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | const { omit, pick, zipObject } = require('lodash'); 10 | const CURRENCIES = require('./currencies'); 11 | 12 | const CURRENCY_CODES = Object.keys(CURRENCIES); 13 | const UNIT_FORMS = ['long', 'short', 'narrow']; 14 | 15 | exports.reduceLocale = ({ main }) => { 16 | const locale = Object.keys(main)[0]; 17 | const { dates, numbers, units } = main[locale]; 18 | const { defaultNumberingSystem } = numbers; 19 | 20 | return { 21 | main: { 22 | [locale]: { 23 | dates: { 24 | ...dates, 25 | timeZoneNames: omit(dates.timeZoneNames, ['metazone', 'zone']), 26 | }, 27 | numbers: { 28 | ...pick(numbers, [ 29 | 'defaultNumberingSystem', 30 | 'minimumGroupingDigits', 31 | `symbols-numberSystem-${defaultNumberingSystem}`, 32 | `currencyFormats-numberSystem-${defaultNumberingSystem}`, 33 | `decimalFormats-numberSystem-${defaultNumberingSystem}`, 34 | `percentFormats-numberSystem-${defaultNumberingSystem}`, 35 | ]), 36 | currencies: zipObject( 37 | CURRENCY_CODES, 38 | CURRENCY_CODES.map((code) => omit(numbers.currencies[code], ['displayName'])), 39 | ), 40 | }, 41 | units: zipObject( 42 | UNIT_FORMS, 43 | UNIT_FORMS.map((form) => { 44 | const keys = Object.keys(units[form]); 45 | 46 | return zipObject( 47 | keys, 48 | keys.map((key) => omit(units[form][key], ['displayName'])), 49 | ); 50 | }), 51 | ), 52 | }, 53 | }, 54 | }; 55 | }; 56 | 57 | exports.reduceSupplemental = ({ supplemental: { currencyData, version, ...rest } }) => ({ 58 | supplemental: { 59 | currencyData: { 60 | fractions: pick(currencyData.fractions, ['DEFAULT', ...CURRENCY_CODES]), 61 | }, 62 | ...rest, 63 | }, 64 | }); 65 | -------------------------------------------------------------------------------- /utils/currencies.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Native Globalize 3 | * 4 | * Copyright 2015-2020 Josh Swan 5 | * Released under the MIT license 6 | * https://github.com/joshswan/react-native-globalize/blob/master/LICENSE 7 | */ 8 | 9 | const currencies = { 10 | AED: 'United Arab Emirates Dirham', 11 | AFN: 'Afghan Afghani', 12 | ALL: 'Albanian Lek', 13 | AMD: 'Armenian Dram', 14 | AOA: 'Angolan Kwanza', 15 | ARS: 'Argentine Peso', 16 | AUD: 'Australian Dollar', 17 | AWG: 'Aruban Florin', 18 | AZN: 'Azerbaijani Manat', 19 | BAM: 'Bosnia-Herzegovina Convertible Mark', 20 | BBD: 'Barbadian Dollar', 21 | BDT: 'Bangladeshi Taka', 22 | BGN: 'Bulgarian Lev', 23 | BHD: 'Bahraini Dinar', 24 | BIF: 'Burundian Franc', 25 | BMD: 'Bermudan Dollar', 26 | BND: 'Brunei Dollar', 27 | BOB: 'Bolivian Boliviano', 28 | BRL: 'Brazilian Real', 29 | BSD: 'Bahamian Dollar', 30 | BTN: 'Bhutanese Ngultrum', 31 | BUK: 'Burmese Kyat', 32 | BWP: 'Botswanan Pula', 33 | BYN: 'Belarusian Ruble', 34 | BZD: 'Belize Dollar', 35 | CAD: 'Canadian Dollar', 36 | CDF: 'Congolese Franc', 37 | CHF: 'Swiss Franc', 38 | CLP: 'Chilean Peso', 39 | CNY: 'Chinese Yuan', 40 | COP: 'Colombian Peso', 41 | CRC: 'Costa Rican Colón', 42 | CUP: 'Cuban Peso', 43 | CVE: 'Cape Verdean Escudo', 44 | CZK: 'Czech Koruna', 45 | DJF: 'Djiboutian Franc', 46 | DKK: 'Danish Krone', 47 | DOP: 'Dominican Peso', 48 | DZD: 'Algerian Dinar', 49 | EGP: 'Egyptian Pound', 50 | ERN: 'Eritrean Nakfa', 51 | ETB: 'Ethiopian Bir', 52 | EUR: 'Euro', 53 | FJD: 'Fijian Dollar', 54 | GBP: 'British Pound', 55 | GEL: 'Georgian Lari', 56 | GHS: 'Ghanaian Cedi', 57 | GMD: 'Gambian Dalasi', 58 | GNF: 'Guinean Franc', 59 | GTQ: 'Guatemalan Quetzal', 60 | GYD: 'Guyanaese Dollar', 61 | HKD: 'Hong Kong Dollar', 62 | HNL: 'Honduran Lempira', 63 | HRK: 'Croatian Kuna', 64 | HTG: 'Haitian Gourde', 65 | HUF: 'Hungarian Forint', 66 | IDR: 'Indonesian Rupiah', 67 | ILS: 'Israeli New Shekel', 68 | INR: 'Indian Rupee', 69 | IQD: 'Iraqi Dinar', 70 | IRR: 'Iranian Rial', 71 | ISK: 'Icelandic Króna', 72 | JMD: 'Jamaican Dollar', 73 | JOD: 'Jordanian Dinar', 74 | JPY: 'Japanese Yen', 75 | KES: 'Kenyan Shilling', 76 | KGS: 'Kyrgystani Som', 77 | KHR: 'Cambodian Riel', 78 | KMF: 'Comorian Franc', 79 | KRW: 'South Korean Won', 80 | KWD: 'Kuwaiti Dinar', 81 | KYD: 'Cayman Islands Dollar', 82 | KZT: 'Kazakhstani Tenge', 83 | LAK: 'Laotian Kip', 84 | LBP: 'Lebanese Pound', 85 | LKR: 'Sri Lankan Rupee', 86 | LRD: 'Liberian Dollar', 87 | LSL: 'Lesotho Loti', 88 | LVL: 'Latvian Lats', 89 | LYD: 'Libyan Dinar', 90 | MAD: 'Moroccan Dirham', 91 | MDL: 'Moldovan Leu', 92 | MGA: 'Malagasy Ariary', 93 | MKD: 'Macedonian Denar', 94 | MMK: 'Myanmar Kyat', 95 | MNT: 'Mongolian Tugrik', 96 | MOP: 'Macanese Pataca', 97 | MRU: 'Mauritanian Ouguiya', 98 | MUR: 'Mauritian Rupee', 99 | MVR: 'Maldivian Rufiyaa', 100 | MWK: 'Malawian Kwacha', 101 | MXN: 'Mexican Peso', 102 | MYR: 'Malaysian Ringgit', 103 | MZN: 'Mozambican Metical', 104 | NAD: 'Namibian Dollar', 105 | NGN: 'Nigerian Naira', 106 | NIO: 'Nicaraguan Córdoba', 107 | NOK: 'Norwegian Krone', 108 | NPR: 'Nepalese Rupee', 109 | NZD: 'New Zealand Dollar', 110 | OMR: 'Omani Rial', 111 | PAB: 'Panamanian Balboa', 112 | PEN: 'Peruvian Sol', 113 | PGK: 'Papua New Guinean Kina', 114 | PHP: 'Philippine Piso', 115 | PKR: 'Pakistani Rupee', 116 | PLN: 'Polish Zloty', 117 | PYG: 'Paraguayan Guarani', 118 | QAR: 'Qatari Rial', 119 | RON: 'Romanian Leu', 120 | RSD: 'Serbian Dinar', 121 | RUB: 'Russian Ruble', 122 | RWF: 'Rwandan Franc', 123 | SAR: 'Saudi Riyal', 124 | SBD: 'Solomon Islands Dollar', 125 | SCR: 'Seychellois Rupee', 126 | SDG: 'Sudanese Pound', 127 | SEK: 'Swedish Krona', 128 | SGD: 'Singapore Dollar', 129 | SLL: 'Sierra Leonean Leone', 130 | SOS: 'Somali Shilling', 131 | SRD: 'Surinamese Dollar', 132 | SSP: 'South Sudanese Pound', 133 | STN: 'São Tomé & Príncipe Dobra', 134 | SYP: 'Syrian Pound', 135 | SZL: 'Swazi Lilangeni', 136 | THB: 'Thai Baht', 137 | TJS: 'Tajikistani Somoni', 138 | TMT: 'Turkmenistani Manat', 139 | TND: 'Tunisian Dinar', 140 | TOP: 'Tongan Paʻanga', 141 | TRY: 'Turkish Lira', 142 | TTD: 'Trinidad & Tobago Dollar', 143 | TWD: 'New Taiwan Dollar', 144 | TZS: 'Tanzanian Shilling', 145 | UAH: 'Ukrainian Hryvnia', 146 | UGX: 'Ugandan Shilling', 147 | USD: 'US Dollar', 148 | UYU: 'Uruguayan Peso', 149 | UZS: 'Uzbekistani Som', 150 | VES: 'Venezuelan Bolívar', 151 | VND: 'Vietnamese Dong', 152 | VUV: 'Vanuatu Vatu', 153 | WST: 'Samoan Tala', 154 | XAF: 'Central African CFA Franc', 155 | XCD: 'East Caribbean Dollar', 156 | XOF: 'West African CFA Franc', 157 | XPF: 'CFP Franc', 158 | YER: 'Yemeni Rial', 159 | ZAR: 'South African Rand', 160 | ZMW: 'Zambian Kwacha', 161 | }; 162 | 163 | module.exports = currencies; 164 | --------------------------------------------------------------------------------