react-scoped-i18n 0.0.3 → 0.0.5
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/README.md +85 -69
- package/dist/cjs/createI18nContext/api/createFormat.d.ts +1 -1
- package/dist/cjs/createI18nContext/api/createFormat.js +91 -15
- package/dist/cjs/createI18nContext/api/createFormat.test.js +41 -12
- package/dist/cjs/createI18nContext/api/createUseI18n.d.ts +1 -1
- package/dist/cjs/createI18nContext/api/createUseI18n.js +3 -1
- package/dist/cjs/createI18nContext/api/createUseI18n.test.js +3 -1
- package/dist/cjs/createI18nContext/api/useCreateFormat.d.ts +9 -0
- package/dist/cjs/createI18nContext/api/useCreateFormat.js +62 -0
- package/dist/cjs/createI18nContext/api/useCreateFormat.test.d.ts +1 -0
- package/dist/cjs/createI18nContext/api/useCreateFormat.test.js +69 -0
- package/dist/cjs/createI18nContext/const/index.d.ts +1 -0
- package/dist/cjs/createI18nContext/const/index.js +2 -1
- package/dist/cjs/createI18nContext/index.d.ts +1 -1
- package/dist/cjs/createI18nContext/util/cache.d.ts +2 -0
- package/dist/cjs/createI18nContext/util/cache.js +22 -0
- package/dist/cjs/env/index.d.ts +1 -0
- package/dist/cjs/env/index.js +5 -0
- package/dist/cjs/env.js +1 -5
- package/dist/cjs/vendor/lru-cache.d.ts +2 -0
- package/dist/cjs/vendor/lru-cache.js +38 -0
- package/dist/esm/createI18nContext/api/createFormat.d.ts +1 -1
- package/dist/esm/createI18nContext/api/createFormat.d.ts.map +1 -1
- package/dist/esm/createI18nContext/api/createFormat.js +88 -15
- package/dist/esm/createI18nContext/api/createFormat.test.js +41 -12
- package/dist/esm/createI18nContext/api/createUseI18n.d.ts +1 -1
- package/dist/esm/createI18nContext/api/createUseI18n.d.ts.map +1 -1
- package/dist/esm/createI18nContext/api/createUseI18n.js +3 -1
- package/dist/esm/createI18nContext/api/createUseI18n.test.js +3 -1
- package/dist/esm/createI18nContext/api/useCreateFormat.d.ts +10 -0
- package/dist/esm/createI18nContext/api/useCreateFormat.d.ts.map +1 -0
- package/dist/esm/createI18nContext/api/useCreateFormat.js +58 -0
- package/dist/esm/createI18nContext/api/useCreateFormat.test.d.ts +2 -0
- package/dist/esm/createI18nContext/api/useCreateFormat.test.d.ts.map +1 -0
- package/dist/esm/createI18nContext/api/useCreateFormat.test.js +67 -0
- package/dist/esm/createI18nContext/const/index.d.ts +1 -0
- package/dist/esm/createI18nContext/const/index.d.ts.map +1 -1
- package/dist/esm/createI18nContext/const/index.js +1 -0
- package/dist/esm/createI18nContext/index.d.ts +1 -1
- package/dist/esm/createI18nContext/util/cache.d.ts +3 -0
- package/dist/esm/createI18nContext/util/cache.d.ts.map +1 -0
- package/dist/esm/createI18nContext/util/cache.js +19 -0
- package/dist/esm/env/index.d.ts +2 -0
- package/dist/esm/env/index.d.ts.map +1 -0
- package/dist/esm/env/index.js +2 -0
- package/dist/esm/env.d.ts.map +1 -1
- package/dist/esm/env.js +1 -5
- package/dist/esm/vendor/lru-cache.d.ts +3 -0
- package/dist/esm/vendor/lru-cache.d.ts.map +1 -0
- package/dist/esm/vendor/lru-cache.js +2 -0
- package/package.json +6 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.potentiallyWarnInvalidCacheState = potentiallyWarnInvalidCacheState;
|
|
4
|
+
const env_1 = require("../../env");
|
|
5
|
+
function potentiallyWarnInvalidCacheState(value, cache, options) {
|
|
6
|
+
if (!env_1.IS_DEV) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const cachedKeys = Array.from(cache.keys());
|
|
10
|
+
const isEquivalent = cachedKeys.some((key) => shallowEqual(key, options));
|
|
11
|
+
if (isEquivalent) {
|
|
12
|
+
console.warn(`[i18n] ⚠️ Formatting ${value} with options ${JSON.stringify(options)} is not cached, but it should be. This is because you are recreating the options object on every render. To fix this, memoize the options object or define it outside of the component.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function shallowEqual(a, b) {
|
|
16
|
+
const aKeys = Object.keys(a);
|
|
17
|
+
const bKeys = Object.keys(b);
|
|
18
|
+
if (aKeys.length !== bKeys.length) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return aKeys.every((key) => a[key] === b[key]);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const IS_DEV: boolean;
|
package/dist/cjs/env.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.IS_DEV = void 0;
|
|
4
|
-
const rawNodeEnv =
|
|
5
|
-
process.env &&
|
|
6
|
-
typeof process.env.NODE_ENV === `string`
|
|
7
|
-
? process.env.NODE_ENV
|
|
8
|
-
: undefined;
|
|
4
|
+
const rawNodeEnv = process.env.NODE_ENV;
|
|
9
5
|
exports.IS_DEV = typeof __DEV__ !== `undefined` ? __DEV__ : rawNodeEnv === `development`;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.LRUCache = void 0;
|
|
37
|
+
const LRU = __importStar(require("lru-cache"));
|
|
38
|
+
exports.LRUCache = LRU.LRUCache ?? LRU.default ?? LRU;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const createFormat: <const Languages extends readonly string[]>({ currentLanguage, }: {
|
|
2
2
|
currentLanguage: Languages[number];
|
|
3
3
|
}) => {
|
|
4
|
-
number: (value: number, options?: Intl.NumberFormatOptions) => string;
|
|
4
|
+
number: (value: number, options?: Omit<Intl.NumberFormatOptions, `style`>) => string;
|
|
5
5
|
date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
6
6
|
time: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
7
7
|
currency: (value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, `style` | `currency`>) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createFormat.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/api/createFormat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createFormat.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/api/createFormat.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,YAAY,GAAI,KAAK,CAAC,SAAS,SAAS,SAAS,MAAM,EAAE,EAAE,sBAErE;IACD,eAAe,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CACpC;oBAiBY,MAAM,YACH,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC;kBAwBrC,IAAI,GAAG,MAAM,YAAY,IAAI,CAAC,qBAAqB;kBAuBnD,IAAI,GAAG,MAAM,YAAY,IAAI,CAAC,qBAAqB;sBA0BxD,MAAM,YACH,MAAM,YACN,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,UAAU,CAAC;wBAoCvD,MAAM,YACH,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC;CA8BtD,CAAC"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import LRUCache from "lru-cache";
|
|
2
|
+
import { LRU_CACHE_MAX_SIZE } from "../const";
|
|
3
|
+
import { potentiallyWarnInvalidCacheState } from "../util/cache";
|
|
1
4
|
export const createFormat = ({ currentLanguage, }) => {
|
|
2
5
|
const defaultNumberFormatter = new Intl.NumberFormat(currentLanguage);
|
|
3
6
|
const defaultDateFormatter = new Intl.DateTimeFormat(currentLanguage);
|
|
@@ -11,26 +14,68 @@ export const createFormat = ({ currentLanguage, }) => {
|
|
|
11
14
|
});
|
|
12
15
|
return {
|
|
13
16
|
number: (value, options) => {
|
|
14
|
-
if (options) {
|
|
15
|
-
return
|
|
17
|
+
if (!options) {
|
|
18
|
+
return defaultNumberFormatter.format(value);
|
|
19
|
+
}
|
|
20
|
+
if (!caches.number.has(options)) {
|
|
21
|
+
potentiallyWarnInvalidCacheState(value, caches.number, options);
|
|
22
|
+
caches.number.set(options, new Intl.NumberFormat(currentLanguage, options));
|
|
23
|
+
}
|
|
24
|
+
const cachedFormatter = caches.number.get(options);
|
|
25
|
+
if (cachedFormatter) {
|
|
26
|
+
return cachedFormatter.format(value);
|
|
16
27
|
}
|
|
17
|
-
return
|
|
28
|
+
return new Intl.NumberFormat(currentLanguage, options).format(value);
|
|
18
29
|
},
|
|
19
30
|
date: (value, options) => {
|
|
20
|
-
if (options) {
|
|
21
|
-
return
|
|
31
|
+
if (!options) {
|
|
32
|
+
return defaultDateFormatter.format(value);
|
|
33
|
+
}
|
|
34
|
+
if (!caches.date.has(options)) {
|
|
35
|
+
potentiallyWarnInvalidCacheState(value, caches.date, options);
|
|
36
|
+
caches.date.set(options, new Intl.DateTimeFormat(currentLanguage, options));
|
|
37
|
+
}
|
|
38
|
+
const cachedFormatter = caches.date.get(options);
|
|
39
|
+
if (cachedFormatter) {
|
|
40
|
+
return cachedFormatter.format(value);
|
|
22
41
|
}
|
|
23
|
-
return
|
|
42
|
+
return new Intl.DateTimeFormat(currentLanguage, options).format(value);
|
|
24
43
|
},
|
|
25
44
|
time: (value, options) => {
|
|
26
|
-
if (options) {
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
if (!options) {
|
|
46
|
+
return defaultTimeFormatter.format(value);
|
|
47
|
+
}
|
|
48
|
+
if (!caches.time.has(options)) {
|
|
49
|
+
potentiallyWarnInvalidCacheState(value, caches.time, options);
|
|
50
|
+
caches.time.set(options, new Intl.DateTimeFormat(currentLanguage, options));
|
|
30
51
|
}
|
|
31
|
-
|
|
52
|
+
const cachedFormatter = caches.time.get(options);
|
|
53
|
+
if (cachedFormatter) {
|
|
54
|
+
return cachedFormatter.format(value);
|
|
55
|
+
}
|
|
56
|
+
return new Intl.DateTimeFormat(currentLanguage, {
|
|
57
|
+
...options,
|
|
58
|
+
}).format(value);
|
|
32
59
|
},
|
|
33
60
|
currency: (value, currency, options) => {
|
|
61
|
+
if (options) {
|
|
62
|
+
const cacheKey = {
|
|
63
|
+
...options,
|
|
64
|
+
currency,
|
|
65
|
+
};
|
|
66
|
+
if (!caches.currency.has(cacheKey)) {
|
|
67
|
+
potentiallyWarnInvalidCacheState(value, caches.currency, cacheKey);
|
|
68
|
+
caches.currency.set(cacheKey, new Intl.NumberFormat(currentLanguage, {
|
|
69
|
+
style: `currency`,
|
|
70
|
+
currency,
|
|
71
|
+
...options,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
const cachedFormatter = caches.currency.get(cacheKey);
|
|
75
|
+
if (cachedFormatter) {
|
|
76
|
+
return cachedFormatter.format(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
34
79
|
return new Intl.NumberFormat(currentLanguage, {
|
|
35
80
|
style: `currency`,
|
|
36
81
|
currency,
|
|
@@ -38,13 +83,41 @@ export const createFormat = ({ currentLanguage, }) => {
|
|
|
38
83
|
}).format(value);
|
|
39
84
|
},
|
|
40
85
|
percentage: (value, options) => {
|
|
41
|
-
if (options) {
|
|
42
|
-
return
|
|
86
|
+
if (!options) {
|
|
87
|
+
return defaultPercentageFormatter.format(value);
|
|
88
|
+
}
|
|
89
|
+
if (!caches.percentage.has(options)) {
|
|
90
|
+
potentiallyWarnInvalidCacheState(value, caches.number, options);
|
|
91
|
+
caches.percentage.set(options, new Intl.NumberFormat(currentLanguage, {
|
|
43
92
|
style: `percent`,
|
|
44
93
|
...options,
|
|
45
|
-
})
|
|
94
|
+
}));
|
|
46
95
|
}
|
|
47
|
-
|
|
96
|
+
const cachedFormatter = caches.percentage.get(options);
|
|
97
|
+
if (cachedFormatter) {
|
|
98
|
+
return cachedFormatter.format(value);
|
|
99
|
+
}
|
|
100
|
+
return new Intl.NumberFormat(currentLanguage, {
|
|
101
|
+
style: `percent`,
|
|
102
|
+
...options,
|
|
103
|
+
}).format(value);
|
|
48
104
|
},
|
|
49
105
|
};
|
|
50
106
|
};
|
|
107
|
+
const caches = {
|
|
108
|
+
number: new LRUCache({
|
|
109
|
+
max: LRU_CACHE_MAX_SIZE,
|
|
110
|
+
}),
|
|
111
|
+
date: new LRUCache({
|
|
112
|
+
max: LRU_CACHE_MAX_SIZE,
|
|
113
|
+
}),
|
|
114
|
+
time: new LRUCache({
|
|
115
|
+
max: LRU_CACHE_MAX_SIZE,
|
|
116
|
+
}),
|
|
117
|
+
currency: new LRUCache({
|
|
118
|
+
max: LRU_CACHE_MAX_SIZE,
|
|
119
|
+
}),
|
|
120
|
+
percentage: new LRUCache({
|
|
121
|
+
max: LRU_CACHE_MAX_SIZE,
|
|
122
|
+
}),
|
|
123
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createFormat } from "./createFormat";
|
|
2
2
|
describe(`createFormat`, () => {
|
|
3
|
+
const numberFormatOptions = {
|
|
4
|
+
minimumFractionDigits: 4,
|
|
5
|
+
};
|
|
3
6
|
const format = createFormat({
|
|
4
7
|
currentLanguage: `en-US`,
|
|
5
8
|
});
|
|
@@ -7,12 +10,11 @@ describe(`createFormat`, () => {
|
|
|
7
10
|
const formattedNumber = format.number(1234567.89);
|
|
8
11
|
expect(formattedNumber).toBe(`1,234,567.89`);
|
|
9
12
|
});
|
|
10
|
-
it(`number formatter returns a formatted value with options`, () => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
expect(formattedNumber).toBe(`12.34%`);
|
|
13
|
+
it(`number formatter returns a formatted value with options (with caching enabled)`, () => {
|
|
14
|
+
const formattedNumber1 = format.number(1234567.89, numberFormatOptions);
|
|
15
|
+
const formattedNumber2 = format.number(1234567.89, numberFormatOptions);
|
|
16
|
+
expect(formattedNumber1).toBe(`1,234,567.8900`);
|
|
17
|
+
expect(formattedNumber2).toBe(`1,234,567.8900`);
|
|
16
18
|
});
|
|
17
19
|
it(`date formatter returns a formatted value`, () => {
|
|
18
20
|
const date = new Date(`2024-01-01T12:00:00Z`);
|
|
@@ -53,12 +55,10 @@ describe(`createFormat`, () => {
|
|
|
53
55
|
expect(formattedCurrency).toBe(`€1,234.6`);
|
|
54
56
|
});
|
|
55
57
|
it(`percentage formatter returns a formatted value`, () => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const formattedPercentage = format.percentage(0.1234);
|
|
61
|
-
expect(formattedPercentage).toBe(`12%`);
|
|
58
|
+
const formattedPercentage1 = format.percentage(0.1234);
|
|
59
|
+
const formattedPercentage2 = format.percentage(0.1267);
|
|
60
|
+
expect(formattedPercentage1).toBe(`12%`);
|
|
61
|
+
expect(formattedPercentage2).toBe(`13%`);
|
|
62
62
|
});
|
|
63
63
|
it(`percentage formatter returns a formatted value with options`, () => {
|
|
64
64
|
const formattedPercentage = format.percentage(0.1234, {
|
|
@@ -66,4 +66,33 @@ describe(`createFormat`, () => {
|
|
|
66
66
|
});
|
|
67
67
|
expect(formattedPercentage).toBe(`12.34%`);
|
|
68
68
|
});
|
|
69
|
+
it(`Error: Number options are not cached`, () => {
|
|
70
|
+
const warnSpy = jest.spyOn(console, `warn`);
|
|
71
|
+
const formattedNumber1 = format.number(1234567.89, {
|
|
72
|
+
minimumFractionDigits: 5,
|
|
73
|
+
});
|
|
74
|
+
const formattedNumber2 = format.number(1234567.89, {
|
|
75
|
+
minimumFractionDigits: 5,
|
|
76
|
+
});
|
|
77
|
+
expect(formattedNumber1).toBe(`1,234,567.89000`);
|
|
78
|
+
expect(formattedNumber2).toBe(`1,234,567.89000`);
|
|
79
|
+
expect(warnSpy).toHaveBeenCalledWith(`[i18n] ⚠️ Formatting 1234567.89 with options {"minimumFractionDigits":5} is not cached, but it should be. This is because you are recreating the options object on every render. To fix this, memoize the options object or define it outside of the component.`);
|
|
80
|
+
});
|
|
81
|
+
it(`Error: Date options are not cached`, () => {
|
|
82
|
+
const warnSpy = jest.spyOn(console, `warn`);
|
|
83
|
+
const date = new Date(`2026-02-03T12:00:00Z`);
|
|
84
|
+
const formattedDate1 = format.date(date, {
|
|
85
|
+
year: `numeric`,
|
|
86
|
+
month: `long`,
|
|
87
|
+
day: `numeric`,
|
|
88
|
+
});
|
|
89
|
+
const formattedDate2 = format.date(date, {
|
|
90
|
+
year: `numeric`,
|
|
91
|
+
month: `long`,
|
|
92
|
+
day: `numeric`,
|
|
93
|
+
});
|
|
94
|
+
expect(formattedDate1).toBe(`February 3, 2026`);
|
|
95
|
+
expect(formattedDate2).toBe(`February 3, 2026`);
|
|
96
|
+
expect(warnSpy).toHaveBeenCalledWith(`[i18n] ⚠️ Formatting Tue Feb 03 2026 13:00:00 GMT+0100 (Central European Standard Time) with options {"year":"numeric","month":"long","day":"numeric"} is not cached, but it should be. This is because you are recreating the options object on every render. To fix this, memoize the options object or define it outside of the component.`);
|
|
97
|
+
});
|
|
69
98
|
});
|
|
@@ -11,7 +11,7 @@ export declare const createUseI18n: <const Languages extends readonly string[],
|
|
|
11
11
|
setCurrentLanguage: import("react").Dispatch<import("react").SetStateAction<Languages[number]>>;
|
|
12
12
|
currentLanguage: Languages[number];
|
|
13
13
|
format: {
|
|
14
|
-
number: (value: number, options?: Intl.NumberFormatOptions) => string;
|
|
14
|
+
number: (value: number, options?: Omit<Intl.NumberFormatOptions, `style`>) => string;
|
|
15
15
|
date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
16
16
|
time: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
17
17
|
currency: (value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, `style` | `currency`>) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createUseI18n.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/api/createUseI18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAuB,MAAM,OAAO,CAAC;AAMrD,eAAO,MAAM,aAAa,GACxB,KAAK,CAAC,SAAS,SAAS,SAAS,MAAM,EAAE,EACzC,KAAK,CAAC,OAAO,SAAS,MAAM,CAC1B,MAAM,EACN,cAAc,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAC1C,EACD,wDAKC;IACD,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC/B,gBAAgB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"createUseI18n.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/api/createUseI18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAuB,MAAM,OAAO,CAAC;AAMrD,eAAO,MAAM,aAAa,GACxB,KAAK,CAAC,SAAS,SAAS,SAAS,MAAM,EAAE,EACzC,KAAK,CAAC,OAAO,SAAS,MAAM,CAC1B,MAAM,EACN,cAAc,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAC1C,EACD,wDAKC;IACD,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC/B,gBAAgB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;;;;;;;;;;;;;CAqDA,CAAC"}
|
|
@@ -27,7 +27,9 @@ export const createUseI18n = ({ I18nContext, languages, fallbackLanguage, common
|
|
|
27
27
|
});
|
|
28
28
|
}, [currentLanguage]);
|
|
29
29
|
const format = useMemo(() => {
|
|
30
|
-
return createFormat({
|
|
30
|
+
return createFormat({
|
|
31
|
+
currentLanguage,
|
|
32
|
+
});
|
|
31
33
|
}, [currentLanguage]);
|
|
32
34
|
const _commons = useMemo(() => {
|
|
33
35
|
return createCommons({
|
|
@@ -14,9 +14,11 @@ describe(`createUseI18n`, () => {
|
|
|
14
14
|
.mockImplementation(() => { });
|
|
15
15
|
try {
|
|
16
16
|
useI18n();
|
|
17
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
17
18
|
}
|
|
18
19
|
catch (e) {
|
|
19
|
-
|
|
20
|
+
// eslint-disable-line unused-imports/no-unused-vars
|
|
21
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
20
22
|
}
|
|
21
23
|
consoleErrorSpy.mockRestore();
|
|
22
24
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const useCreateFormat: <const Languages extends readonly string[]>({ currentLanguage, }: {
|
|
2
|
+
currentLanguage: Languages[number];
|
|
3
|
+
}) => {
|
|
4
|
+
number: (value: number, options?: Intl.NumberFormatOptions) => string;
|
|
5
|
+
date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
6
|
+
time: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
7
|
+
currency: (value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, `style` | `currency`>) => string;
|
|
8
|
+
percentage: (value: number, options?: Omit<Intl.NumberFormatOptions, `style`>) => string;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=useCreateFormat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCreateFormat.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/api/useCreateFormat.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,GAAI,KAAK,CAAC,SAAS,SAAS,SAAS,MAAM,EAAE,EAAE,sBAExE;IACD,eAAe,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CACpC;oBAyBmB,MAAM,YAAY,IAAI,CAAC,mBAAmB;kBAQ5C,IAAI,GAAG,MAAM,YAAY,IAAI,CAAC,qBAAqB;kBAQnD,IAAI,GAAG,MAAM,YAAY,IAAI,CAAC,qBAAqB;sBAWxD,MAAM,YACH,MAAM,YACN,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,UAAU,CAAC;wBAUvD,MAAM,YACH,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC;CAYtD,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
export const useCreateFormat = ({ currentLanguage, }) => {
|
|
3
|
+
const defaultNumberFormatter = new Intl.NumberFormat(currentLanguage);
|
|
4
|
+
const defaultDateFormatter = new Intl.DateTimeFormat(currentLanguage);
|
|
5
|
+
const defaultTimeFormatter = new Intl.DateTimeFormat(currentLanguage, {
|
|
6
|
+
hour: `numeric`,
|
|
7
|
+
minute: `numeric`,
|
|
8
|
+
second: `numeric`,
|
|
9
|
+
});
|
|
10
|
+
const defaultPercentageFormatter = new Intl.NumberFormat(currentLanguage, {
|
|
11
|
+
style: `percent`,
|
|
12
|
+
});
|
|
13
|
+
const defaultCurrencyFormatter = useMemo(() => {
|
|
14
|
+
return new Intl.NumberFormat(currentLanguage, {
|
|
15
|
+
style: `currency`,
|
|
16
|
+
currency: `USD`,
|
|
17
|
+
});
|
|
18
|
+
}, [currentLanguage]);
|
|
19
|
+
console.log(defaultCurrencyFormatter.resolvedOptions().currency);
|
|
20
|
+
return {
|
|
21
|
+
number: (value, options) => {
|
|
22
|
+
if (options) {
|
|
23
|
+
return new Intl.NumberFormat(currentLanguage, options).format(value);
|
|
24
|
+
}
|
|
25
|
+
return defaultNumberFormatter.format(value);
|
|
26
|
+
},
|
|
27
|
+
date: (value, options) => {
|
|
28
|
+
if (options) {
|
|
29
|
+
return new Intl.DateTimeFormat(currentLanguage, options).format(value);
|
|
30
|
+
}
|
|
31
|
+
return defaultDateFormatter.format(value);
|
|
32
|
+
},
|
|
33
|
+
time: (value, options) => {
|
|
34
|
+
if (options) {
|
|
35
|
+
return new Intl.DateTimeFormat(currentLanguage, {
|
|
36
|
+
...options,
|
|
37
|
+
}).format(value);
|
|
38
|
+
}
|
|
39
|
+
return defaultTimeFormatter.format(value);
|
|
40
|
+
},
|
|
41
|
+
currency: (value, currency, options) => {
|
|
42
|
+
return new Intl.NumberFormat(currentLanguage, {
|
|
43
|
+
style: `currency`,
|
|
44
|
+
currency,
|
|
45
|
+
...options,
|
|
46
|
+
}).format(value);
|
|
47
|
+
},
|
|
48
|
+
percentage: (value, options) => {
|
|
49
|
+
if (options) {
|
|
50
|
+
return new Intl.NumberFormat(currentLanguage, {
|
|
51
|
+
style: `percent`,
|
|
52
|
+
...options,
|
|
53
|
+
}).format(value);
|
|
54
|
+
}
|
|
55
|
+
return defaultPercentageFormatter.format(value);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCreateFormat.test.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/api/useCreateFormat.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useCreateFormat } from "./useCreateFormat";
|
|
2
|
+
describe(`createFormat`, () => {
|
|
3
|
+
const format = useCreateFormat({
|
|
4
|
+
currentLanguage: `en-US`,
|
|
5
|
+
});
|
|
6
|
+
it(`number formatter returns a formatted value`, () => {
|
|
7
|
+
const formattedNumber = format.number(1234567.89);
|
|
8
|
+
expect(formattedNumber).toBe(`1,234,567.89`);
|
|
9
|
+
});
|
|
10
|
+
it(`number formatter returns a formatted value with options`, () => {
|
|
11
|
+
const formattedNumber = format.number(0.1234, {
|
|
12
|
+
style: `percent`,
|
|
13
|
+
minimumFractionDigits: 2,
|
|
14
|
+
});
|
|
15
|
+
expect(formattedNumber).toBe(`12.34%`);
|
|
16
|
+
});
|
|
17
|
+
it(`date formatter returns a formatted value`, () => {
|
|
18
|
+
const date = new Date(`2024-01-01T12:00:00Z`);
|
|
19
|
+
const formattedDate = format.date(date);
|
|
20
|
+
expect(formattedDate).toBe(`1/1/2024`);
|
|
21
|
+
});
|
|
22
|
+
it(`date formatter returns a formatted value with options`, () => {
|
|
23
|
+
const date = new Date(`1999-01-01T12:00:00Z`);
|
|
24
|
+
const formattedDate = format.date(date, {
|
|
25
|
+
year: `numeric`,
|
|
26
|
+
month: `long`,
|
|
27
|
+
day: `numeric`,
|
|
28
|
+
});
|
|
29
|
+
expect(formattedDate).toBe(`January 1, 1999`);
|
|
30
|
+
});
|
|
31
|
+
it(`time formatter returns a formatted value`, () => {
|
|
32
|
+
const date = new Date(`1912-06-23T02:15:00`);
|
|
33
|
+
const formattedTime = format.time(date);
|
|
34
|
+
expect(formattedTime).toBe(`2:15:00 AM`);
|
|
35
|
+
});
|
|
36
|
+
it(`time formatter returns a formatted value with options`, () => {
|
|
37
|
+
const date = new Date(`2000-12-31T23:45:30`);
|
|
38
|
+
const formattedTime = format.time(date, {
|
|
39
|
+
hour12: false,
|
|
40
|
+
hour: `2-digit`,
|
|
41
|
+
minute: `2-digit`,
|
|
42
|
+
});
|
|
43
|
+
expect(formattedTime).toBe(`23:45`);
|
|
44
|
+
});
|
|
45
|
+
it(`currency formatter returns a formatted value`, () => {
|
|
46
|
+
const formattedCurrency = format.currency(1234.567, `USD`);
|
|
47
|
+
expect(formattedCurrency).toBe(`$1,234.57`);
|
|
48
|
+
});
|
|
49
|
+
it(`currency formatter returns a formatted value with options`, () => {
|
|
50
|
+
const formattedCurrency = format.currency(1234.567, `EUR`, {
|
|
51
|
+
maximumFractionDigits: 1,
|
|
52
|
+
});
|
|
53
|
+
expect(formattedCurrency).toBe(`€1,234.6`);
|
|
54
|
+
});
|
|
55
|
+
it(`percentage formatter returns a formatted value`, () => {
|
|
56
|
+
const formattedPercentage1 = format.percentage(0.1234);
|
|
57
|
+
const formattedPercentage2 = format.percentage(0.1267);
|
|
58
|
+
expect(formattedPercentage1).toBe(`12%`);
|
|
59
|
+
expect(formattedPercentage2).toBe(`13%`);
|
|
60
|
+
});
|
|
61
|
+
it(`percentage formatter returns a formatted value with options`, () => {
|
|
62
|
+
const formattedPercentage = format.percentage(0.1234, {
|
|
63
|
+
minimumFractionDigits: 2,
|
|
64
|
+
});
|
|
65
|
+
expect(formattedPercentage).toBe(`12.34%`);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/const/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/const/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D,eAAO,MAAM,kBAAkB,MAAM,CAAC"}
|
|
@@ -17,7 +17,7 @@ export declare function createI18nContext<Languages extends readonly string[], C
|
|
|
17
17
|
setCurrentLanguage: import("react").Dispatch<import("react").SetStateAction<Languages[number]>>;
|
|
18
18
|
currentLanguage: Languages[number];
|
|
19
19
|
format: {
|
|
20
|
-
number: (value: number, options?: Intl.NumberFormatOptions) => string;
|
|
20
|
+
number: (value: number, options?: Omit<Intl.NumberFormatOptions, `style`>) => string;
|
|
21
21
|
date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
22
22
|
time: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
23
23
|
currency: (value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, `style` | `currency`>) => string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import LRUCache from "lru-cache";
|
|
2
|
+
export declare function potentiallyWarnInvalidCacheState<K extends Intl.NumberFormatOptions | Intl.DateTimeFormatOptions, V extends Intl.NumberFormat | Intl.DateTimeFormat>(value: number | Date, cache: LRUCache<K, V>, options: K): void;
|
|
3
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/createI18nContext/util/cache.ts"],"names":[],"mappings":"AACA,OAAO,QAAQ,MAAM,WAAW,CAAC;AAEjC,wBAAgB,gCAAgC,CAC9C,CAAC,SAAS,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,qBAAqB,EAC/D,CAAC,SAAS,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,cAAc,EACjD,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,QAcxD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IS_DEV } from "../../env";
|
|
2
|
+
export function potentiallyWarnInvalidCacheState(value, cache, options) {
|
|
3
|
+
if (!IS_DEV) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const cachedKeys = Array.from(cache.keys());
|
|
7
|
+
const isEquivalent = cachedKeys.some((key) => shallowEqual(key, options));
|
|
8
|
+
if (isEquivalent) {
|
|
9
|
+
console.warn(`[i18n] ⚠️ Formatting ${value} with options ${JSON.stringify(options)} is not cached, but it should be. This is because you are recreating the options object on every render. To fix this, memoize the options object or define it outside of the component.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function shallowEqual(a, b) {
|
|
13
|
+
const aKeys = Object.keys(a);
|
|
14
|
+
const bKeys = Object.keys(b);
|
|
15
|
+
if (aKeys.length !== bKeys.length) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return aKeys.every((key) => a[key] === b[key]);
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/env/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,SACsD,CAAC"}
|
package/dist/esm/env.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,SACsD,CAAC"}
|
package/dist/esm/env.js
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
const rawNodeEnv =
|
|
2
|
-
process.env &&
|
|
3
|
-
typeof process.env.NODE_ENV === `string`
|
|
4
|
-
? process.env.NODE_ENV
|
|
5
|
-
: undefined;
|
|
1
|
+
const rawNodeEnv = process.env.NODE_ENV;
|
|
6
2
|
export const IS_DEV = typeof __DEV__ !== `undefined` ? __DEV__ : rawNodeEnv === `development`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lru-cache.d.ts","sourceRoot":"","sources":["../../../src/vendor/lru-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,WAAW,CAAC;AAEjC,eAAO,MAAM,QAAQ,EAAE,OAAO,GACwB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-scoped-i18n",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A scoped internationalization (i18n) library for React applications",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/akocan98/react-scoped-i18n"
|
|
11
|
+
"url": "git+https://github.com/akocan98/react-scoped-i18n.git"
|
|
12
12
|
},
|
|
13
13
|
"bugs": "https://github.com/akocan98/react-scoped-i18n/issues",
|
|
14
14
|
"keywords": [
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@testing-library/react": "^16.3.1",
|
|
57
57
|
"@types/jest": "^30.0.0",
|
|
58
|
+
"@types/lru-cache": "^5.1.1",
|
|
58
59
|
"@types/node": "^25.0.3",
|
|
59
60
|
"@types/react": "^19.2.7",
|
|
60
61
|
"@types/react-dom": "^19.2.3",
|
|
@@ -73,5 +74,8 @@
|
|
|
73
74
|
"react-dom": "^19.2.0",
|
|
74
75
|
"ts-jest": "^29.4.5",
|
|
75
76
|
"typescript": "^5.9.2"
|
|
77
|
+
},
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"lru-cache": "^5.1.1"
|
|
76
80
|
}
|
|
77
81
|
}
|