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.
Files changed (51) hide show
  1. package/README.md +85 -69
  2. package/dist/cjs/createI18nContext/api/createFormat.d.ts +1 -1
  3. package/dist/cjs/createI18nContext/api/createFormat.js +91 -15
  4. package/dist/cjs/createI18nContext/api/createFormat.test.js +41 -12
  5. package/dist/cjs/createI18nContext/api/createUseI18n.d.ts +1 -1
  6. package/dist/cjs/createI18nContext/api/createUseI18n.js +3 -1
  7. package/dist/cjs/createI18nContext/api/createUseI18n.test.js +3 -1
  8. package/dist/cjs/createI18nContext/api/useCreateFormat.d.ts +9 -0
  9. package/dist/cjs/createI18nContext/api/useCreateFormat.js +62 -0
  10. package/dist/cjs/createI18nContext/api/useCreateFormat.test.d.ts +1 -0
  11. package/dist/cjs/createI18nContext/api/useCreateFormat.test.js +69 -0
  12. package/dist/cjs/createI18nContext/const/index.d.ts +1 -0
  13. package/dist/cjs/createI18nContext/const/index.js +2 -1
  14. package/dist/cjs/createI18nContext/index.d.ts +1 -1
  15. package/dist/cjs/createI18nContext/util/cache.d.ts +2 -0
  16. package/dist/cjs/createI18nContext/util/cache.js +22 -0
  17. package/dist/cjs/env/index.d.ts +1 -0
  18. package/dist/cjs/env/index.js +5 -0
  19. package/dist/cjs/env.js +1 -5
  20. package/dist/cjs/vendor/lru-cache.d.ts +2 -0
  21. package/dist/cjs/vendor/lru-cache.js +38 -0
  22. package/dist/esm/createI18nContext/api/createFormat.d.ts +1 -1
  23. package/dist/esm/createI18nContext/api/createFormat.d.ts.map +1 -1
  24. package/dist/esm/createI18nContext/api/createFormat.js +88 -15
  25. package/dist/esm/createI18nContext/api/createFormat.test.js +41 -12
  26. package/dist/esm/createI18nContext/api/createUseI18n.d.ts +1 -1
  27. package/dist/esm/createI18nContext/api/createUseI18n.d.ts.map +1 -1
  28. package/dist/esm/createI18nContext/api/createUseI18n.js +3 -1
  29. package/dist/esm/createI18nContext/api/createUseI18n.test.js +3 -1
  30. package/dist/esm/createI18nContext/api/useCreateFormat.d.ts +10 -0
  31. package/dist/esm/createI18nContext/api/useCreateFormat.d.ts.map +1 -0
  32. package/dist/esm/createI18nContext/api/useCreateFormat.js +58 -0
  33. package/dist/esm/createI18nContext/api/useCreateFormat.test.d.ts +2 -0
  34. package/dist/esm/createI18nContext/api/useCreateFormat.test.d.ts.map +1 -0
  35. package/dist/esm/createI18nContext/api/useCreateFormat.test.js +67 -0
  36. package/dist/esm/createI18nContext/const/index.d.ts +1 -0
  37. package/dist/esm/createI18nContext/const/index.d.ts.map +1 -1
  38. package/dist/esm/createI18nContext/const/index.js +1 -0
  39. package/dist/esm/createI18nContext/index.d.ts +1 -1
  40. package/dist/esm/createI18nContext/util/cache.d.ts +3 -0
  41. package/dist/esm/createI18nContext/util/cache.d.ts.map +1 -0
  42. package/dist/esm/createI18nContext/util/cache.js +19 -0
  43. package/dist/esm/env/index.d.ts +2 -0
  44. package/dist/esm/env/index.d.ts.map +1 -0
  45. package/dist/esm/env/index.js +2 -0
  46. package/dist/esm/env.d.ts.map +1 -1
  47. package/dist/esm/env.js +1 -5
  48. package/dist/esm/vendor/lru-cache.d.ts +3 -0
  49. package/dist/esm/vendor/lru-cache.d.ts.map +1 -0
  50. package/dist/esm/vendor/lru-cache.js +2 -0
  51. package/package.json +6 -2
@@ -0,0 +1,2 @@
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;
@@ -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;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IS_DEV = void 0;
4
+ const rawNodeEnv = process.env.NODE_ENV;
5
+ exports.IS_DEV = typeof __DEV__ !== `undefined` ? __DEV__ : rawNodeEnv === `development`;
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 = typeof process !== `undefined` &&
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,2 @@
1
+ import * as LRU from "lru-cache";
2
+ export declare const LRUCache: typeof LRU;
@@ -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":"AAAA,eAAO,MAAM,YAAY,GAAI,KAAK,CAAC,SAAS,SAAS,SAAS,MAAM,EAAE,EAAE,sBAErE;IACD,eAAe,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CACpC;oBAgBmB,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"}
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 new Intl.NumberFormat(currentLanguage, options).format(value);
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 defaultNumberFormatter.format(value);
28
+ return new Intl.NumberFormat(currentLanguage, options).format(value);
18
29
  },
19
30
  date: (value, options) => {
20
- if (options) {
21
- return new Intl.DateTimeFormat(currentLanguage, options).format(value);
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 defaultDateFormatter.format(value);
42
+ return new Intl.DateTimeFormat(currentLanguage, options).format(value);
24
43
  },
25
44
  time: (value, options) => {
26
- if (options) {
27
- return new Intl.DateTimeFormat(currentLanguage, {
28
- ...options,
29
- }).format(value);
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
- return defaultTimeFormatter.format(value);
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 new Intl.NumberFormat(currentLanguage, {
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
- }).format(value);
94
+ }));
46
95
  }
47
- return defaultPercentageFormatter.format(value);
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 formattedNumber = format.number(0.1234, {
12
- style: `percent`,
13
- minimumFractionDigits: 2,
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 formattedPercentage = format.percentage(0.1234);
57
- expect(formattedPercentage).toBe(`12%`);
58
- });
59
- it(`percentage formatter returns a formatted value `, () => {
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;;;;;;;;;;;;;CAmDA,CAAC"}
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({ currentLanguage });
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
- expect(consoleErrorSpy).toHaveBeenCalled(); // eslint-disable-line unused-imports/no-unused-vars
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useCreateFormat.test.d.ts.map
@@ -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,2 +1,3 @@
1
1
  export declare const MISSING_TRANSLATION = "[MISSING TRANSLATION]";
2
+ export declare const LRU_CACHE_MAX_SIZE = 500;
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -1 +1,2 @@
1
1
  export const MISSING_TRANSLATION = `[MISSING TRANSLATION]`;
2
+ export const LRU_CACHE_MAX_SIZE = 500;
@@ -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,2 @@
1
+ export declare const IS_DEV: boolean;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/env/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,SACsD,CAAC"}
@@ -0,0 +1,2 @@
1
+ const rawNodeEnv = process.env.NODE_ENV;
2
+ export const IS_DEV = typeof __DEV__ !== `undefined` ? __DEV__ : rawNodeEnv === `development`;
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/env.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,MAAM,SACsD,CAAC"}
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 = typeof process !== `undefined` &&
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,3 @@
1
+ import * as LRU from "lru-cache";
2
+ export declare const LRUCache: typeof LRU;
3
+ //# sourceMappingURL=lru-cache.d.ts.map
@@ -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"}
@@ -0,0 +1,2 @@
1
+ import * as LRU from "lru-cache";
2
+ export const LRUCache = LRU.LRUCache ?? LRU.default ?? LRU;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-scoped-i18n",
3
- "version": "0.0.3",
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
  }