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
package/README.md CHANGED
@@ -1,112 +1,114 @@
1
1
  # react-scoped-i18n 🌐
2
2
 
3
- `react-scoped-i18n 🌐` is a **fully type-safe** i18n solution for **React**.
3
+ A React i18n library where **translations live next to the components that render them** - no keys, no JSON files, fully type-safe at compile time.
4
4
 
5
- It encourages writing translations right next to the components that use them.
5
+ Works with **React** and **React Native** (_vanilla & Expo_).
6
6
 
7
7
  ---
8
8
 
9
- ## Getting started:
9
+ ## The problem with key-based i18n
10
10
 
11
- ### [Installation & Usage](/docs/usage.md)
11
+ Every other i18n library makes you name things. You write a key in your component, then jump to a JSON file to write the actual string, then come back. Keys get stale. Typos go unnoticed until runtime. Your translation file becomes a graveyard of strings you're not sure are still used.
12
12
 
13
- ### [API](/docs/api.md)
13
+ `react-scoped-i18n` flips this: **translations are just code, written inline, where you need them.**
14
14
 
15
- ###### Note: You can use this with both React and React Native (+ Expo) projects. 🚀
15
+ ```tsx
16
+ const { t } = useI18n();
17
+
18
+ const name = `Oto`;
19
+
20
+ return <Heading>
21
+ {t({
22
+ en: `Welcome back, ${name}!`,
23
+ es: `¡Bienvenido de nuevo, ${name}!`,
24
+ })}
25
+ </Heading>;
26
+ ```
16
27
 
28
+ No keys. No files. `ctrl+f` on any rendered string takes you straight to the component.
17
29
 
18
30
  ---
19
31
 
20
- ## Why `react-scoped-i18n 🌐`?
32
+ ## Type safety that actually catches bugs
21
33
 
22
- ### Key features:
23
- - Very minimal setup with out-of-the-box number & date formatting
24
- - Fully type-safe:
25
- - - missing translations or unsupported languages are compile-time errors
26
- - - return types of `t()` are inferred from translation values
27
- - Utilize the widely supported [Internationalization API (Intl)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) for number, currency, date and time formatting
28
- - Usage is entirely in the runtime; no build-time transforms, no new syntax is required for string interpolation or dynamic translations generated at runtime, everything is plain JS/TS
34
+ Forget to add a translation for a language you support? **TypeScript error.** Reference an unsupported language? **TypeScript error.** At compile time, not in production.
29
35
 
30
- ### The focus is on dev experience:
31
- - Translations are colocated with the components that use them; looking up translations in the codebase always immediately leads to the relevant component code
32
- - No context switching between component code and translation files when developing UI
33
- - No tedious naming of translation keys, as they usually provide little value
34
- - Ditch restrictive translation file formats (JSON, YAML); use the full power of JS/TS
35
- - Runs within React's context system. No additional build steps, changes can be hot-reloaded, language switches reflected immediately
36
+ ```tsx
37
+ return <Heading>
38
+ {t({
39
+ en: `Welcome back, ${name}!`,
40
+ // TS Error: Property 'es' is missing - your app supports Spanish
41
+ })}
42
+ </Heading>;
43
+ ```
36
44
 
37
45
  ---
38
46
 
39
- ## What does it look like?
47
+ ## Getting started
40
48
 
41
- ##### Very Basic Example:
49
+ - **[Installation & Usage](/docs/usage.md)** - 30 second setup
50
+ - **[API Reference](/docs/api.md)** - full reference
42
51
 
