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