= ({ 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 |
--------------------------------------------------------------------------------