@react-aria/i18n 3.2.0 → 3.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,89 +10,31 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import IntlMessageFormat from 'intl-messageformat';
13
+ import {LocalizedStrings, MessageDictionary, MessageFormatter} from '@internationalized/message';
14
+ import {useCallback, useMemo} from 'react';
14
15
  import {useLocale} from './context';
15
16
 
16
- type MessageFormatterStrings = {
17
- [lang: string]: {
18
- [key: string]: string
19
- }
20
- };
21
-
22
17
  type FormatMessage = (key: string, variables?: {[key: string]: any}) => string;
23
18
 
24
- const formatterCache = new Map();
19
+ const cache = new WeakMap();
20
+ function getCachedDictionary(strings: LocalizedStrings) {
21
+ let dictionary = cache.get(strings);
22
+ if (!dictionary) {
23
+ dictionary = new MessageDictionary(strings);
24
+ cache.set(strings, dictionary);
25
+ }
26
+
27
+ return dictionary;
28
+ }
25
29
 
26
30
  /**
27
31
  * Handles formatting ICU Message strings to create localized strings for the current locale.
28
32
  * Automatically updates when the locale changes, and handles caching of messages for performance.
29
33
  * @param strings - A mapping of languages to strings by key.
30
34
  */
31
- export function useMessageFormatter(strings: MessageFormatterStrings): FormatMessage {
32
- let {locale: currentLocale} = useLocale();
33
-
34
- // Check the cache
35
- let localeCache = formatterCache.get(strings);
36
- if (localeCache && localeCache.has(currentLocale)) {
37
- return localeCache.get(currentLocale);
38
- }
39
-
40
- // Add to the formatter cache if needed
41
- if (!localeCache) {
42
- localeCache = new Map();
43
- formatterCache.set(strings, localeCache);
44
- }
45
-
46
- // Get the strings for the current locale
47
- let localeStrings = selectLocale(strings, currentLocale);
48
-
49
- // Create a new message formatter
50
- let cache = {};
51
- let formatMessage = (key, variables) => {
52
- let message = cache[key + '.' + currentLocale];
53
- if (!message) {
54
- let msg = localeStrings[key];
55
- if (!msg) {
56
- throw new Error(`Could not find intl message ${key} in ${currentLocale} locale`);
57
- }
58
-
59
- message = new IntlMessageFormat(msg, currentLocale);
60
- cache[key] = message;
61
- }
62
-
63
- return message.format(variables);
64
- };
65
-
66
- localeCache.set(currentLocale, formatMessage);
67
- return formatMessage;
68
- }
69
-
70
- function selectLocale(strings, locale) {
71
- // If there is an exact match, use it.
72
- if (strings[locale]) {
73
- return strings[locale];
74
- }
75
-
76
- // Attempt to find the closest match by language.
77
- // For example, if the locale is fr-CA (French Canadian), but there is only
78
- // an fr-FR (France) set of strings, use that.
79
- let language = getLanguage(locale);
80
- for (let key in strings) {
81
- if (key.startsWith(language + '-')) {
82
- return strings[key];
83
- }
84
- }
85
-
86
- // Nothing close, use english.
87
- return strings['en-US'];
88
- }
89
-
90
- function getLanguage(locale) {
91
- // @ts-ignore
92
- if (Intl.Locale) {
93
- // @ts-ignore
94
- return new Intl.Locale(locale).language;
95
- }
96
-
97
- return locale.split('-')[0];
35
+ export function useMessageFormatter(strings: LocalizedStrings): FormatMessage {
36
+ let {locale} = useLocale();
37
+ let dictionary = useMemo(() => getCachedDictionary(strings), [strings]);
38
+ let formatter = useMemo(() => new MessageFormatter(locale, dictionary), [locale, dictionary]);
39
+ return useCallback((key, variables) => formatter.format(key, variables), [formatter]);
98
40
  }
@@ -10,42 +10,16 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {numberFormatSignDisplayPolyfill} from './utils';
13
+ import {NumberFormatOptions, NumberFormatter} from '@internationalized/number';
14
14
  import {useLocale} from './context';
15
-
16
- let formatterCache = new Map<string, Intl.NumberFormat>();
17
-
18
- let supportsSignDisplay = false;
19
- try {
20
- // @ts-ignore
21
- supportsSignDisplay = (new Intl.NumberFormat('de-DE', {signDisplay: 'exceptZero'})).resolvedOptions().signDisplay === 'exceptZero';
22
- // eslint-disable-next-line no-empty
23
- } catch (e) {}
15
+ import {useMemo} from 'react';
24
16
 
25
17
  /**
26
18
  * Provides localized number formatting for the current locale. Automatically updates when the locale changes,
27
19
  * and handles caching of the number formatter for performance.
28
20
  * @param options - Formatting options.
29
21
  */
30
- export function useNumberFormatter(options?: Intl.NumberFormatOptions): Intl.NumberFormat {
22
+ export function useNumberFormatter(options: NumberFormatOptions = {}): Intl.NumberFormat {
31
23
  let {locale} = useLocale();
32
-
33
- let cacheKey = locale + (options ? Object.entries(options).sort((a, b) => a[0] < b[0] ? -1 : 1).join() : '');
34
- if (formatterCache.has(cacheKey)) {
35
- return formatterCache.get(cacheKey);
36
- }
37
-
38
- let numberFormatter = new Intl.NumberFormat(locale, options);
39
- // @ts-ignore
40
- let {signDisplay} = options || {};
41
- formatterCache.set(cacheKey, (!supportsSignDisplay && signDisplay != null) ? new Proxy(numberFormatter, {
42
- get(target, property) {
43
- if (property === 'format') {
44
- return (v) => numberFormatSignDisplayPolyfill(numberFormatter, signDisplay, v);
45
- } else {
46
- return target[property];
47
- }
48
- }
49
- }) : numberFormatter);
50
- return numberFormatter;
24
+ return useMemo(() => new NumberFormatter(locale, options), [locale, options]);
51
25
  }
package/src/utils.ts CHANGED
@@ -31,36 +31,3 @@ export function isRTL(locale: string) {
31
31
  let lang = locale.split('-')[0];
32
32
  return RTL_LANGS.has(lang);
33
33
  }
34
-
35
- export function numberFormatSignDisplayPolyfill(numberFormat: Intl.NumberFormat, signDisplay: 'always' | 'exceptZero' | 'auto' | 'never', num: number) {
36
- if (signDisplay === 'auto') {
37
- return numberFormat.format(num);
38
- } else if (signDisplay === 'never') {
39
- return numberFormat.format(Math.abs(num));
40
- } else {
41
- let needsPositiveSign = false;
42
- if (signDisplay === 'always') {
43
- needsPositiveSign = num > 0 || Object.is(num, 0);
44
- } else if (signDisplay === 'exceptZero') {
45
- if (Object.is(num, -0) || Object.is(num, 0)) {
46
- num = Math.abs(num);
47
- } else {
48
- needsPositiveSign = num > 0;
49
- }
50
- }
51
-
52
- if (needsPositiveSign) {
53
- let negative = numberFormat.format(-num);
54
- let noSign = numberFormat.format(num);
55
- // ignore RTL/LTR marker character
56
- let minus = negative.replace(noSign, '').replace(/\u200e|\u061C/, '');
57
- if ([...minus].length !== 1) {
58
- console.warn('@react-aria/i18n polyfill for NumberFormat signDisplay: Unsupported case');
59
- }
60
- let positive = negative.replace(noSign, '!!!').replace(minus, '+').replace('!!!', noSign);
61
- return positive;
62
- } else {
63
- return numberFormat.format(num);
64
- }
65
- }
66
- }
@@ -1,48 +0,0 @@
1
- /*
2
- * Copyright 2020 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
-
13
- import {useCallback, useEffect, useRef} from 'react';
14
- import {useLocale} from './context';
15
-
16
- type NumberParser = {
17
- parse: (value:string) => number
18
- }
19
-
20
- /**
21
- * Provides localized number parsing for the current locale.
22
- * Idea from https://observablehq.com/@mbostock/localized-number-parsing.
23
- */
24
- export function useNumberParser(): NumberParser {
25
- let {locale} = useLocale();
26
- const numberData = useRef({group: null, decimal: null, numeral: null, index: null});
27
-
28
- useEffect(() => {
29
- const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
30
- const numerals = [...new Intl.NumberFormat(locale, {useGrouping: false}).format(9876543210)].reverse();
31
- const index = new Map(numerals.map((d, i) => [d, i]));
32
-
33
- numberData.current.group = new RegExp(`[${parts.find(d => d.type === 'group').value}]`, 'g');
34
- numberData.current.decimal = new RegExp(`[${parts.find(d => d.type === 'decimal').value}]`);
35
- numberData.current.numeral = new RegExp(`[${numerals.join('')}]`, 'g');
36
- numberData.current.index = d => index.get(d);
37
- }, [locale]);
38
-
39
- const parse = useCallback((value:string) => {
40
- value = value.trim()
41
- .replace(numberData.current.group, '')
42
- .replace(numberData.current.decimal, '.')
43
- .replace(numberData.current.numeral, numberData.current.index);
44
- return value ? +value : NaN;
45
- }, []);
46
-
47
- return {parse};
48
- }