@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.
- package/dist/main.js +64 -186
- package/dist/main.js.map +1 -1
- package/dist/module.js +54 -184
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +9 -16
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -5
- package/src/index.ts +2 -1
- package/src/useDateFormatter.ts +32 -8
- package/src/useMessageFormatter.ts +17 -75
- package/src/useNumberFormatter.ts +4 -30
- package/src/utils.ts +0 -33
- package/src/useNumberParser.ts +0 -48
|
@@ -10,89 +10,31 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
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
|
|
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:
|
|
32
|
-
let {locale
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 {
|
|
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
|
|
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
|
-
}
|
package/src/useNumberParser.ts
DELETED
|
@@ -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
|
-
}
|