43
- ```tsx
44
- import { useI18n } from "@/i18n";
45
- import { Heading, Button } from "@/components";
52
+ ---
46
53
 
47
- export const WelcomeMessage = () => {
48
- const { t, commons } = useI18n();
54
+ ## Key features
55
+
56
+ - **Colocated translations** - live in the component, not a separate file
57
+ - **Compile-time safety** - missing or unsupported languages are TypeScript errors
58
+ - **No build-time transforms** - no Babel plugins, no magic, lives in React runtime (React Context)
59
+ - **No custom syntax** - plain JS template literals, no `{count, plural, ...}` to memorise
60
+ - **Out-of-the-box formatting** - numbers, currencies, dates and times via the native [Intl API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)
61
+ - **Hot-reload friendly** - language switches are reflected immediately
62
+ - **Minimal setup** - takes 30 seconds
63
+
64
+ ---
49
65
 
66
+ ## Examples
67
+
68
+ ### Interpolation
69
+
70
+ ```tsx
71
+ export const WelcomeMessage = () => {
72
+ const { t } = useI18n();
50
73
  const name = `John`;
51
74
 
52
75
  return (
53
- <>
54
- <Heading>
55
- {t({
56
- // all fully type-safe for all configured languages
57
- en: `Welcome to the website, ${name}!`,
58
- es: `¡Bienvenido al sitio web, ${name}!`,
59
- sl: `Dobrodošli na spletno stran, ${name}!`,
60
- })}
61
- </Heading>
62
- <Button>{
63
- // "commons" object is used for commonly used, shared translations
64
- t(commons.continue)
65
- }</Button>
66
- </>
76
+ <Heading>
77
+ {t({
78
+ en: `Welcome to the website, ${name}!`,
79
+ es: `¡Bienvenido al sitio web, ${name}!`,
80
+ sl: `Dobrodošli na spletno stran, ${name}!`,
81
+ })}
82
+ </Heading>
67
83
  );
68
84
  };
69
85
  ```
70
86
 
71
- <details>
72
- <summary>
73
- Number Formatting Basic Example:
74
- </summary>
87
+ ### Number & currency formatting
75
88
 
76
89
  ```tsx
77
- import { useI18n } from "@/i18n";
78
- import { Text } from "@/components";
79
-
80
90
  export const PriceTag = () => {
81
91
  const { t, format } = useI18n();
82
92
 
83
93
  const price = 19.99;
84
94
 
85
- const currency = `USD`;
86
-
87
95
  return (
88
96
  <Text>
89
97
  {t({
90
- en: `The price is ${format.currency(price, currency)}.`,
91
- es: `El precio es ${format.currency(price, currency)}.`,
92
- sl: `Cena je ${format.currency(price, currency)}.`,
98
+ en: `The price is ${format.currency(price, "USD")}.`,
99
+ es: `El precio es ${format.currency(price, "USD")}.`,
100
+ sl: `Cena je ${format.currency(price, "USD")}.`,
93
101
  })}
94
102
  </Text>
95
103
  );
96
104
  };
97
105
  ```
98
- </details>
99
106
 
107
+ ### Pluralization
100
108
 
101
- <details>
102
- <summary>
103
- Pluralization Basic Example:
104
- </summary>
109
+ Full ICU category support (`one`, `two`, `many`, etc.), including a `negative` shorthand and the ability to target specific numbers.
105
110
 
106
111
  ```tsx
107
- import { useI18n } from "@/i18n";
108
- import { Text } from "@/components";
109
-
110
112
  export const Apples = () => {
111
113
  const { tPlural } = useI18n();
112
114
 
@@ -119,7 +121,7 @@ export const Apples = () => {
119
121
  negative: `You are in apple debt...`,
120
122
  one: `You have one apple.`,
121
123
  many: `You have ${count} apples.`,
122
- 42: `You have the perfect number of apples!`, // ‼️ you can target specific numbers
124
+ 42: `You have the perfect number of apples!`,
123
125
  },
124
126
  es: {
125
127
  one: `Tienes una manzana.`,
@@ -127,21 +129,35 @@ export const Apples = () => {
127
129
  },
128
130
  sl: {
129
131
  one: `Imaš eno jabolko.`,
130
- two: `Imaš dve jabolki.`, // ‼️ handling dual form in Slovenian that English and Spanish don't have
132
+ two: `Imaš dve jabolki.`, // handling the Slovenian dual form
131
133
  many: `Imaš ${count} jabolk.`,
132
- }
134
+ },
133
135
  })}
134
136
  </Text>
135
- )
137
+ );
136
138
  };
137
139
  ```
138
- </details>
139
140
 
140
- ----
141
+ ### Shared / common translations
142
+
143
+ ```tsx
144
+ const { t, commons } = useI18n();
145
+
146
+ return <Button>{t(commons.continue)}</Button>;
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Who this is for
152
+
153
+ `react-scoped-i18n` is built for **small teams and indie developers** who:
141
154
 
