localize-react 1.7.1 → 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 -45
- 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
|
-
|
|
32
|
+
```tsx
|
|
33
|
+
import { LocalizationProvider, useLocalize } from 'localize-react';
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
35
|
+
const translations = {
|
|
36
|
+
en: { hello: 'Hi {{name}}!' },
|
|
37
|
+
es: { hello: '¡Hola {{name}}!' },
|
|
38
|
+
ja: { hello: '{{name}}さん、こんにちは!' },
|
|
39
|
+
};
|
|
23
40
|
|
|
24
|
-
|
|
41
|
+
function Greeting() {
|
|
42
|
+
const { translate } = useLocalize();
|
|
43
|
+
return <h1>{translate('hello', { name: 'Alex' })}</h1>;
|
|
44
|
+
}
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
export default function App() {
|
|
47
|
+
return (
|
|
48
|
+
<LocalizationProvider locale="en" translations={translations}>
|
|
49
|
+
<Greeting />
|
|
50
|
+
</LocalizationProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
28
53
|
```
|
|
29
54
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
### Provider & Consumer
|
|
55
|
+
That's the whole API. Three exports, no plugins, no extraction toolchain. Ship it.
|
|
33
56
|
|
|
34
|
-
|
|
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.
|
|
57
|
+
---
|
|
39
58
|
|
|
40
|
-
|
|
59
|
+
## ✨ Why?
|
|
41
60
|
|
|
42
|
-
|
|
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
|
-
```
|
|
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.**
|
|
67
62
|
|
|
68
|
-
|
|
63
|
+
It does exactly what a frontend most often needs:
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
- `
|
|
73
|
-
-
|
|
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.
|
|
74
69
|
|
|
75
|
-
|
|
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).
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
import React from 'react';
|
|
79
|
-
import ReactDOM from 'react-dom';
|
|
80
|
-
import { LocalizationProvider, Message } from 'localize-react';
|
|
72
|
+
## 📦 Specs
|
|
81
73
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 |
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
<LocalizationProvider
|
|
90
|
-
locale="en"
|
|
91
|
-
translations={TRANSLATIONS}
|
|
92
|
-
>
|
|
93
|
-
<Message descriptor="name" />
|
|
94
|
-
</LocalizationProvider>
|
|
95
|
-
);
|
|
86
|
+
## 🚀 Install
|
|
96
87
|
|
|
97
|
-
|
|
88
|
+
```sh
|
|
89
|
+
npm install localize-react
|
|
90
|
+
# or: pnpm add localize-react · yarn add localize-react · bun add localize-react
|
|
98
91
|
```
|
|
99
92
|
|
|
100
|
-
|
|
93
|
+
## ⚡️ Quickstart
|
|
101
94
|
|
|
102
|
-
|
|
103
|
-
import React from 'react';
|
|
104
|
-
import ReactDOM from 'react-dom';
|
|
105
|
-
import { LocalizationProvider, Message } from 'localize-react';
|
|
95
|
+
### 1. Define translations
|
|
106
96
|
|
|
107
|
-
|
|
97
|
+
```ts
|
|
98
|
+
export const translations = {
|
|
108
99
|
en: {
|
|
109
|
-
|
|
100
|
+
greeting: { hello: 'Hi {{name}}!' },
|
|
101
|
+
cart: { summary: '{{count}} items, {{total}} total' },
|
|
110
102
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<LocalizationProvider
|
|
115
|
-
locale="en"
|
|
116
|
-
translations={TRANSLATIONS}
|
|
117
|
-
>
|
|
118
|
-
<Message descriptor="name" values={{ name: 'Alex' }} />
|
|
119
|
-
</LocalizationProvider>
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
ReactDOM.render(<App />, node); // "Alex" will be rendered
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
To use with default message:
|
|
126
|
-
|
|
127
|
-
```js
|
|
128
|
-
import React from 'react';
|
|
129
|
-
import ReactDOM from 'react-dom';
|
|
130
|
-
import { LocalizationProvider, Message } from 'localize-react';
|
|
131
|
-
|
|
132
|
-
const TRANSLATIONS = {
|
|
133
|
-
en: {},
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const App = () => (
|
|
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
|
-
```
|
|
151
|
-
|
|
152
|
-
### useLocalize
|
|
153
|
-
|
|
154
|
-
`useLocalize` hook is used to provide localization context, which can be used for translation.
|
|
155
|
-
|
|
156
|
-
**NOTE**
|
|
157
|
-
|
|
158
|
-
Keep in mind, that hooks are not supported in class components!
|
|
159
|
-
|
|
160
|
-
Example:
|
|
161
|
-
|
|
162
|
-
```js
|
|
163
|
-
import React from 'react';
|
|
164
|
-
import ReactDOM from 'react-dom';
|
|
165
|
-
import { LocalizationProvider, useLocalize } from 'localize-react';
|
|
166
|
-
|
|
167
|
-
const TRANSLATIONS = {
|
|
168
|
-
en: {
|
|
169
|
-
name: 'Alex',
|
|
103
|
+
es: {
|
|
104
|
+
greeting: { hello: '¡Hola {{name}}!' },
|
|
105
|
+
cart: { summary: '{{count}} artículos, {{total}} total' },
|
|
170
106
|
},
|
|
171
|
-
};
|
|
107
|
+
} as const;
|
|
108
|
+
```
|
|
172
109
|
|
|
173
|
-
|
|
174
|
-
const { translate } = useLocalize();
|
|
110
|
+
### 2. Mount the provider
|
|
175
111
|
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const App = () => {
|
|
112
|
+
```tsx
|
|
113
|
+
import { LocalizationProvider } from 'localize-react';
|
|
114
|
+
import { translations } from './i18n/translations';
|
|
180
115
|
|
|
116
|
+
export function App() {
|
|
181
117
|
return (
|
|
182
|
-
<LocalizationProvider
|
|
183
|
-
|
|
184
|
-
translations={TRANSLATIONS}
|
|
185
|
-
>
|
|
186
|
-
<Test />
|
|
118
|
+
<LocalizationProvider locale="en" translations={translations}>
|
|
119
|
+
<Shell />
|
|
187
120
|
</LocalizationProvider>
|
|
188
121
|
);
|
|
189
122
|
}
|
|
190
|
-
|
|
191
|
-
ReactDOM.render(<App />, node); // "Alex" will be rendered
|
|
192
123
|
```
|
|
193
124
|
|
|
194
|
-
###
|
|
125
|
+
### 3. Translate, two ways
|
|
195
126
|
|
|
196
|
-
|
|
127
|
+
```tsx
|
|
128
|
+
import { Message, useLocalize } from 'localize-react';
|
|
197
129
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Or with React component:
|
|
203
|
-
|
|
204
|
-
```js
|
|
205
|
-
<Message descriptor="My name is {{name}}. I am {{age}}" values={{ name: 'Alex', age: 25 }} />
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### contextType
|
|
209
|
-
|
|
210
|
-
Alternative way of usage inside class components:
|
|
211
|
-
|
|
212
|
-
```js
|
|
213
|
-
import React from 'react';
|
|
214
|
-
import { LocalizationContext, LocalizationProvider } from 'localize-react';
|
|
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"]}
|