localize-react 1.6.0 → 2.0.0-next.0
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 +141 -240
- package/dist/index.cjs +133 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +138 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.mjs +127 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +143 -30
- package/.babelrc +0 -20
- package/dist/localize-react.js +0 -1
- package/index.d.ts +0 -39
- package/rollup.config.js +0 -24
package/README.md
CHANGED
|
@@ -1,307 +1,208 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<a href="https://yankouskia.github.io/localize-react">
|
|
4
|
+
<img src="./website/static/img/logo.svg" alt="localize-react" width="96" height="96" />
|
|
5
|
+
</a>
|
|
4
6
|
|
|
5
7
|
# localize-react
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
### **React i18n, without the weight.**
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Tiny, type-safe React i18n built on Context and hooks.<br/>
|
|
12
|
+
**< 1 kB brotli · 0 runtime deps · dual ESM + CJS · React 19 ready.**
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
[**Docs**](https://yankouskia.github.io/localize-react) ·
|
|
15
|
+
[**Quickstart**](https://yankouskia.github.io/localize-react/docs/quickstart) ·
|
|
16
|
+
[**API**](https://yankouskia.github.io/localize-react/docs/api) ·
|
|
17
|
+
[**Recipes**](https://yankouskia.github.io/localize-react/docs/recipes) ·
|
|
18
|
+
[**Migration v1 → v2**](https://yankouskia.github.io/localize-react/docs/migration-v2)
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
[](https://www.npmjs.com/package/localize-react)
|
|
21
|
+
[](https://www.npmjs.com/package/localize-react)
|
|
22
|
+
[](https://github.com/yankouskia/localize-react/actions/workflows/ci.yml)
|
|
23
|
+
[](https://codecov.io/gh/yankouskia/localize-react)
|
|
24
|
+
[](https://bundlejs.com/?q=localize-react)
|
|
25
|
+
[](https://github.com/yankouskia/localize-react/blob/master/src/types.ts)
|
|
26
|
+
[](LICENSE)
|
|
14
27
|
|
|
28
|
+
</div>
|
|
15
29
|
|
|
16
|
-
|
|
30
|
+
---
|
|
17
31
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
```sh
|
|
21
|
-
npm install localize-react --save
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
yarn:
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
yarn add localize-react
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## API
|
|
31
|
-
|
|
32
|
-
### Provider & Consumer
|
|
33
|
-
|
|
34
|
-
`LocalizationProvider` is used to provide data for translations into React context. The root application component should be wrapped into `LocalizationProvider`. Component has the next props:
|
|
35
|
-
- `children` - children to render
|
|
36
|
-
- `locale` - [OPTIONAL] locale to be used for translations. If locale is not specified regular translations object will be used as map of `{ key: translations }`
|
|
37
|
-
- `translations` - object with translations
|
|
38
|
-
- `disableCache` - boolean variable to disable cache on runtime (`false` by default). Setting this to `true` could affect runtime performance, but could be useful for development.
|
|
39
|
-
|
|
40
|
-
Example:
|
|
41
|
-
|
|
42
|
-
```js
|
|
43
|
-
import React from 'react';
|
|
44
|
-
import ReactDOM from 'react-dom';
|
|
45
|
-
import { LocalizationConsumer, LocalizationProvider } from 'localize-react';
|
|
46
|
-
|
|
47
|
-
const TRANSLATIONS = {
|
|
48
|
-
en: {
|
|
49
|
-
name: 'Alex',
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const App = () => (
|
|
54
|
-
<LocalizationProvider
|
|
55
|
-
disableCache
|
|
56
|
-
locale="en"
|
|
57
|
-
translations={TRANSLATIONS}
|
|
58
|
-
>
|
|
59
|
-
<LocalizationConsumer>
|
|
60
|
-
{({ translate }) => translate('name')}
|
|
61
|
-
</LocalizationConsumer>
|
|
62
|
-
</LocalizationProvider>
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
ReactDOM.render(<App />, node); // "Alex" will be rendered
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Message
|
|
69
|
-
|
|
70
|
-
`Message` component is used to provide translated message by specified key, which should be passed via props. Component has the next props:
|
|
71
|
-
- `descriptor` - translation key (descriptor)
|
|
72
|
-
- `defaultMessage` - message to be used in case translation is not provided (values object are applied to default message as well)
|
|
73
|
-
- `values` - possible values to use with template string (Template should be passed in next format: `Hello {{name}}`)
|
|
74
|
-
|
|
75
|
-
Example:
|
|
76
|
-
|
|
77
|
-
```js
|
|
78
|
-
import React from 'react';
|
|
79
|
-
import ReactDOM from 'react-dom';
|
|
80
|
-
import { LocalizationProvider, Message } from 'localize-react';
|
|
81
|
-
|
|
82
|
-
const TRANSLATIONS = {
|
|
83
|
-
en: {
|
|
84
|
-
name: 'Alex',
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const App = () => (
|
|
89
|
-
<LocalizationProvider
|
|
90
|
-
locale="en"
|
|
91
|
-
translations={TRANSLATIONS}
|
|
92
|
-
>
|
|
93
|
-
<Message descriptor="name" />
|
|
94
|
-
</LocalizationProvider>
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
ReactDOM.render(<App />, node); // "Alex" will be rendered
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
To use with templates:
|
|
101
|
-
|
|
102
|
-
```js
|
|
103
|
-
import React from 'react';
|
|
104
|
-
import ReactDOM from 'react-dom';
|
|
105
|
-
import { LocalizationProvider, Message } from 'localize-react';
|
|
32
|
+
```tsx
|
|
33
|
+
import { LocalizationProvider, useLocalize } from 'localize-react';
|
|
106
34
|
|
|
107
|
-
const
|
|
108
|
-
en: {
|
|
109
|
-
|
|
110
|
-
},
|
|
35
|
+
const translations = {
|
|
36
|
+
en: { hello: 'Hi {{name}}!' },
|
|
37
|
+
es: { hello: '¡Hola {{name}}!' },
|
|
38
|
+
ja: { hello: '{{name}}さん、こんにちは!' },
|
|
111
39
|
};
|
|
112
40
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
>
|
|
118
|
-
<Message descriptor="name" values={{ name: 'Alex' }} />
|
|
119
|
-
</LocalizationProvider>
|
|
120
|
-
);
|
|
41
|
+
function Greeting() {
|
|
42
|
+
const { translate } = useLocalize();
|
|
43
|
+
return <h1>{translate('hello', { name: 'Alex' })}</h1>;
|
|
44
|
+
}
|
|
121
45
|
|
|
122
|
-
|
|
46
|
+
export default function App() {
|
|
47
|
+
return (
|
|
48
|
+
<LocalizationProvider locale="en" translations={translations}>
|
|
49
|
+
<Greeting />
|
|
50
|
+
</LocalizationProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
123
53
|
```
|
|
124
54
|
|
|
125
|
-
|
|
55
|
+
That's the whole API. Three exports, no plugins, no extraction toolchain. Ship it.
|
|
126
56
|
|
|
127
|
-
|
|
128
|
-
import React from 'react';
|
|
129
|
-
import ReactDOM from 'react-dom';
|
|
130
|
-
import { LocalizationProvider, Message } from 'localize-react';
|
|
57
|
+
---
|
|
131
58
|
|
|
132
|
-
|
|
133
|
-
en: {},
|
|
134
|
-
};
|
|
59
|
+
## ✨ Why?
|
|
135
60
|
|
|
136
|
-
|
|
137
|
-
<LocalizationProvider
|
|
138
|
-
locale="en"
|
|
139
|
-
translations={TRANSLATIONS}
|
|
140
|
-
>
|
|
141
|
-
<Message
|
|
142
|
-
descriptor="name"
|
|
143
|
-
defaultMessage="Hello, {{name}}!"
|
|
144
|
-
values={{ name: 'Alex' }}
|
|
145
|
-
/>
|
|
146
|
-
</LocalizationProvider>
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
ReactDOM.render(<App />, node); // "Alex" will be rendered
|
|
150
|
-
```
|
|
61
|
+
Most React i18n libraries are 30–80 kB and bring opinions about plural rules, ICU MessageFormat, async loading, and TMS workflows. **`localize-react` is the smallest thing that works.**
|
|
151
62
|
|
|
152
|
-
|
|
63
|
+
It does exactly what a frontend most often needs:
|
|
153
64
|
|
|
154
|
-
|
|
65
|
+
- A nested translations tree, keyed by locale.
|
|
66
|
+
- Dot-path lookups (`'cart.summary'`).
|
|
67
|
+
- `{{name}}`-style interpolation.
|
|
68
|
+
- A graceful fallback when keys are missing.
|
|
155
69
|
|
|
156
|
-
|
|
70
|
+
For everything else (plurals, currency, dates) — reach for [the platform](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl): `Intl.PluralRules`, `Intl.NumberFormat`, `Intl.DateTimeFormat`. Free, fast, already in the browser. See the [Intl formatters recipe](https://yankouskia.github.io/localize-react/docs/recipes/intl-formatters).
|
|
157
71
|
|
|
158
|
-
|
|
72
|
+
## 📦 Specs
|
|
159
73
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
74
|
+
| Property | Value |
|
|
75
|
+
| -------------------- | ------------------------------------------------------ |
|
|
76
|
+
| Bundle (brotli) | **916 B** ESM · 989 B CJS |
|
|
77
|
+
| Runtime dependencies | **0** |
|
|
78
|
+
| Source | Strict **TypeScript 6** |
|
|
79
|
+
| Module formats | ESM + CJS with proper `exports`/`types` conditions |
|
|
80
|
+
| Tree-shaking | `sideEffects: false` |
|
|
81
|
+
| Peer range | React `>= 16.8 < 20` (tested in CI through React 19) |
|
|
82
|
+
| Node | `>= 20.19` (CI on 20, 22, 24 × Linux/macOS/Windows) |
|
|
83
|
+
| Test coverage | **100 %** statements · 100 % functions · 98 % branches |
|
|
84
|
+
| Type-checked exports | Validated by `publint` + `@arethetypeswrong/cli` in CI |
|
|
163
85
|
|
|
164
|
-
|
|
86
|
+
## 🚀 Install
|
|
165
87
|
|
|
166
|
-
```
|
|
167
|
-
|
|
88
|
+
```sh
|
|
89
|
+
npm install localize-react
|
|
90
|
+
# or: pnpm add localize-react · yarn add localize-react · bun add localize-react
|
|
168
91
|
```
|
|
169
92
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Keep in mind, that hooks are not supported in class components!
|
|
93
|
+
## ⚡️ Quickstart
|
|
173
94
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
```js
|
|
177
|
-
import React from 'react';
|
|
178
|
-
import ReactDOM from 'react-dom';
|
|
179
|
-
import { LocalizationProvider, useLocalize } from 'localize-react';
|
|
95
|
+
### 1. Define translations
|
|
180
96
|
|
|
181
|
-
|
|
97
|
+
```ts
|
|
98
|
+
export const translations = {
|
|
182
99
|
en: {
|
|
183
|
-
|
|
100
|
+
greeting: { hello: 'Hi {{name}}!' },
|
|
101
|
+
cart: { summary: '{{count}} items, {{total}} total' },
|
|
184
102
|
},
|
|
185
|
-
|
|
103
|
+
es: {
|
|
104
|
+
greeting: { hello: '¡Hola {{name}}!' },
|
|
105
|
+
cart: { summary: '{{count}} artículos, {{total}} total' },
|
|
106
|
+
},
|
|
107
|
+
} as const;
|
|
108
|
+
```
|
|
186
109
|
|
|
187
|
-
|
|
188
|
-
const { translate } = useLocalize();
|
|
110
|
+
### 2. Mount the provider
|
|
189
111
|
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const App = () => {
|
|
112
|
+
```tsx
|
|
113
|
+
import { LocalizationProvider } from 'localize-react';
|
|
114
|
+
import { translations } from './i18n/translations';
|
|
194
115
|
|
|
116
|
+
export function App() {
|
|
195
117
|
return (
|
|
196
|
-
<LocalizationProvider
|
|
197
|
-
|
|
198
|
-
translations={TRANSLATIONS}
|
|
199
|
-
>
|
|
200
|
-
<Test />
|
|
118
|
+
<LocalizationProvider locale="en" translations={translations}>
|
|
119
|
+
<Shell />
|
|
201
120
|
</LocalizationProvider>
|
|
202
121
|
);
|
|
203
122
|
}
|
|
204
|
-
|
|
205
|
-
ReactDOM.render(<App />, node); // "Alex" will be rendered
|
|
206
123
|
```
|
|
207
124
|
|
|
208
|
-
###
|
|
125
|
+
### 3. Translate, two ways
|
|
209
126
|
|
|
210
|
-
|
|
127
|
+
```tsx
|
|
128
|
+
import { Message, useLocalize } from 'localize-react';
|
|
211
129
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const TRANSLATIONS = {
|
|
217
|
-
en: {
|
|
218
|
-
name: 'Alex',
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
class Translation extends React.PureComponent {
|
|
224
|
-
render() {
|
|
225
|
-
return (
|
|
226
|
-
<span>
|
|
227
|
-
{this.context.translate('name')}
|
|
228
|
-
</span>
|
|
229
|
-
)
|
|
230
|
-
}
|
|
130
|
+
// Hook
|
|
131
|
+
function Cart() {
|
|
132
|
+
const { translate } = useLocalize();
|
|
133
|
+
return <p>{translate('cart.summary', { count: 3, total: '$42.00' })}</p>;
|
|
231
134
|
}
|
|
232
135
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const App = () => {
|
|
136
|
+
// Component
|
|
137
|
+
function CartHeader() {
|
|
236
138
|
return (
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
>
|
|
241
|
-
<Translation />
|
|
242
|
-
</LocalizationProvider>
|
|
139
|
+
<h1>
|
|
140
|
+
<Message descriptor="greeting.hello" values={{ name: 'Alex' }} />
|
|
141
|
+
</h1>
|
|
243
142
|
);
|
|
244
143
|
}
|
|
245
|
-
|
|
246
|
-
ReactDOM.render(<App />, node); // "Alex" will be rendered
|
|
247
144
|
```
|
|
248
145
|
|
|
249
|
-
|
|
250
|
-
Locale could be passed in short or long option.
|
|
251
|
-
|
|
146
|
+
That's the whole story. Full docs at **[yankouskia.github.io/localize-react](https://yankouskia.github.io/localize-react)**.
|
|
252
147
|
|
|
253
|
-
|
|
148
|
+
## 🧠 Concept in one screen
|
|
254
149
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
150
|
+
| Operation | API |
|
|
151
|
+
| ------------------------ | -------------------------------------------------------- |
|
|
152
|
+
| Mount translations | `<LocalizationProvider locale translations>` |
|
|
153
|
+
| Translate (hook) | `useLocalize().translate(descriptor, values?, default?)` |
|
|
154
|
+
| Translate (component) | `<Message descriptor values? defaultMessage? />` |
|
|
155
|
+
| Switch locale at runtime | Re-render with a new `locale` prop |
|
|
156
|
+
| Missing key | Renders `defaultMessage ?? descriptor` (never throws) |
|
|
157
|
+
| Nested lookup | `translate('a.b.c')` walks the tree |
|
|
158
|
+
| Interpolation | `{{token}}` — literal replacement, safe with regex chars |
|
|
159
|
+
| Locale normalization | `En-US` → `en_us` → `en` |
|
|
261
160
|
|
|
262
|
-
|
|
263
|
-
Translations could be passed in any object form (plain or with deep properties)
|
|
161
|
+
## 📚 Recipes
|
|
264
162
|
|
|
265
|
-
|
|
163
|
+
Real-world patterns, fully documented on the site:
|
|
266
164
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
```
|
|
165
|
+
- [Switching locales (URL / cookie / localStorage)](https://yankouskia.github.io/localize-react/docs/recipes/switching-locales)
|
|
166
|
+
- [Lazy-loading translation chunks with `React.use()`](https://yankouskia.github.io/localize-react/docs/recipes/lazy-loading)
|
|
167
|
+
- [Next.js (App Router) — `[locale]` segments + middleware](https://yankouskia.github.io/localize-react/docs/recipes/nextjs)
|
|
168
|
+
- [Vite + React Router — `import.meta.glob`](https://yankouskia.github.io/localize-react/docs/recipes/vite)
|
|
169
|
+
- [Testing — RTL render helper, descriptor coverage](https://yankouskia.github.io/localize-react/docs/recipes/testing)
|
|
170
|
+
- [Intl formatters — plurals, currency, dates, lists](https://yankouskia.github.io/localize-react/docs/recipes/intl-formatters)
|
|
278
171
|
|
|
279
|
-
|
|
172
|
+
## 🥊 How it compares
|
|
280
173
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
174
|
+
| | **localize-react** | react-i18next | react-intl | lingui |
|
|
175
|
+
| -------------------- | ------------------ | ------------- | ---------- | --------- |
|
|
176
|
+
| Bundle (brotli) | **< 1 kB** | ~17 kB | ~38 kB | ~9 kB |
|
|
177
|
+
| Runtime deps | **0** | several | several | one macro |
|
|
178
|
+
| Pluralization (CLDR) | Use `Intl` | ✅ | ✅ (ICU) | ✅ (ICU) |
|
|
179
|
+
| Number / date format | Use `Intl` | Optional | ✅ | ✅ |
|
|
180
|
+
| ICU MessageFormat | ❌ | ✅ | ✅ | ✅ |
|
|
181
|
+
| Lazy locale loading | DIY | ✅ | ✅ | ✅ |
|
|
182
|
+
| Auto extraction | ❌ | ✅ | CLI | CLI |
|
|
183
|
+
| TypeScript-first | ✅ | ✅ | ✅ | ✅ |
|
|
184
|
+
| Learning curve | **Tiny** | Medium | Medium | Medium |
|
|
286
185
|
|
|
287
|
-
|
|
186
|
+
Use `localize-react` when you want a hook + a tag. Reach for the others when CLDR plurals, ICU MessageFormat, or a TMS workflow matter — they're all great at what they do.
|
|
288
187
|
|
|
289
|
-
|
|
188
|
+
## 🛡 Production-ready
|
|
290
189
|
|
|
291
|
-
|
|
190
|
+
- **Types ship inside the package** — no `@types/localize-react` to chase.
|
|
191
|
+
- **Provenance attestation** on every published version (npm OIDC trusted publishing).
|
|
192
|
+
- **CodeQL** runs on every PR; CI matrix exercises Node 20/22/24 × Linux/macOS/Windows.
|
|
193
|
+
- **Size budget enforced** — < 2 kB ESM, < 2.5 kB CJS, checked on every PR with [`size-limit`](https://github.com/ai/size-limit).
|
|
194
|
+
- **No dynamic require, no eval, no regex from user input** — interpolation is literal `replaceAll`.
|
|
292
195
|
|
|
293
|
-
|
|
196
|
+
## 📖 v1 → v2
|
|
294
197
|
|
|
295
|
-
|
|
198
|
+
The runtime API is unchanged. v2 modernizes the toolchain (strict TS 6, dual ESM+CJS, React 19 peer, GitHub Actions + Changesets). One soft TypeScript regression in `exactOptionalPropertyTypes` mode — see the [migration guide](https://yankouskia.github.io/localize-react/docs/migration-v2).
|
|
296
199
|
|
|
297
|
-
|
|
200
|
+
## 🤝 Contributing
|
|
298
201
|
|
|
299
|
-
`
|
|
202
|
+
PRs welcome. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the setup + release flow. Security reports: please open a private security advisory rather than a public issue.
|
|
300
203
|
|
|
301
|
-
|
|
302
|
-
yarn test
|
|
303
|
-
```
|
|
204
|
+
If you'd like to support the project, [sponsoring](https://github.com/sponsors/yankouskia) helps a lot.
|
|
304
205
|
|
|
305
|
-
|
|
206
|
+
## 📄 License
|
|
306
207
|
|
|
307
|
-
|
|
208
|
+
[MIT](./LICENSE) © Aliaksandr Yankouski
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/Provider.tsx
|
|
7
|
+
|
|
8
|
+
// src/helpers.ts
|
|
9
|
+
var NO_TRANSLATION_WARNING_MESSAGE = "[LOCALIZE-REACT]: There are no translations for specified locale";
|
|
10
|
+
var NO_TEMPLATE_VALUE_MESSAGE = "[LOCALIZE-REACT] Looks like template is being used, but no value passed for ";
|
|
11
|
+
var PARSE_TEMPLATE_REGEXP = /\{\{([^{}]+)\}\}/g;
|
|
12
|
+
var translationCache = /* @__PURE__ */ Object.create(null);
|
|
13
|
+
function sanitizeLocale(locale, translations) {
|
|
14
|
+
if (!locale) return null;
|
|
15
|
+
if (typeof translations[locale] === "object") return locale;
|
|
16
|
+
const normalized = locale.toLowerCase().replaceAll("-", "_");
|
|
17
|
+
if (typeof translations[normalized] === "object") return normalized;
|
|
18
|
+
const short = normalized.split("_")[0];
|
|
19
|
+
if (short && typeof translations[short] === "object") return short;
|
|
20
|
+
console.warn(NO_TRANSLATION_WARNING_MESSAGE, locale);
|
|
21
|
+
return locale;
|
|
22
|
+
}
|
|
23
|
+
function memoize(fn) {
|
|
24
|
+
return (descriptor, values, defaultMessage) => {
|
|
25
|
+
const cacheKey = values ? JSON.stringify(values) + descriptor + (defaultMessage ?? "") : descriptor + (defaultMessage ?? "");
|
|
26
|
+
const cached = translationCache[cacheKey];
|
|
27
|
+
if (cached !== void 0) return cached;
|
|
28
|
+
const output = fn(descriptor, values, defaultMessage);
|
|
29
|
+
translationCache[cacheKey] = output;
|
|
30
|
+
return output;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function clearCache() {
|
|
34
|
+
translationCache = /* @__PURE__ */ Object.create(null);
|
|
35
|
+
}
|
|
36
|
+
function transformToPairs(templates, values) {
|
|
37
|
+
return templates.map((token) => {
|
|
38
|
+
const placeholderKey = token.slice(2, -2);
|
|
39
|
+
if (Object.hasOwn(values, placeholderKey)) {
|
|
40
|
+
return [token, values[placeholderKey]];
|
|
41
|
+
}
|
|
42
|
+
console.warn(NO_TEMPLATE_VALUE_MESSAGE, token);
|
|
43
|
+
return [token, token];
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function buildTranslation(source, values) {
|
|
47
|
+
if (!values) return source;
|
|
48
|
+
if (Object.keys(values).length === 0) return source;
|
|
49
|
+
const templates = source.match(PARSE_TEMPLATE_REGEXP);
|
|
50
|
+
if (!templates || templates.length === 0) return source;
|
|
51
|
+
const pairs = transformToPairs(templates, values);
|
|
52
|
+
let result = source;
|
|
53
|
+
for (const [token, value] of pairs) {
|
|
54
|
+
result = result.replaceAll(token, String(value));
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
var DEFAULT_CONTEXT_VALUE = {
|
|
59
|
+
locale: void 0,
|
|
60
|
+
translate: (descriptor, _values, defaultMessage) => defaultMessage ?? descriptor,
|
|
61
|
+
translations: {}
|
|
62
|
+
};
|
|
63
|
+
var LocalizationContext = react.createContext(
|
|
64
|
+
DEFAULT_CONTEXT_VALUE
|
|
65
|
+
);
|
|
66
|
+
LocalizationContext.displayName = "LocalizationContext";
|
|
67
|
+
function LocalizationProvider({
|
|
68
|
+
children,
|
|
69
|
+
disableCache = false,
|
|
70
|
+
locale,
|
|
71
|
+
translations = {}
|
|
72
|
+
}) {
|
|
73
|
+
const pureTranslations = react.useMemo(() => {
|
|
74
|
+
const sanitizedLocale = sanitizeLocale(locale, translations);
|
|
75
|
+
const localeTranslations = sanitizedLocale === null ? translations : translations[sanitizedLocale];
|
|
76
|
+
return localeTranslations && typeof localeTranslations === "object" ? localeTranslations : {};
|
|
77
|
+
}, [locale, translations]);
|
|
78
|
+
react.useEffect(() => {
|
|
79
|
+
clearCache();
|
|
80
|
+
}, [locale, translations]);
|
|
81
|
+
const translate = react.useMemo(() => {
|
|
82
|
+
const pureTranslate = (descriptor, values, defaultMessage) => {
|
|
83
|
+
if (!descriptor) return defaultMessage ?? descriptor;
|
|
84
|
+
const fallback = typeof defaultMessage === "string" ? defaultMessage : descriptor;
|
|
85
|
+
const direct = pureTranslations[descriptor];
|
|
86
|
+
if (typeof direct === "string") {
|
|
87
|
+
return values ? buildTranslation(direct, values) : direct;
|
|
88
|
+
}
|
|
89
|
+
const segments = descriptor.split(".");
|
|
90
|
+
if (segments.length === 1) {
|
|
91
|
+
return buildTranslation(fallback, values);
|
|
92
|
+
}
|
|
93
|
+
const resolved = resolveNestedKey(pureTranslations, segments);
|
|
94
|
+
return typeof resolved === "string" ? buildTranslation(resolved, values) : buildTranslation(fallback, values);
|
|
95
|
+
};
|
|
96
|
+
return disableCache ? pureTranslate : memoize(pureTranslate);
|
|
97
|
+
}, [disableCache, pureTranslations]);
|
|
98
|
+
const value = react.useMemo(
|
|
99
|
+
() => ({ locale, translate, translations }),
|
|
100
|
+
[locale, translate, translations]
|
|
101
|
+
);
|
|
102
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LocalizationContext.Provider, { value, children });
|
|
103
|
+
}
|
|
104
|
+
var LocalizationConsumer = LocalizationContext.Consumer;
|
|
105
|
+
function resolveNestedKey(tree, segments) {
|
|
106
|
+
let cursor = tree;
|
|
107
|
+
for (const segment of segments) {
|
|
108
|
+
if (cursor === void 0 || typeof cursor === "string") return void 0;
|
|
109
|
+
cursor = cursor[segment];
|
|
110
|
+
}
|
|
111
|
+
return typeof cursor === "string" ? cursor : void 0;
|
|
112
|
+
}
|
|
113
|
+
function useLocalize() {
|
|
114
|
+
return react.useContext(LocalizationContext);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/Message.tsx
|
|
118
|
+
function Message({
|
|
119
|
+
defaultMessage,
|
|
120
|
+
descriptor,
|
|
121
|
+
values
|
|
122
|
+
}) {
|
|
123
|
+
const { translate } = useLocalize();
|
|
124
|
+
return translate(descriptor, values, defaultMessage);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
exports.LocalizationConsumer = LocalizationConsumer;
|
|
128
|
+
exports.LocalizationContext = LocalizationContext;
|
|
129
|
+
exports.LocalizationProvider = LocalizationProvider;
|
|
130
|
+
exports.Message = Message;
|
|
131
|
+
exports.useLocalize = useLocalize;
|
|
132
|
+
//# sourceMappingURL=index.cjs.map
|
|
133
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/helpers.ts","../src/Provider.tsx","../src/use-localize.ts","../src/Message.tsx"],"names":["createContext","useMemo","useEffect","jsx","useContext"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { TemplateValues, Translate, Translations } from './types.js';\n\n/** Warning logged when no locale entry matches the requested locale. */\nexport const NO_TRANSLATION_WARNING_MESSAGE =\n '[LOCALIZE-REACT]: There are no translations for specified locale';\n\n/** Warning logged when a `{{placeholder}}` has no matching value. */\nexport const NO_TEMPLATE_VALUE_MESSAGE =\n '[LOCALIZE-REACT] Looks like template is being used, but no value passed for ';\n\n/**\n * Pattern matching `{{name}}`-style mustaches. Anchored to avoid\n * consuming adjacent braces (`{{{a}}}` only matches `{{a}}`).\n */\nexport const PARSE_TEMPLATE_REGEXP = /\\{\\{([^{}]+)\\}\\}/g;\n\n/**\n * Module-scoped translation cache. Kept module-scoped (rather than\n * provider-scoped) for behavioural parity with v1.x; see ADR-008.\n */\nlet translationCache: Record<string, string> = Object.create(null) as Record<\n string,\n string\n>;\n\n/**\n * Normalize a locale string against the available translation keys.\n *\n * - Returns `null` if no locale was supplied.\n * - Returns the input as-is if `translations[locale]` is an object.\n * - Lowercases and replaces dashes with underscores, then retries.\n * - Falls back to the leading segment (`en_us` → `en`).\n * - If nothing matches, logs a warning and returns the original input.\n */\nexport function sanitizeLocale(\n locale: string | undefined,\n translations: Translations,\n): string | null {\n if (!locale) return null;\n\n if (typeof translations[locale] === 'object') return locale;\n\n const normalized = locale.toLowerCase().replaceAll('-', '_');\n if (typeof translations[normalized] === 'object') return normalized;\n\n const short = normalized.split('_')[0];\n if (short && typeof translations[short] === 'object') return short;\n\n console.warn(NO_TRANSLATION_WARNING_MESSAGE, locale);\n return locale;\n}\n\n/**\n * Returns a memoizing wrapper around {@link fn}. The cache key combines\n * the descriptor, the JSON-serialized values, and the default message.\n */\nexport function memoize(fn: Translate): Translate {\n return (descriptor, values, defaultMessage) => {\n const cacheKey = values\n ? JSON.stringify(values) + descriptor + (defaultMessage ?? '')\n : descriptor + (defaultMessage ?? '');\n\n const cached = translationCache[cacheKey];\n if (cached !== undefined) return cached;\n\n const output = fn(descriptor, values, defaultMessage);\n translationCache[cacheKey] = output;\n return output;\n };\n}\n\n/** Reset the module-level translation cache. */\nexport function clearCache(): void {\n translationCache = Object.create(null) as Record<string, string>;\n}\n\n/**\n * Pair each `{{key}}` template token with the matching value from\n * {@link values}. Tokens with no value are paired with themselves and\n * logged.\n */\nexport function transformToPairs(\n templates: readonly string[],\n values: TemplateValues,\n): readonly (readonly [string, string | number])[] {\n return templates.map((token) => {\n const placeholderKey = token.slice(2, -2);\n if (Object.hasOwn(values, placeholderKey)) {\n return [token, values[placeholderKey]!] as const;\n }\n console.warn(NO_TEMPLATE_VALUE_MESSAGE, token);\n return [token, token] as const;\n });\n}\n\n/**\n * Replace every `{{placeholder}}` token in {@link source} with its\n * corresponding entry from {@link values}. Uses literal string\n * replacement (not `new RegExp(token)`) so values containing regex\n * meta-characters are safe — see MIGRATION_PLAN.md risk R4.\n */\nexport function buildTranslation(\n source: string,\n values?: TemplateValues,\n): string {\n if (!values) return source;\n if (Object.keys(values).length === 0) return source;\n\n const templates = source.match(PARSE_TEMPLATE_REGEXP);\n if (!templates || templates.length === 0) return source;\n\n const pairs = transformToPairs(templates, values);\n\n let result = source;\n for (const [token, value] of pairs) {\n result = result.replaceAll(token, String(value));\n }\n return result;\n}\n","import { createContext, useEffect, useMemo } from 'react';\nimport type { JSX } from 'react';\n\nimport {\n buildTranslation,\n clearCache,\n memoize,\n sanitizeLocale,\n} from './helpers.js';\nimport type {\n LocalizationContextValue,\n LocalizationProviderProps,\n TemplateValues,\n Translate,\n Translations,\n} from './types.js';\n\nconst DEFAULT_CONTEXT_VALUE: LocalizationContextValue = {\n locale: undefined,\n translate: (descriptor, _values, defaultMessage) =>\n defaultMessage ?? descriptor,\n translations: {},\n};\n\n/**\n * React context carrying the active translate function. Prefer\n * {@link useLocalize} or {@link LocalizationConsumer} in new code;\n * `static contextType = LocalizationContext` is supported for legacy\n * class components.\n */\nexport const LocalizationContext = createContext<LocalizationContextValue>(\n DEFAULT_CONTEXT_VALUE,\n);\n\nLocalizationContext.displayName = 'LocalizationContext';\n\n/**\n * Provides translation data to descendants. Wrap the root of your app\n * (or the subtree that needs translations) with this component.\n *\n * @example\n * ```tsx\n * <LocalizationProvider locale=\"en\" translations={messages}>\n * <App />\n * </LocalizationProvider>\n * ```\n */\nexport function LocalizationProvider({\n children,\n disableCache = false,\n locale,\n translations = {},\n}: LocalizationProviderProps): JSX.Element {\n const pureTranslations = useMemo<Translations>(() => {\n const sanitizedLocale = sanitizeLocale(locale, translations);\n const localeTranslations: Translations | string | undefined =\n sanitizedLocale === null ? translations : translations[sanitizedLocale];\n return localeTranslations && typeof localeTranslations === 'object'\n ? localeTranslations\n : {};\n }, [locale, translations]);\n\n useEffect(() => {\n clearCache();\n }, [locale, translations]);\n\n const translate = useMemo<Translate>(() => {\n const pureTranslate: Translate = (descriptor, values, defaultMessage) => {\n if (!descriptor) return defaultMessage ?? descriptor;\n\n const fallback =\n typeof defaultMessage === 'string' ? defaultMessage : descriptor;\n\n const direct = pureTranslations[descriptor];\n if (typeof direct === 'string') {\n return values ? buildTranslation(direct, values) : direct;\n }\n\n const segments = descriptor.split('.');\n if (segments.length === 1) {\n return buildTranslation(fallback, values);\n }\n\n const resolved = resolveNestedKey(pureTranslations, segments);\n return typeof resolved === 'string'\n ? buildTranslation(resolved, values)\n : buildTranslation(fallback, values);\n };\n\n return disableCache ? pureTranslate : memoize(pureTranslate);\n }, [disableCache, pureTranslations]);\n\n const value = useMemo<LocalizationContextValue>(\n () => ({ locale, translate, translations }),\n [locale, translate, translations],\n );\n\n return (\n <LocalizationContext.Provider value={value}>\n {children}\n </LocalizationContext.Provider>\n );\n}\n\n/**\n * Render-prop consumer for {@link LocalizationContext}. Prefer\n * {@link useLocalize} in function components.\n */\nexport const LocalizationConsumer = LocalizationContext.Consumer;\n\nfunction resolveNestedKey(\n tree: Translations,\n segments: readonly string[],\n): string | undefined {\n let cursor: string | Translations | undefined = tree;\n for (const segment of segments) {\n if (cursor === undefined || typeof cursor === 'string') return undefined;\n cursor = cursor[segment];\n }\n return typeof cursor === 'string' ? cursor : undefined;\n}\n\n// `TemplateValues` re-export keeps the file self-contained for the\n// snapshot of the public type surface; do not remove without updating\n// `src/index.ts`.\nexport type { TemplateValues };\n","import { useContext } from 'react';\n\nimport { LocalizationContext } from './Provider.js';\nimport type { LocalizationContextValue } from './types.js';\n\n/**\n * Read the current {@link LocalizationContextValue} from the nearest\n * {@link LocalizationProvider}. Throws nothing if there is no provider —\n * a no-op `translate` (returns the descriptor) is used instead.\n *\n * @example\n * ```tsx\n * function Greeting() {\n * const { translate } = useLocalize();\n * return <h1>{translate('greeting.hello', { name: 'World' })}</h1>;\n * }\n * ```\n */\nexport function useLocalize(): LocalizationContextValue {\n return useContext(LocalizationContext);\n}\n","import type { MessageProps } from './types.js';\nimport { useLocalize } from './use-localize.js';\n\n/**\n * Render a translated string by descriptor.\n *\n * Equivalent to inlining `useLocalize().translate(descriptor, values,\n * defaultMessage)`. Use this when you want a component instead of a\n * hook call (e.g. inside `<button title={...} />` is not possible, but\n * `<button title=\"...\"><Message .../></button>` reads fine in JSX).\n *\n * @example\n * ```tsx\n * <Message descriptor=\"greeting.hello\" values={{ name: 'Alex' }} />\n * ```\n */\nexport function Message({\n defaultMessage,\n descriptor,\n values,\n}: MessageProps): string {\n const { translate } = useLocalize();\n return translate(descriptor, values, defaultMessage);\n}\n"]}
|