142
- `react-scoped-i18n 🌐` shines most when devs are the ones adding translations into the app, and when the number of supported languages is small-to-medium sized.
155
+ - Write and maintain their own translations (or work directly with translators in code)
156
+ - Support a small-to-medium number of languages
157
+ - Want TypeScript to enforce correctness, not just assist with autocomplete
158
+ - Prefer reading code over managing JSON files
143
159
 
144
- You can find more in-depth examples in the [Installation & Usage](/docs/usage.md) and the API definitions in [API](/docs/api.md)
160
+ If your workflow involves external translation platforms like Crowdin or Lokalise (or third party translators touching files directly), this approach is likely not for you.
145
161
 
146
162
  ---
147
163
 
@@ -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,6 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.createFormat = void 0;
7
+ const lru_cache_1 = __importDefault(require("lru-cache"));
8
+ const const_1 = require("../const");
9
+ const cache_1 = require("../util/cache");
4
10
  const createFormat = ({ currentLanguage, }) => {
5
11
  const defaultNumberFormatter = new Intl.NumberFormat(currentLanguage);
6
12
  const defaultDateFormatter = new Intl.DateTimeFormat(currentLanguage);
@@ -14,26 +20,68 @@ const createFormat = ({ currentLanguage, }) => {
14
20
  });
