@vielzeug/i18nit 1.2.0 → 2.0.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.
Files changed (46) hide show
  1. package/README.md +51 -823
  2. package/dist/core.cjs +2 -0
  3. package/dist/core.cjs.map +1 -0
  4. package/dist/core.d.ts +35 -0
  5. package/dist/core.d.ts.map +1 -0
  6. package/dist/core.js +53 -0
  7. package/dist/core.js.map +1 -0
  8. package/dist/helpers.cjs +2 -0
  9. package/dist/helpers.cjs.map +1 -0
  10. package/dist/helpers.d.ts +20 -0
  11. package/dist/helpers.d.ts.map +1 -0
  12. package/dist/helpers.js +47 -0
  13. package/dist/helpers.js.map +1 -0
  14. package/dist/i18n.cjs +2 -0
  15. package/dist/i18n.cjs.map +1 -0
  16. package/dist/i18n.d.ts +78 -0
  17. package/dist/i18n.d.ts.map +1 -0
  18. package/dist/i18n.js +218 -0
  19. package/dist/i18n.js.map +1 -0
  20. package/dist/i18nit.cjs +2 -2
  21. package/dist/i18nit.cjs.map +1 -1
  22. package/dist/i18nit.d.ts +3 -0
  23. package/dist/i18nit.d.ts.map +1 -0
  24. package/dist/i18nit.js +2 -235
  25. package/dist/i18nit.js.map +1 -1
  26. package/dist/index.cjs +1 -2
  27. package/dist/index.d.ts +4 -81
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +2 -6
  30. package/dist/interpolate.cjs +2 -0
  31. package/dist/interpolate.cjs.map +1 -0
  32. package/dist/interpolate.d.ts +11 -0
  33. package/dist/interpolate.d.ts.map +1 -0
  34. package/dist/interpolate.js +13 -0
  35. package/dist/interpolate.js.map +1 -0
  36. package/dist/intl.cjs +2 -0
  37. package/dist/intl.cjs.map +1 -0
  38. package/dist/intl.d.ts +16 -0
  39. package/dist/intl.d.ts.map +1 -0
  40. package/dist/intl.js +65 -0
  41. package/dist/intl.js.map +1 -0
  42. package/dist/types.d.ts +97 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/package.json +19 -9
  45. package/dist/index.cjs.map +0 -1
  46. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -1,856 +1,84 @@
1
1
  # @vielzeug/i18nit
2
2
 
3
- ## What is I18nit?
3
+ > Lightweight, type-safe i18n with nested keys, lazy loaders, interpolation, pluralization, and reactive subscriptions.
4
4
 