15
21
  return {
16
22
  number: (value, options) => {
17
- if (options) {
18
- return new Intl.NumberFormat(currentLanguage, options).format(value);
23
+ if (!options) {
24
+ return defaultNumberFormatter.format(value);
25
+ }
26
+ if (!caches.number.has(options)) {
27
+ (0, cache_1.potentiallyWarnInvalidCacheState)(value, caches.number, options);
28
+ caches.number.set(options, new Intl.NumberFormat(currentLanguage, options));
19
29
  }
20
- return defaultNumberFormatter.format(value);
30
+ const cachedFormatter = caches.number.get(options);
31
+ if (cachedFormatter) {
32
+ return cachedFormatter.format(value);
33
+ }
34
+ return new Intl.NumberFormat(currentLanguage, options).format(value);
21
35
  },
22
36
  date: (value, options) => {
23
- if (options) {
24
- return new Intl.DateTimeFormat(currentLanguage, options).format(value);
37
+ if (!options) {
38
+ return defaultDateFormatter.format(value);
39
+ }
40
+ if (!caches.date.has(options)) {
41
+ (0, cache_1.potentiallyWarnInvalidCacheState)(value, caches.date, options);
42
+ caches.date.set(options, new Intl.DateTimeFormat(currentLanguage, options));
25
43
  }
26
- return defaultDateFormatter.format(value);
44
+ const cachedFormatter = caches.date.get(options);
45
+ if (cachedFormatter) {
46
+ return cachedFormatter.format(value);
47
+ }
48
+ return new Intl.DateTimeFormat(currentLanguage, options).format(value);
27
49
  },
28
50
  time: (value, options) => {
29
- if (options) {
30
- return new Intl.DateTimeFormat(currentLanguage, {
31
- ...options,
32
- }).format(value);
51
+ if (!options) {
52
+ return defaultTimeFormatter.format(value);
33
53
  }
34
- return defaultTimeFormatter.format(value);
54
+ if (!caches.time.has(options)) {
55
+ (0, cache_1.potentiallyWarnInvalidCacheState)(value, caches.time, options);
56
+ caches.time.set(options, new Intl.DateTimeFormat(currentLanguage, options));
57
+ }
58
+ const cachedFormatter = caches.time.get(options);
59
+ if (cachedFormatter) {
60
+ return cachedFormatter.format(value);
61
+ }
62
+ return new Intl.DateTimeFormat(currentLanguage, {
63
+ ...options,
64
+ }).format(value);
35
65
  },
36
66
  currency: (value, currency, options) => {
67
+ if (options) {
68
+ const cacheKey = {
69
+ ...options,
70
+ currency,
71
+ };
72
+ if (!caches.currency.has(cacheKey)) {
73
+ (0, cache_1.potentiallyWarnInvalidCacheState)(value, caches.currency, cacheKey);
74
+ caches.currency.set(cacheKey, new Intl.NumberFormat(currentLanguage, {
75
+ style: `currency`,
76
+ currency,
77
+ ...options,
78
+ }));
79
+ }
80
+ const cachedFormatter = caches.currency.get(cacheKey);
81
+ if (cachedFormatter) {
82
+ return cachedFormatter.format(value);
83
+ }
84
+ }
37
85
  return new Intl.NumberFormat(currentLanguage, {
38
86
  style: `currency`,
39
87
  currency,
@@ -41,14 +89,42 @@ const createFormat = ({ currentLanguage, }) => {
41
89
  }).format(value);
42
90
  },
43
91
  percentage: (value, options) => {
44
- if (options) {
45
- return new Intl.NumberFormat(currentLanguage, {
92
+ if (!options) {
93
+ return defaultPercentageFormatter.format(value);
94
+ }
95
+ if (!caches.percentage.has(options)) {
96
+ (0, cache_1.potentiallyWarnInvalidCacheState)(value, caches.number, options);
97
+ caches.percentage.set(options, new Intl.NumberFormat(currentLanguage, {
46
98
  style: `percent`,
47
99
  ...options,
48
- }).format(value);
100
+ }));
49
101
  }
50
- return defaultPercentageFormatter.format(value);
102
+ const cachedFormatter = caches.percentage.get(options);
103
+ if (cachedFormatter) {
104
+ return cachedFormatter.format(value);
105
+ }
106
+ return new Intl.NumberFormat(currentLanguage, {
107
+ style: `percent`,
108
+ ...options,
109
+ }).format(value);
51
110
  },
52
111
  };
53
112
  };
54
113
  exports.createFormat = createFormat;
114
+ const caches = {
115
+ number: new lru_cache_1.default({
116
+ max: const_1.LRU_CACHE_MAX_SIZE,
117
+ }),
118
+ date: new lru_cache_1.default({
119
+ max: const_1.LRU_CACHE_MAX_SIZE,
120
+ }),
121
+ time: new lru_cache_1.default({
122
+ max: const_1.LRU_CACHE_MAX_SIZE,
123
+ }),
124
+ currency: new lru_cache_1.default({
125
+ max: const_1.LRU_CACHE_MAX_SIZE,
126
+ }),
127
+ percentage: new lru_cache_1.default({
128
+ max: const_1.LRU_CACHE_MAX_SIZE,
129
+ }),
130
+ };
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const createFormat_1 = require("./createFormat");
4
4
  describe(`createFormat`, () => {
5
+ const numberFormatOptions = {
6
+ minimumFractionDigits: 4,
7
+ };
5
8
  const format = (0, createFormat_1.createFormat)({
6
9
  currentLanguage: `en-US`,
7
10
  });
@@ -9,12 +12,11 @@ describe(`createFormat`, () => {
9
12
  const formattedNumber = format.number(1234567.89);
10
13
  expect(formattedNumber).toBe(`1,234,567.89`);
11
14
  });
12
- it(`number formatter returns a formatted value with options`, () => {
13
- const formattedNumber = format.number(0.1234, {
14
- style: `percent`,
15
- minimumFractionDigits: 2,
16
- });
17
- expect(formattedNumber).toBe(`12.34%`);
15
+ it(`number formatter returns a formatted value with options (with caching enabled)`, () => {
16
+ const formattedNumber1 = format.number(1234567.89, numberFormatOptions);
17
+ const formattedNumber2 = format.number(1234567.89, numberFormatOptions);
18
+ expect(formattedNumber1).toBe(`1,234,567.8900`);
19
+ expect(formattedNumber2).toBe(`1,234,567.8900`);
18
20
  });
19
21
  it(`date formatter returns a formatted value`, () => {
20
22
  const date = new Date(`2024-01-01T12:00:00Z`);
@@ -55,12 +57,10 @@ describe(`createFormat`, () => {
55
57
  expect(formattedCurrency).toBe(`€1,234.6`);
56
58
  });
57
59
  it(`percentage formatter returns a formatted value`, () => {
58
- const formattedPercentage = format.percentage(0.1234);
59
- expect(formattedPercentage).toBe(`12%`);
60
- });
61
- it(`percentage formatter returns a formatted value `, () => {
62
- const formattedPercentage = format.percentage(0.1234);
63
- expect(formattedPercentage).toBe(`12%`);
60
+ const formattedPercentage1 = format.percentage(0.1234);
61
+ const formattedPercentage2 = format.percentage(0.1267);
62
+ expect(formattedPercentage1).toBe(`12%`);
63
+ expect(formattedPercentage2).toBe(`13%`);
64
64
  });
65
65
  it(`percentage formatter returns a formatted value with options`, () => {
66
66
  const formattedPercentage = format.percentage(0.1234, {
@@ -68,4 +68,33 @@ describe(`createFormat`, () => {
68
68
  });
69
69
  expect(formattedPercentage).toBe(`12.34%`);
70
70
  });
71
+ it(`Error: Number options are not cached`, () => {
72
+ const warnSpy = jest.spyOn(console, `warn`);
73
+ const formattedNumber1 = format.number(1234567.89, {
74
+ minimumFractionDigits: 5,
75
+ });
76
+ const formattedNumber2 = format.number(1234567.89, {
77
+ minimumFractionDigits: 5,
78
+ });
79
+ expect(formattedNumber1).toBe(`1,234,567.89000`);
80
+ expect(formattedNumber2).toBe(`1,234,567.89000`);
81
+ 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.`);
82
+ });
83
+ it(`Error: Date options are not cached`, () => {
84
+ const warnSpy = jest.spyOn(console, `warn`);
85
+ const date = new Date(`2026-02-03T12:00:00Z`);
86
+ const formattedDate1 = format.date(date, {
87
+ year: `numeric`,
88
+ month: `long`,
89
+ day: `numeric`,
90
+ });
91
+ const formattedDate2 = format.date(date, {
92
+ year: `numeric`,
93
+ month: `long`,
94
+ day: `numeric`,
95
+ });
96
+ expect(formattedDate1).toBe(`February 3, 2026`);
97
+ expect(formattedDate2).toBe(`February 3, 2026`);
98
+ 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.`);
99
+ });
71
100
  });
@@ -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;
@@ -30,7 +30,9 @@ const createUseI18n = ({ I18nContext, languages, fallbackLanguage, commons, }) =
30
30
  });
31
31
  }, [currentLanguage]);
32
32
  const format = (0, react_1.useMemo)(() => {
33
- return (0, createFormat_1.createFormat)({ currentLanguage });
33
+ return (0, createFormat_1.createFormat)({
34
+ currentLanguage,
35
+ });
34
36
  }, [currentLanguage]);
35
37
  const _commons = (0, react_1.useMemo)(() => {
36
38
  return (0, createCommons_1.createCommons)({
@@ -16,9 +16,11 @@ describe(`createUseI18n`, () => {
16
16
  .mockImplementation(() => { });
17
17
  try {
18
18
  useI18n();
19
+ // eslint-disable-next-line unused-imports/no-unused-vars
19
20
  }
20
21
  catch (e) {
21
- expect(consoleErrorSpy).toHaveBeenCalled(); // eslint-disable-line unused-imports/no-unused-vars
22
+ // eslint-disable-line unused-imports/no-unused-vars
23
+ expect(consoleErrorSpy).toHaveBeenCalled();
22
24
  }
23
25
  consoleErrorSpy.mockRestore();
24
26
  });
@@ -0,0 +1,9 @@
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
+ };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCreateFormat = void 0;
4
+ const react_1 = require("react");
5
+ const useCreateFormat = ({ currentLanguage, }) => {
6
+ const defaultNumberFormatter = new Intl.NumberFormat(currentLanguage);
7
+ const defaultDateFormatter = new Intl.DateTimeFormat(currentLanguage);
8
+ const defaultTimeFormatter = new Intl.DateTimeFormat(currentLanguage, {
9
+ hour: `numeric`,
10
+ minute: `numeric`,
11
+ second: `numeric`,
12
+ });
13
+ const defaultPercentageFormatter = new Intl.NumberFormat(currentLanguage, {
14
+ style: `percent`,
15
+ });
16
+ const defaultCurrencyFormatter = (0, react_1.useMemo)(() => {
17
+ return new Intl.NumberFormat(currentLanguage, {
18
+ style: `currency`,
19
+ currency: `USD`,
20
+ });
21
+ }, [currentLanguage]);
22
+ console.log(defaultCurrencyFormatter.resolvedOptions().currency);
23
+ return {
24
+ number: (value, options) => {
25
+ if (options) {
26
+ return new Intl.NumberFormat(currentLanguage, options).format(value);
27
+ }
28
+ return defaultNumberFormatter.format(value);
29
+ },
30
+ date: (value, options) => {
31
+ if (options) {
32
+ return new Intl.DateTimeFormat(currentLanguage, options).format(value);
33
+ }
34
+ return defaultDateFormatter.format(value);
35
+ },
36
+ time: (value, options) => {
37
+ if (options) {
38
+ return new Intl.DateTimeFormat(currentLanguage, {
39
+ ...options,
40
+ }).format(value);
41
+ }
42
+ return defaultTimeFormatter.format(value);
43
+ },
44
+ currency: (value, currency, options) => {
45
+ return new Intl.NumberFormat(currentLanguage, {
46
+ style: `currency`,
47
+ currency,
48
+ ...options,
49
+ }).format(value);
50
+ },
51
+ percentage: (value, options) => {
52
+ if (options) {
53
+ return new Intl.NumberFormat(currentLanguage, {
54
+ style: `percent`,
55
+ ...options,
56
+ }).format(value);
57
+ }
58
+ return defaultPercentageFormatter.format(value);
59
+ },
60
+ };
61
+ };
62
+ exports.useCreateFormat = useCreateFormat;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const useCreateFormat_1 = require("./useCreateFormat");
4
+ describe(`createFormat`, () => {
5
+ const format = (0, useCreateFormat_1.useCreateFormat)({
6
+ currentLanguage: `en-US`,
7
+ });
8
+ it(`number formatter returns a formatted value`, () => {
9
+ const formattedNumber = format.number(1234567.89);
10
+ expect(formattedNumber).toBe(`1,234,567.89`);
11
+ });
12
+ it(`number formatter returns a formatted value with options`, () => {
13
+ const formattedNumber = format.number(0.1234, {
14
+ style: `percent`,
15
+ minimumFractionDigits: 2,
16
+ });
17
+ expect(formattedNumber).toBe(`12.34%`);
18
+ });
19
+ it(`date formatter returns a formatted value`, () => {
20
+ const date = new Date(`2024-01-01T12:00:00Z`);
21
+ const formattedDate = format.date(date);
22
+ expect(formattedDate).toBe(`1/1/2024`);
23
+ });
24
+ it(`date formatter returns a formatted value with options`, () => {
25
+ const date = new Date(`1999-01-01T12:00:00Z`);
26
+ const formattedDate = format.date(date, {
27
+ year: `numeric`,
28
+ month: `long`,
29
+ day: `numeric`,
30
+ });
31
+ expect(formattedDate).toBe(`January 1, 1999`);
32
+ });
33
+ it(`time formatter returns a formatted value`, () => {
34
+ const date = new Date(`1912-06-23T02:15:00`);
35
+ const formattedTime = format.time(date);
36
+ expect(formattedTime).toBe(`2:15:00 AM`);
37
+ });
38
+ it(`time formatter returns a formatted value with options`, () => {
39
+ const date = new Date(`2000-12-31T23:45:30`);
40
+ const formattedTime = format.time(date, {
41
+ hour12: false,
42
+ hour: `2-digit`,
43
+ minute: `2-digit`,
44
+ });
45
+ expect(formattedTime).toBe(`23:45`);
46
+ });
47
+ it(`currency formatter returns a formatted value`, () => {
48
+ const formattedCurrency = format.currency(1234.567, `USD`);
49
+ expect(formattedCurrency).toBe(`$1,234.57`);
50
+ });
51
+ it(`currency formatter returns a formatted value with options`, () => {
52
+ const formattedCurrency = format.currency(1234.567, `EUR`, {
53
+ maximumFractionDigits: 1,
54
+ });
55
+ expect(formattedCurrency).toBe(`€1,234.6`);
56
+ });
57
+ it(`percentage formatter returns a formatted value`, () => {
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
+ });
63
+ it(`percentage formatter returns a formatted value with options`, () => {
64
+ const formattedPercentage = format.percentage(0.1234, {
65
+ minimumFractionDigits: 2,
66
+ });
67
+ expect(formattedPercentage).toBe(`12.34%`);
68
+ });
69
+ });
@@ -1 +1,2 @@
1
1
  export declare const MISSING_TRANSLATION = "[MISSING TRANSLATION]";
2
+ export declare const LRU_CACHE_MAX_SIZE = 500;
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MISSING_TRANSLATION = void 0;
3
+ exports.LRU_CACHE_MAX_SIZE = exports.MISSING_TRANSLATION = void 0;
4
4
  exports.MISSING_TRANSLATION = `[MISSING TRANSLATION]`;
5
+ exports.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;