5
- **I18nit** is a type-safe, lightweight internationalization library for TypeScript. Build multilingual applications with powerful pluralization, nested translations, and lazy loading—all in just 1.6 KB.
5
+ [![npm version](https://img.shields.io/npm/v/@vielzeug/i18nit)](https://www.npmjs.com/package/@vielzeug/i18nit) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- ### The Problem
7
+ `@vielzeug/i18nit` is a zero-dependency internationalization library for TypeScript. It combines typed key paths, fallback locale chains, async locale loading, and Intl formatting helpers.
8
8
 
9
- Internationalization libraries are often heavy and complex:
9
+ ## Installation
10
10
 
11
- - **i18next** is feature-rich but adds 11KB+ to your bundle
12
- - **react-intl** is React-specific and requires setup
13
- - **FormatJS** has a steep learning curve
14
- - Manual translations lead to missing keys and runtime errors
15
- - Type safety requires extra tooling
16
-
17
- ### The Solution
18
-
19
- I18nit provides a simple, type-safe API using native browser APIs:
20
-
21
- ```typescript
22
- import { createI18n } from '@vielzeug/i18nit';
23
-
24
- const i18n = createI18n({
25
- locale: 'en',
26
- messages: {
27
- en: {
28
- welcome: 'Welcome, {name}!',
29
- items: 'You have {count} item | You have {count} items',
30
- },
31
- es: {
32
- welcome: '¡Bienvenido, {name}!',
33
- items: 'Tienes {count} artículo | Tienes {count} artículos',
34
- },
35
- },
36
- });
37
-
38
- // Interpolation
39
- i18n.t('welcome', { name: 'Alice' }); // "Welcome, Alice!"
40
-
41
- // Automatic pluralization
42
- i18n.t('items', { count: 1 }); // "You have 1 item"
43
- i18n.t('items', { count: 5 }); // "You have 5 items"
11
+ ```sh
12
+ pnpm add @vielzeug/i18nit
13
+ # npm install @vielzeug/i18nit
14
+ # yarn add @vielzeug/i18nit
44
15
  ```
45
16
 
46
- ## Features
47
-
48
- - ✅ **Type-Safe** – Full TypeScript support with generic types
49
- - ✅ **Lightweight** – 1.6 KB gzipped with zero dependencies
50
- - ✅ **Universal Pluralization** – 100+ languages via Intl.PluralRules API
51
- - ✅ **Smart Array Handling** – Auto-join with separators, length access, and safe indexing
52
- - ✅ **Path Interpolation** – Support for nested objects and array indices
53
- - ✅ **Lazy Loading** – Async locale loading with automatic caching
54
- - ✅ **Namespaces** – Organize translations by feature or module
55
- - ✅ **Fallback Chain** – Multiple fallback locales with automatic language variants
56
- - ✅ **HTML Escaping** – Built-in XSS protection
57
- - ✅ **Number & Date Formatting** – Locale-aware formatting with Intl API
58
- - ✅ **Framework Agnostic** – Works with React, Vue, Svelte, or vanilla JS
59
-
60
- ## 🆚 Comparison with Alternatives
17
+ ## Entry Points
61
18
 
62
- | Feature | I18nit | i18next | react-intl | FormatJS |
63
- | ------------------- | -------------- | ------- | ---------- | ------------ |
64
- | Bundle Size (gzip) | **~1.6 KB** | ~11KB | ~14KB | ~14KB |
65
- | TypeScript Support | First-class | ✅ Good | ✅ Good | ✅ Excellent |
66
- | Pluralization | ✅ Native Intl | ✅ ICU | ✅ ICU | ✅ ICU |
67
- | Nested Translations | ✅ Built-in | ✅ Yes | ⚠️ Limited | ✅ Yes |
68
- | Lazy Loading | ✅ Async | ✅ Yes | ⚠️ Manual | ✅ Yes |
69
- | Framework Agnostic | ✅ Yes | ✅ Yes | ❌ React | ❌ React |
70
- | Dependencies | 0 | 3 | 5 | 7 |
19
+ | Entry | Purpose |
20
+ | --- | --- |
21
+ | `@vielzeug/i18nit` | Main API (`createI18n`, `I18n`, exported types) |
22
+ | `@vielzeug/i18nit/core` | Core bundle entry |
71
23
 
72
- ## 📦 Installation
24
+ ## Quick Start
73
25
 
74
- ```bash
75
- # pnpm
76
- pnpm add @vielzeug/i18nit
77
- # npm
78
- npm install @vielzeug/i18nit
79
- # yarn
80
- yarn add @vielzeug/i18nit
81
- ```
82
-
83
- ## 🚀 Quick Start
84
-
85
- ```typescript
26
+ ```ts
86
27
  import { createI18n } from '@vielzeug/i18nit';
87
28
 
88
- // Create instance with messages
89
29
  const i18n = createI18n({
30
+ fallback: 'en',
90
31
  locale: 'en',
91
32
  messages: {
92
- en: {
93
- greeting: 'Hello, {name}!',
94
- items: {
95
- zero: 'No items',
96
- one: 'One item',
97
- other: '{count} items',
98
- },
99
- },
100
- es: {
101
- greeting: '¡Hola, {name}!',
102
- items: {
103
- zero: 'Sin artículos',
104
- one: 'Un artículo',
105
- other: '{count} artículos',
106
- },
107
- },
108
- },
109
- });
110
-
111
- // Simple translation
112
- i18n.t('greeting', { name: 'World' }); // "Hello, World!"
113
-
114
- // Pluralization
115
- i18n.t('items', { count: 0 }); // "No items"
116
- i18n.t('items', { count: 1 }); // "One item"
117
- i18n.t('items', { count: 5 }); // "5 items"
118
-
119
- // Change locale
120
- i18n.setLocale('es');
121
- i18n.t('greeting', { name: 'Mundo' }); // "¡Hola, Mundo!"
122
- ```
123
-
124
- ## 📚 Core Concepts
125
-
126
- ### Translation Keys
127
-
128
- Translation keys support dot notation for nested organization:
129
-
130
- ```typescript
131
- const i18n = createI18n({
132
- messages: {
133
- en: {
134
- 'user.profile.title': 'Profile',
135
- 'user.settings.title': 'Settings',
136
- 'admin.dashboard': 'Dashboard',
33
+ de: {
34
+ greeting: 'Hallo, {name}!',
35
+ inbox: { one: 'Eine Nachricht', other: '{count} Nachrichten' },
137
36
  },
138
- },
139
- });
140
-
141
- i18n.t('user.profile.title'); // "Profile"
142
- ```
143
-
144
- ### Nested Message Objects
145
-
146
- You can organize messages using nested objects for better structure:
147
-
148
- ```typescript
149
- const i18n = createI18n({
150
- locale: 'en',
151
- messages: {
152
37
  en: {
153
- // Flat structure
154
- welcome: 'Welcome!',
155
-
156
- // Nested structure - access with dot notation
157
- user: {
158
- greeting: 'Hello, {name}!',
159
- profile: {
160
- title: 'User Profile',
161
- settings: 'Profile Settings',
162
- },
163
- },
164
-
165
- // Deep nesting
166
- app: {
167
- navigation: {
168
- menu: {
169
- home: 'Home',
170
- about: 'About',
171
- },
172
- },
173
- },
38
+ greeting: 'Hello, {name}!',
39
+ inbox: { zero: 'No messages', one: 'One message', other: '{count} messages' },
40
+ nav: { home: 'Home' },
174
41
  },
175
42
  },
176
43
  });
177
44
 
178
- // Access nested messages with dot notation
179
- i18n.t('welcome'); // "Welcome!"
180
- i18n.t('user.greeting', { name: 'Alice' }); // "Hello, Alice!"
181
- i18n.t('user.profile.title'); // "User Profile"
182
- i18n.t('app.navigation.menu.home'); // "Home"
183
-
184
- // Use with namespaces for cleaner code
185
- const userNs = i18n.namespace('user');
186
- userNs.t('greeting', { name: 'Bob' }); // "Hello, Bob!"
187
- userNs.t('profile.title'); // "User Profile"
188
- ```
189
-
190
- ### Variable Interpolation
191
-
192
- #### Basic Interpolation
193
-
194
- ```typescript
195
45
  i18n.t('greeting', { name: 'Alice' });
196
- // Template: "Hello, {name}!"
197
- // Result: "Hello, Alice!"
198
- ```
199
-
200
- #### Nested Object Access
201
-
202
- ```typescript
203
- i18n.t('message', { user: { name: 'Bob', role: 'Admin' } });
204
- // Template: "User {user.name} is {user.role}"
205
- // Result: "User Bob is Admin"
206
- ```
207
-
208
- #### Array Index Access
209
-
210
- ```typescript
211
- i18n.t('friends', { friends: [{ name: 'Charlie' }, { name: 'Dave' }] });
212
- // Template: "First friend: {friends[0].name}"
213
- // Result: "First friend: Charlie"
214
- ```
215
-
216
- #### Array Handling
217
-
218
- Arrays can be intelligently formatted with various separators:
219
-
220
- ```typescript
221
- const i18n = createI18n({
222
- messages: {
223
- en: {
224
- shopping: 'Shopping list: {items}',
225
- guests: 'Invited: {names|and}',
226
- options: 'Choose: {choices|or}',
227
- path: 'Path: {folders| / }',
228
- count: 'You have {items.length} items',
229
- },
230
- },
231
- });
232
-
233
- // Default comma separator
234
- i18n.t('shopping', { items: ['Apple', 'Banana', 'Orange'] });
235
- // "Shopping list: Apple, Banana, Orange"
236
-
237
- // Natural "and" lists (locale-aware via Intl.ListFormat – supports 100+ languages automatically)
238
- i18n.t('guests', { names: ['Alice'] });
239
- // "Invited: Alice"
240
- i18n.t('guests', { names: ['Alice', 'Bob'] });
241
- // "Invited: Alice and Bob"
242
- i18n.t('guests', { names: ['Alice', 'Bob', 'Charlie'] });
243
- // "Invited: Alice, Bob, and Charlie" (Oxford comma in English)
244
-
245
- // Natural "or" lists (locale-aware via Intl.ListFormat – supports 100+ languages automatically)
246
- i18n.t('options', { choices: ['Tea', 'Coffee', 'Juice'] });
247
- // "Choose: Tea, Coffee, or Juice"
248
-
249
- // Custom separators
250
- i18n.t('path', { folders: ['home', 'user', 'documents'] });
251
- // "Path: home / user / documents"
252
-
253
- // Array length
254
- i18n.t('count', { items: ['A', 'B', 'C'] });
255
- // "You have 3 items"
256
- ```
257
-
258
- **Array Features:**
259
-
260
- - `{items}` – Join with comma (`, `)
261
- - `{items|and}` – Natural "and" list with locale-aware conjunction (uses Intl.ListFormat – supports 100+ languages)
262
- - `{items|or}` – Natural "or" list with locale-aware conjunction (uses Intl.ListFormat – supports 100+ languages)
263
- - `{items| – }` – Custom separator (e.g., "A – B – C")
264
- - `{items.length}` – Array length
265
- - `{items[0]}` – Safe index access (returns empty if out of bounds)
266
-
267
- **Locale-Aware List Formatting:**
268
- The `and` and `or` separators use the built-in **Intl.ListFormat API** which automatically handles:
269
-
270
- - **100+ languages** – Supports all languages available in the browser/runtime
271
- - **Proper grammar** – Oxford comma, locale-specific punctuation
272
- - **Right-to-left languages** – Arabic, Hebrew, etc.
273
- - **Unicode CLDR standards** – International standard for list formatting
274
- - **No manual configuration** – Zero maintenance required
275
-
276
- Examples across languages:
277
-
278
- - **English**: "A, B, and C" (with Oxford comma)
279
- - **Spanish**: "A, B y C" (uses "y")
280
- - **French**: "A, B et C" (uses "et")
281
- - **German**: "A, B und C" (uses "und")
282
- - **Japanese**: "A、B、C" (uses Japanese comma)
283
- - **Arabic**: Proper RTL formatting with "و"
284
- - And 90+ more languages automatically!
285
-
286
- #### Supported Path Formats
287
-
288
- - `{name}` – Simple variable
289
- - `{user.name}` – Nested object property
290
- - `{items[0]}` – Array index (safe – returns empty if out of bounds)
291
- - `{items}` – Array join with default separator
292
- - `{items|and}` – Array join with "and"
293
- - `{items.length}` – Array length
294
- - `{data.items[0].value}` – Mixed notation
295
-
296
- **Limitations:**
297
-
298
- - Only numeric bracket notation `[0]`, `[123]`
299
- - Quoted keys not supported `["key"]`
300
- - Non-numeric brackets not supported `[key]`
301
-
302
- ### Missing Variable Handling
303
-
304
- Missing variables are automatically replaced with empty strings:
305
-
306
- ```typescript
307
- const i18n = createI18n({
308
- messages: { en: { msg: 'Hello, {name}!' } },
309
- });
310
-
311
- i18n.t('msg'); // "Hello, !"
312
- i18n.t('msg', { name: 'Alice' }); // "Hello, Alice!"
313
- ```
314
-
315
- ### Pluralization
316
-
317
- Support for multiple plural forms based on locale-specific rules:
318
-
319
- ```typescript
320
- const i18n = createI18n({
321
- locale: 'en',
322
- messages: {
323
- en: {
324
- notifications: {
325
- zero: 'No notifications',
326
- one: 'One notification',
327
- other: '{count} notifications',
328
- },
329
- },
330
- },
331
- });
332
-
333
- i18n.t('notifications', { count: 0 }); // "No notifications"
334
- i18n.t('notifications', { count: 1 }); // "One notification"
335
- i18n.t('notifications', { count: 5 }); // "5 notifications"
336
- ```
337
-
338
- #### Supported Plural Rules
339
-
340
- i18nit uses the browser's built-in `Intl.PluralRules` API to automatically support pluralization for **100+ languages**, including:
341
-
342
- - **English (en)**: one, other
343
- - **French (fr)**: one (0-1), other
344
- - **Arabic (ar)**: zero, one, two, few, many, other
345
- - **Polish (pl)**: one, few, many
346
- - **Russian (ru)**: one, few, many, other
347
- - **German (de)**: one, other
348
- - **Chinese (zh)**: other
349
- - **Japanese (ja)**: other
350
- - And 90+ more languages...
351
-
352
- ## 🔥 Advanced Features
353
-
354
- ### Fallback Locales
355
-
356
- Define fallback locales for missing translations:
357
-
358
- ```typescript
359
- const i18n = createI18n({
360
- locale: 'de-CH',
361
- fallback: ['de', 'en'],
362
- messages: {
363
- 'de-CH': { greeting: 'Grüezi!' },
364
- de: { greeting: 'Hallo!', goodbye: 'Auf Wiedersehen!' },
365
- en: { greeting: 'Hello!', goodbye: 'Goodbye!', welcome: 'Welcome!' },
366
- },
367
- });
368
-
369
- i18n.t('greeting'); // "Grüezi!" (de-CH)
370
- i18n.t('goodbye'); // "Auf Wiedersehen!" (de fallback)
371
- i18n.t('welcome'); // "Welcome!" (en fallback)
372
- ```
373
-
374
- **Fallback Chain:**
375
-
376
- 1. Primary locale (e.g., `de-CH`)
377
- 2. Base language (e.g., `de` from `de-CH`)
378
- 3. First fallback locale
379
- 4. Base of first fallback
380
- 5. Continue through all fallbacks
381
-
382
- ### Async Locale Loading
383
-
384
- Load translations on-demand for better performance. Loaders receive the locale as a parameter, allowing you to reuse a single function:
385
-
386
- ```typescript
387
- // Define a reusable loader function
388
- const loadLocale = async (locale: string) => {
389
- const response = await fetch(`/locales/${locale}.json`);
390
- return response.json();
391
- };
392
-
393
- const i18n = createI18n({
394
- locale: 'en',
395
- loaders: {
396
- fr: loadLocale, // Loader receives 'fr' as parameter
397
- de: loadLocale, // Loader receives 'de' as parameter
398
- es: loadLocale, // Loader receives 'es' as parameter
399
- },
400
- });
401
-
402
- // Load a locale before using it
403
- await i18n.load('fr');
404
- i18n.setLocale('fr');
405
- i18n.t('greeting'); // Uses loaded French messages
406
-
407
- // Or use dynamic imports
408
- const importLoader = async (locale: string) => {
409
- const module = await import(`./locales/${locale}.json`);
410
- return module.default;
411
- };
412
-
413
- i18n.register('it', importLoader);
414
- await i18n.load('it');
415
- ```
416
-
417
- // Preload at app startup
418
- await i18n.loadAll(['en', 'fr', 'de']);
419
-
420
- // Or load explicitly
421
- await i18n.load('fr');
422
- i18n.setLocale('fr');
423
- i18n.t('greeting'); // Now uses French
424
-
425
- // Register loader dynamically
426
- i18n.register('es', async () => {
427
- const module = await import('./locales/es.json');
428
- return module.default;
429
- });
430
-
431
- // Load and use
432
- await i18n.load('es');
433
- i18n.t('greeting', undefined, { locale: 'es' });
434
- ```
435
-
436
- **Features:**
437
-
438
- - Concurrent requests are deduplicated
439
- - Failed loads throw errors (can be caught)
440
- - Locale is cached after loading
441
- - Use `loadAll()` to preload multiple locales at once
442
-
443
- ### Namespaces
444
-
445
- Organize translations by feature or module:
46
+ i18n.t('inbox', { count: 0 });
47
+ i18n.t('inbox', { count: 3 });
446
48
 
447
- ```typescript
448
- const i18n = createI18n({
449
- messages: {
450
- en: {
451
- 'auth.login.title': 'Login',
452
- 'auth.login.button': 'Sign In',
453
- 'auth.register.title': 'Register',
454
- 'dashboard.welcome': 'Welcome back!',
455
- },
456
- },
457
- });
458
-
459
- // Create namespaced translator
460
- const auth = i18n.namespace('auth.login');
461
- auth.t('title'); // "Login"
462
- auth.t('button'); // "Sign In"
463
-
464
- const dashboard = i18n.namespace('dashboard');
465
- dashboard.t('welcome'); // "Welcome back!"
466
- ```
467
-
468
- ### HTML Escaping
469
-
470
- Protect against XSS attacks with automatic HTML escaping:
471
-
472
- ```typescript
473
- const i18n = createI18n({
474
- messages: {
475
- en: {
476
- userContent: 'Comment: {content}',
477
- },
478
- },
479
- });
480
-
481
- // Enable escaping globally
482
- const safeI18n = createI18n({
483
- escape: true,
484
- messages: { en: { html: '<script>alert("xss")</script>' } },
485
- });
486
-
487
- safeI18n.t('html');
488
- // "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"
489
-
490
- // Or per translation
491
- i18n.t('userContent', { content: '<b>Bold</b>' }, { escape: true });
492
- // "Comment: &lt;b&gt;Bold&lt;/b&gt;"
493
- ```
494
-
495
- ### Number & Date Formatting
496
-
497
- Locale-aware formatting using the Intl API:
498
-
499
- ```typescript
500
- const i18n = createI18n({ locale: 'en-US' });
501
-
502
- // Number formatting
503
- i18n.number(1234.56); // "1,234.56"
504
- i18n.number(99.99, { style: 'currency', currency: 'USD' }); // "$99.99"
505
- i18n.number(0.15, { style: 'percent' }); // "15%"
506
-
507
- // Date formatting
508
- const date = new Date('2024-01-15');
509
- i18n.date(date); // "1/15/2024"
510
- i18n.date(date, { dateStyle: 'long' }); // "January 15, 2024"
511
- i18n.date(date, { timeStyle: 'short' }); // "12:00 AM"
512
-
513
- // Timestamps
514
- i18n.date(Date.now(), { dateStyle: 'medium' }); // "Jan 15, 2024"
515
-
516
- // Custom locale
517
- i18n.number(1234.56, undefined, 'de-DE'); // "1.234,56"
518
- i18n.date(date, { dateStyle: 'short' }, 'fr'); // "15/01/2024"
519
- ```
520
-
521
- ### Subscriptions
522
-
523
- React to locale changes:
524
-
525
- ```typescript
526
- const i18n = createI18n({ locale: 'en' });
527
-
528
- // Subscribe to locale changes
529
- const unsubscribe = i18n.subscribe((locale) => {
530
- console.log('Locale changed to:', locale);
531
- // Update UI, reload data, etc.
532
- });
533
-
534
- i18n.setLocale('fr'); // Logs: "Locale changed to: fr"
535
-
536
- // Unsubscribe when done
537
- unsubscribe();
538
- ```
539
-
540
- **Use Cases:**
541
-
542
- - Update UI when locale changes
543
- - Reload locale-specific data
544
- - Analytics/tracking
545
- - State management integration
546
-
547
- ### Subscriptions
548
-
549
- ### createI18n(config?)
550
-
551
- Creates a new i18n instance.
552
-
553
- ```typescript
554
- type I18nConfig = {
555
- locale?: string; // Default: 'en'
556
- fallback?: string | string[]; // Fallback locale(s)
557
- messages?: Record<string, Messages>; // Initial translations
558
- loaders?: Record<string, () => Promise<Messages>>; // Async loaders
559
- escape?: boolean; // Global HTML escaping (default: false)
560
- };
561
- ```
562
-
563
- ### Translation Methods
564
-
565
- #### `t(key, vars?, options?)`
566
-
567
- Translate a key synchronously.
568
-
569
- ```typescript
570
- i18n.t('greeting'); // Simple
571
- i18n.t('greeting', { name: 'Alice' }); // With variables
572
- i18n.t('greeting', { name: 'Bob' }, { locale: 'fr', escape: true }); // With options
573
- ```
574
-
575
- **Options:**
576
-
577
- - `locale?: string` – Override locale for this translation
578
- - `escape?: boolean` – Override HTML escaping
579
-
580
- ### Locale Management
581
-
582
- ```typescript
583
- i18n.setLocale('fr'); // Change locale
584
- i18n.getLocale(); // Get current locale
585
- i18n.hasLocale('es'); // Check if locale exists
586
- i18n.has('key'); // Check if key exists
587
- i18n.has('key', 'fr'); // Check if key exists in locale
588
- await i18n.hasAsync('key', 'es'); // Check with async loading
589
- ```
590
-
591
- ### Message Management
592
-
593
- ```typescript
594
- // Add messages (merge)
595
- i18n.add('en', { newKey: 'New value' });
596
-
597
- // Set messages (replace)
598
- i18n.set('en', { key: 'Value' });
599
-
600
- // Get messages for locale
601
- const messages = i18n.getMessages('en');
602
- ```
603
-
604
- ### Async Loading
605
-
606
- ```typescript
607
- // Register loader
608
- i18n.register('de', async () => import('./locales/de.json'));
609
-
610
- // Load locale
611
- await i18n.load('de');
49
+ i18n.locale = 'de';
50
+ i18n.t('nav.home'); // falls back to en
612
51
  ```
613
52
 
614
- ### Formatting
615
-
616
- ```typescript
617
- i18n.number(value, options?, locale?);
618
- i18n.date(value, options?, locale?);
619
- ```
620
-
621
- ### Namespace
622
-
623
- ```typescript
624
- const ns = i18n.namespace('auth');
625
- ns.t('login.title');
626
- ```
627
-
628
- ### Subscriptions
629
-
630
- ```typescript
631
- const unsubscribe = i18n.subscribe((locale) => {
632
- console.log('Locale changed:', locale);
633
- });
634
- ```
635
-
636
- ## Framework Integration
637
-
638
- ### React
639
-
640
- ```tsx
641
- import { createI18n } from '@vielzeug/i18nit';
642
- import { createContext, useContext, useState, useEffect } from 'react';
643
-
644
- const I18nContext = createContext(null);
645
-
646
- export function I18nProvider({ children, config }) {
647
- const [i18n] = useState(() => createI18n(config));
648
- const [locale, setLocale] = useState(i18n.getLocale());
649
-
650
- useEffect(() => {
651
- return i18n.subscribe(setLocale);
652
- }, [i18n]);
653
-
654
- return <I18nContext.Provider value={{ i18n, locale }}>{children}</I18nContext.Provider>;
655
- }
656
-
657
- export function useI18n() {
658
- const context = useContext(I18nContext);
659
- if (!context) throw new Error('useI18n must be used within I18nProvider');
660
- return context;
661
- }
662
-
663
- export function useTranslation(namespace?: string) {
664
- const { i18n } = useI18n();
665
- const ns = namespace ? i18n.namespace(namespace) : i18n;
666
-
667
- return {
668
- t: ns.t.bind(ns),
669
- locale: i18n.getLocale(),
670
- setLocale: i18n.setLocale.bind(i18n),
671
- };
672
- }
673
-
674
- // Usage
675
- function MyComponent() {
676
- const { t, locale, setLocale } = useTranslation('dashboard');
677
-
678
- return (
679
- <div>
680
- <h1>{t('welcome')}</h1>
681
- <button onClick={() => setLocale('fr')}>Français</button>
682
- </div>
683
- );
684
- }
685
- ```
686
-
687
- ### Vue 3
688
-
689
- ```typescript
690
- import { createI18n } from '@vielzeug/i18nit';
691
- import { ref, onUnmounted, Plugin } from 'vue';
692
-
693
- const i18n = createI18n({ locale: 'en' });
694
- const locale = ref(i18n.getLocale());
695
-
696
- const unsubscribe = i18n.subscribe((newLocale) => {
697
- locale.value = newLocale;
698
- });
699
-
700
- export const i18nPlugin: Plugin = {
701
- install(app) {
702
- app.config.globalProperties.$t = i18n.t.bind(i18n);
703
- app.config.globalProperties.$i18n = i18n;
704
-
705
- app.provide('i18n', i18n);
706
- app.provide('locale', locale);
707
- },
708
- };
709
-
710
- // Composable
711
- export function useI18n() {
712
- return {
713
- t: i18n.t.bind(i18n),
714
- locale,
715
- setLocale: (newLocale: string) => i18n.setLocale(newLocale),
716
- };
717
- }
718
-
719
- // Usage in component
720
- <script setup>
721
- import { useI18n } from './i18n';
722
-
723
- const { t, locale, setLocale } = useI18n();
724
- </script>
725
-
726
- <template>
727
- <div>
728
- <h1>{{ t('welcome') }}</h1>
729
- <button @click="setLocale('fr')">Français</button>
730
- </div>
731
- </template>
732
- ```
733
-
734
- ### Svelte
735
-
736
- ```typescript
737
- import { createI18n } from '@vielzeug/i18nit';
738
- import { writable } from 'svelte/store';
739
-
740
- const i18n = createI18n({ locale: 'en' });
741
- export const locale = writable(i18n.getLocale());
742
-
743
- i18n.subscribe((newLocale) => {
744
- locale.set(newLocale);
745
- });
746
-
747
- export const t = i18n.t.bind(i18n);
748
- export const setLocale = i18n.setLocale.bind(i18n);
749
-
750
- // Usage
751
- <script>
752
- import { t, setLocale } from './i18n';
753
- </script>
754
-
755
- <h1>{$t('welcome')}</h1>
756
- <button on:click={() => setLocale('fr')}>Français</button>
757
- ```
758
-
759
- ## Best Practices
760
-
761
- ### 1. Organize Translations by Feature
762
-
763
- ```typescript
764
- const messages = {
765
- en: {
766
- 'auth.login.title': 'Login',
767
- 'auth.register.title': 'Register',
768
- 'dashboard.stats.users': 'Users',
769
- 'dashboard.stats.revenue': 'Revenue',
770
- },
771
- };
772
- ```
773
-
774
- ### 2. Use Namespaces for Large Apps
775
-
776
- ```typescript
777
- const authTranslations = i18n.namespace('auth');
778
- const dashboardTranslations = i18n.namespace('dashboard');
779
- ```
780
-
781
- ### 3. Lazy Load Translations
782
-
783
- ```typescript
784
- const i18n = createI18n({
785
- loaders: {
786
- 'en-US': () => import('./locales/en-US.json'),
787
- 'es-ES': () => import('./locales/es-ES.json'),
788
- },
789
- });
790
- ```
791
-
792
- ### 4. Type-Safe Translation Keys
793
-
794
- ```typescript
795
- type TranslationKeys = 'auth.login.title' | 'auth.register.title' | 'dashboard.welcome';
796
-
797
- function t(key: TranslationKeys, vars?: Record<string, unknown>) {
798
- return i18n.t(key, vars);
799
- }
800
- ```
801
-
802
- ## TypeScript Support
803
-
804
- Full TypeScript support with type inference:
805
-
806
- ```typescript
807
- import { createI18n, type Messages, type I18nConfig } from '@vielzeug/i18nit';
808
-
809
- // Define your messages type
810
- interface MyMessages extends Messages {
811
- greeting: string;
812
- items: {
813
- zero: string;
814
- one: string;
815
- other: string;
816
- };
817
- }
818
-
819
- const config: I18nConfig = {
820
- locale: 'en',
821
- messages: {
822
- en: {
823
- greeting: 'Hello!',
824
- items: { zero: 'No items', one: 'One item', other: '{count} items' },
825
- } satisfies MyMessages,
826
- },
827
- };
828
-
829
- const i18n = createI18n(config);
830
- ```
831
-
832
- ## 📖 Documentation
833
-
834
- - [**Full Documentation**](https://helmuthdu.github.io/vielzeug/i18nit)
835
- - [**Usage Guide**](https://helmuthdu.github.io/vielzeug/i18nit/usage)
836
- - [**API Reference**](https://helmuthdu.github.io/vielzeug/i18nit/api)
837
- - [**Examples**](https://helmuthdu.github.io/vielzeug/i18nit/examples)
838
-
839
- ## 📄 License
53
+ ## Features
840
54
 
841
- MIT © [Helmuth Saatkamp](https://github.com/helmuthdu)
55
+ - Typed translation keys from your message tree
56
+ - Dot-notation nested key lookup
57
+ - ICU-style interpolation with object/array path support
58
+ - Plural messages (`zero/one/two/few/many/other`) via `Intl.PluralRules`
59
+ - Locale chain fallback (`sr-Latn-RS -> sr-Latn -> sr`) + configured fallback locales
60
+ - Async locale loading (`load`, `setLocale`, `registerLoader`, `reload`)
61
+ - Catalog updates (`add` deep-merge, `replace` full replace)
62
+ - Subscription API with batched notifications (`batch`, `subscribe`)
63
+ - Intl format helpers (`number`, `date`, `list`, `relative`, `currency`)
64
+ - Namespace and locale-bound views (`scope`, `withLocale`)
65
+ - Diagnostic hooks (`onDiagnostic`) and missing-key hook (`onMissing`)
842
66
 
843
- ## 🤝 Contributing
67
+ ## API At a Glance
844
68
 
845
- Contributions are welcome! Check our [GitHub repository](https://github.com/helmuthdu/vielzeug).
69
+ - `createI18n<T>(options?) => I18n<T>`
70
+ - `class I18n<T> implements BoundI18n<T>`
71
+ - `type BoundI18n<T>`
72
+ - `type I18nOptions<T>`
73
+ - `type Messages`, `TranslationKey`, `TranslationKeyParam`, `PluralKeys`, `NamespaceKeys`
846
74
 
847
- ## 🔗 Links
75
+ ## Documentation
848
76
 
849
- - [GitHub Repository](https://github.com/helmuthdu/vielzeug)
850
- - [Documentation](https://helmuthdu.github.io/vielzeug/deposit)
851
- - [NPM Package](https://www.npmjs.com/package/@vielzeug/deposit)
852
- - [Issue Tracker](https://github.com/helmuthdu/vielzeug/issues)
77
+ - [Overview](https://vielzeug.dev/i18nit/)
78
+ - [Usage Guide](https://vielzeug.dev/i18nit/usage)
79
+ - [API Reference](https://vielzeug.dev/i18nit/api)
80
+ - [Examples](https://vielzeug.dev/i18nit/examples)
853
81
 
854
- ---
82
+ ## License
855
83
 
856
- Part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) ecosystem – A collection of type-safe utilities for modern web development.
84
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.