@minejs/i18n 0.0.1
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/LICENSE +21 -0
- package/README.md +736 -0
- package/dist/main.cjs +2 -0
- package/dist/main.cjs.map +1 -0
- package/dist/main.d.cts +292 -0
- package/dist/main.d.ts +292 -0
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
<!-- ╔══════════════════════════════ BEG ══════════════════════════════╗ -->
|
|
2
|
+
|
|
3
|
+
<br>
|
|
4
|
+
<div align="center">
|
|
5
|
+
<p>
|
|
6
|
+
<img src="./assets/img/logo.png" alt="logo" style="" height="60" />
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.0.1-black"/>
|
|
12
|
+
<img src="https://img.shields.io/badge/🔥-@minejs-black"/>
|
|
13
|
+
<img src="https://img.shields.io/badge/zero-dependencies-black" alt="Test Coverage" />
|
|
14
|
+
<br>
|
|
15
|
+
<img src="https://img.shields.io/badge/coverage-95.19%25-brightgreen" alt="Test Coverage" />
|
|
16
|
+
<img src="https://img.shields.io/github/issues/minejs/i18n?style=flat" alt="Github Repo Issues" />
|
|
17
|
+
<img src="https://img.shields.io/github/stars/minejs/i18n?style=social" alt="GitHub Repo stars" />
|
|
18
|
+
</div>
|
|
19
|
+
<br>
|
|
20
|
+
|
|
21
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
<!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
|
|
26
|
+
|
|
27
|
+
- ## Quick Start 🔥
|
|
28
|
+
|
|
29
|
+
> **_A lightweight, production-ready internationalization (i18n) library with zero dependencies._**
|
|
30
|
+
|
|
31
|
+
- ### Setup
|
|
32
|
+
|
|
33
|
+
> install [`space`](https://github.com/solution-lib/space) first.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
space i @minejs/i18n
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
40
|
+
|
|
41
|
+
- ### Usage
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { I18nManager, setupI18n, t, setLanguage } from '@minejs/i18n'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- ### 1. Basic Translation
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { I18nManager } from '@minejs/i18n'
|
|
51
|
+
|
|
52
|
+
// Create a manager instance
|
|
53
|
+
const i18n = new I18nManager()
|
|
54
|
+
|
|
55
|
+
// Load translations
|
|
56
|
+
i18n.loadLanguage('en', {
|
|
57
|
+
greeting: 'Hello',
|
|
58
|
+
welcome: 'Welcome to our app'
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
i18n.loadLanguage('ar', {
|
|
62
|
+
greeting: 'مرحبا',
|
|
63
|
+
welcome: 'مرحبا بك في تطبيقنا'
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Translate
|
|
67
|
+
console.log(i18n.t('greeting')) // "Hello"
|
|
68
|
+
|
|
69
|
+
// Switch language
|
|
70
|
+
await i18n.setLanguage('ar')
|
|
71
|
+
console.log(i18n.t('greeting')) // "مرحبا"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- ### 2. Parameter Replacement
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const i18n = new I18nManager()
|
|
78
|
+
|
|
79
|
+
i18n.loadLanguage('en', {
|
|
80
|
+
message: 'Hello {name}, you have {count} messages',
|
|
81
|
+
userName: 'John'
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Replace parameters
|
|
85
|
+
const result = i18n.t('message', {
|
|
86
|
+
name: 'John',
|
|
87
|
+
count: '5'
|
|
88
|
+
})
|
|
89
|
+
// "Hello John, you have 5 messages"
|
|
90
|
+
|
|
91
|
+
// Parameters can reference other translation keys
|
|
92
|
+
const greeting = i18n.t('message', {
|
|
93
|
+
name: 'userName', // References i18n.t('userName')
|
|
94
|
+
count: '3'
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- ### 3. Nested Translations
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const i18n = new I18nManager()
|
|
102
|
+
|
|
103
|
+
// Deeply nested structures are flattened automatically
|
|
104
|
+
i18n.loadLanguage('en', {
|
|
105
|
+
app: {
|
|
106
|
+
title: 'My App',
|
|
107
|
+
pages: {
|
|
108
|
+
home: {
|
|
109
|
+
title: 'Home',
|
|
110
|
+
description: 'Welcome to home page'
|
|
111
|
+
},
|
|
112
|
+
about: {
|
|
113
|
+
title: 'About Us'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
console.log(i18n.t('app.title')) // "My App"
|
|
120
|
+
console.log(i18n.t('app.pages.home.title')) // "Home"
|
|
121
|
+
console.log(i18n.t('app.pages.home.description')) // "Welcome to home page"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- ### 4. Parse HTML Tags
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const i18n = new I18nManager()
|
|
128
|
+
|
|
129
|
+
i18n.loadLanguage('en', {
|
|
130
|
+
message: 'Hello <strong>World</strong>'
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Parse HTML tags into tokens
|
|
134
|
+
const tokens = i18n.tParse('message')
|
|
135
|
+
// [
|
|
136
|
+
// { type: 'text', content: 'Hello ' },
|
|
137
|
+
// { type: 'tag', tag: 'strong', content: 'World' }
|
|
138
|
+
// ]
|
|
139
|
+
|
|
140
|
+
// Convert newlines to <br> tags
|
|
141
|
+
i18n.loadLanguage('en', {
|
|
142
|
+
multiline: 'Line 1\\nLine 2\\nLine 3'
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const lines = i18n.tParse('multiline')
|
|
146
|
+
// Includes <br> tags for newlines
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- ### 5. RTL Language Support
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const i18n = new I18nManager()
|
|
153
|
+
|
|
154
|
+
i18n.loadLanguage('en', { title: 'My App' })
|
|
155
|
+
i18n.loadLanguage('ar', { title: 'تطبيقي' })
|
|
156
|
+
|
|
157
|
+
// Check if current language is RTL
|
|
158
|
+
await i18n.setLanguage('ar')
|
|
159
|
+
console.log(i18n.isRTL()) // true
|
|
160
|
+
|
|
161
|
+
// Check specific language
|
|
162
|
+
console.log(i18n.isRTLLanguage('ar')) // true
|
|
163
|
+
console.log(i18n.isRTLLanguage('en')) // false
|
|
164
|
+
|
|
165
|
+
// Supported RTL languages: ar, he, fa, ur, yi, ji, iw, ku
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- ### 6. Language Change Events
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const i18n = new I18nManager()
|
|
172
|
+
|
|
173
|
+
i18n.loadLanguage('en', { greeting: 'Hello' })
|
|
174
|
+
i18n.loadLanguage('ar', { greeting: 'مرحبا' })
|
|
175
|
+
|
|
176
|
+
// Subscribe to language changes
|
|
177
|
+
const unsubscribe = i18n.onChange((lang) => {
|
|
178
|
+
console.log('Language changed to:', lang)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
await i18n.setLanguage('ar')
|
|
182
|
+
// Logs: "Language changed to: ar"
|
|
183
|
+
|
|
184
|
+
// Unsubscribe
|
|
185
|
+
unsubscribe()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- ### 7. Storage Integration
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { memoryStorage, browserStorage } from '@minejs/i18n'
|
|
192
|
+
|
|
193
|
+
// Use browser localStorage (automatically persists language preference)
|
|
194
|
+
const i18n = new I18nManager({
|
|
195
|
+
defaultLanguage: 'en',
|
|
196
|
+
supportedLanguages: ['en', 'ar', 'fr'],
|
|
197
|
+
storage: browserStorage // Or memoryStorage for server
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// Language preference is saved and restored automatically
|
|
201
|
+
await i18n.setLanguage('ar')
|
|
202
|
+
await i18n.init() // Restores 'ar' from storage
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- ### 8. Global Instance
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import {
|
|
209
|
+
t, tLang, setLanguage, getLanguage,
|
|
210
|
+
isRTL, hasKey, onChange, loadLanguage,
|
|
211
|
+
loadTranslations
|
|
212
|
+
} from '@minejs/i18n'
|
|
213
|
+
|
|
214
|
+
// Use global instance with convenience functions
|
|
215
|
+
loadLanguage('en', {
|
|
216
|
+
greeting: 'Hello',
|
|
217
|
+
farewell: 'Goodbye'
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
console.log(t('greeting')) // "Hello"
|
|
221
|
+
console.log(hasKey('greeting')) // true
|
|
222
|
+
console.log(getLanguage()) // "en"
|
|
223
|
+
|
|
224
|
+
// Translate with specific language
|
|
225
|
+
loadLanguage('ar', { greeting: 'مرحبا' })
|
|
226
|
+
console.log(tLang('greeting', 'ar')) // "مرحبا"
|
|
227
|
+
|
|
228
|
+
// Listen for changes
|
|
229
|
+
onChange((lang) => {
|
|
230
|
+
console.log('Switched to:', lang)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
await setLanguage('ar')
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- ### 9. Lazy Loading
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { setupLazy } from '@minejs/i18n'
|
|
240
|
+
|
|
241
|
+
// Only load default language at startup
|
|
242
|
+
const loader = await setupLazy({
|
|
243
|
+
defaultLanguage: 'en',
|
|
244
|
+
supportedLanguages: ['en', 'ar', 'fr', 'de', 'zh'],
|
|
245
|
+
basePath: 'https://cdn.example.com/i18n/'
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// Later, when user switches language
|
|
249
|
+
await loader.load('ar')
|
|
250
|
+
await setLanguage('ar')
|
|
251
|
+
|
|
252
|
+
// Check if language is already loaded
|
|
253
|
+
if (!loader.isLoaded('fr')) {
|
|
254
|
+
await loader.load('fr')
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
- ### 10. Auto-Setup: Environment-Aware Loading
|
|
259
|
+
|
|
260
|
+
> Automatically detects browser vs server environment and loads translations from files or URLs
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { setupAuto } from '@minejs/i18n'
|
|
264
|
+
|
|
265
|
+
// Browser: Fetches from http://localhost:3000/static/i18n/en.json
|
|
266
|
+
// Server: Reads from ./static/i18n/en.json
|
|
267
|
+
const loader = await setupAuto({
|
|
268
|
+
defaultLanguage: 'en',
|
|
269
|
+
supportedLanguages: ['en', 'ar', 'fr', 'de'],
|
|
270
|
+
basePath: 'http://localhost:3000/static/i18n/',
|
|
271
|
+
// For server-side, use:
|
|
272
|
+
// basePath: './static/i18n/',
|
|
273
|
+
fileExtension: 'json' // Optional, defaults to 'json'
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Load other languages on-demand
|
|
277
|
+
await loader.load('ar')
|
|
278
|
+
await setLanguage('ar')
|
|
279
|
+
|
|
280
|
+
// File structure example:
|
|
281
|
+
// Browser: /public/static/i18n/en.json
|
|
282
|
+
// Server: ./static/i18n/en.json
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Translation file format** (`en.json`):
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"greeting": "Hello",
|
|
289
|
+
"welcome": "Welcome to our app",
|
|
290
|
+
"app": {
|
|
291
|
+
"name": "MyApp",
|
|
292
|
+
"title": "Welcome"
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- ### 11. Utility Functions
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { genPageTitle, plural } from '@minejs/i18n'
|
|
301
|
+
|
|
302
|
+
// Generate page title with app name
|
|
303
|
+
const title = genPageTitle('home', 'page.')
|
|
304
|
+
// LTR: "Home - MyApp"
|
|
305
|
+
// RTL: "MyApp - الرئيسية"
|
|
306
|
+
|
|
307
|
+
// Pluralization
|
|
308
|
+
const itemCount = plural(1, 'item.single', 'item.plural')
|
|
309
|
+
// 1 → "1 item"
|
|
310
|
+
// 5 → "5 items"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
<br>
|
|
315
|
+
|
|
316
|
+
- ## API Reference 🔥
|
|
317
|
+
|
|
318
|
+
- ### I18nManager Class
|
|
319
|
+
|
|
320
|
+
- #### `constructor(config?: I18nConfig)`
|
|
321
|
+
> Create a new I18n manager instance.
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const i18n = new I18nManager({
|
|
325
|
+
defaultLanguage: 'en',
|
|
326
|
+
fallbackLanguage: 'en',
|
|
327
|
+
supportedLanguages: ['en', 'ar', 'fr'],
|
|
328
|
+
storage: browserStorage,
|
|
329
|
+
onLanguageChange: (lang) => console.log('Changed to:', lang)
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
- #### `init(): Promise<void>`
|
|
334
|
+
> Initialize manager and restore language from storage.
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
const i18n = new I18nManager({ storage: browserStorage })
|
|
338
|
+
await i18n.init() // Restores saved language
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
- #### `loadLanguage(lang: LanguageCode, translations: object): void`
|
|
342
|
+
> Load translations for a specific language. Nested objects are flattened.
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
i18n.loadLanguage('en', {
|
|
346
|
+
app: { name: 'MyApp' },
|
|
347
|
+
greeting: 'Hello'
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
i18n.t('app.name') // "MyApp"
|
|
351
|
+
i18n.t('greeting') // "Hello"
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
- #### `loadTranslations(translations: TranslationSet): void`
|
|
355
|
+
> Load multiple languages at once.
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
i18n.loadTranslations({
|
|
359
|
+
en: { greeting: 'Hello' },
|
|
360
|
+
ar: { greeting: 'مرحبا' },
|
|
361
|
+
fr: { greeting: 'Bonjour' }
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
- #### `t(key: string, params?: object): string`
|
|
366
|
+
> Translate a key with optional parameter replacement.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
i18n.loadLanguage('en', {
|
|
370
|
+
greeting: 'Hello {name}'
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
i18n.t('greeting', { name: 'John' }) // "Hello John"
|
|
374
|
+
i18n.t('missing.key') // "missing.key" (returns key if not found)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
- #### `tLang(key: string, lang: LanguageCode, params?: object): string`
|
|
378
|
+
> Translate a key with a specific language temporarily.
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
i18n.loadLanguage('ar', { greeting: 'مرحبا' })
|
|
382
|
+
|
|
383
|
+
const result = i18n.tLang('greeting', 'ar')
|
|
384
|
+
// Returns Arabic translation without changing current language
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
- #### `tParse(key: string, params?: object): TranslationToken[]`
|
|
388
|
+
> Parse translation with HTML tags into tokens. Converts \n and /n to <br> tags.
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
i18n.loadLanguage('en', {
|
|
392
|
+
message: 'Hello <strong>World</strong>\\nLine 2'
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const tokens = i18n.tParse('message')
|
|
396
|
+
// [
|
|
397
|
+
// { type: 'text', content: 'Hello ' },
|
|
398
|
+
// { type: 'tag', tag: 'strong', content: 'World' },
|
|
399
|
+
// { type: 'tag', tag: 'br', content: '' },
|
|
400
|
+
// { type: 'text', content: 'Line 2' }
|
|
401
|
+
// ]
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
- #### `setLanguage(lang: LanguageCode): Promise<void>`
|
|
405
|
+
> Set current language and trigger change listeners.
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
await i18n.setLanguage('ar')
|
|
409
|
+
// Language is changed and persisted to storage if available
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
- #### `getLanguage(): LanguageCode`
|
|
413
|
+
> Get current language code.
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
const lang = i18n.getLanguage() // "en"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
- #### `getSupportedLanguages(): LanguageCode[]`
|
|
420
|
+
> Get array of all supported languages.
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
const langs = i18n.getSupportedLanguages() // ['en', 'ar', 'fr']
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
- #### `isLanguageSupported(lang: LanguageCode): boolean`
|
|
427
|
+
> Check if a language is supported.
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
i18n.isLanguageSupported('ar') // true
|
|
431
|
+
i18n.isLanguageSupported('unsupported') // false
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
- #### `hasKey(key: string): boolean`
|
|
435
|
+
> Check if translation key exists in current or fallback language.
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
i18n.hasKey('greeting') // true
|
|
439
|
+
i18n.hasKey('missing') // false
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
- #### `getTranslations(): Record<string, string>`
|
|
443
|
+
> Get all translations for current language.
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
const all = i18n.getTranslations()
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
- #### `isRTL(): boolean`
|
|
450
|
+
> Check if current language is right-to-left.
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
await i18n.setLanguage('ar')
|
|
454
|
+
i18n.isRTL() // true
|
|
455
|
+
|
|
456
|
+
await i18n.setLanguage('en')
|
|
457
|
+
i18n.isRTL() // false
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
- #### `isRTLLanguage(lang: LanguageCode): boolean`
|
|
461
|
+
> Check if specific language is right-to-left.
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
i18n.isRTLLanguage('ar') // true
|
|
465
|
+
i18n.isRTLLanguage('he') // true
|
|
466
|
+
i18n.isRTLLanguage('en') // false
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
- #### `onChange(callback: (lang: LanguageCode) => void): () => void`
|
|
470
|
+
> Subscribe to language changes. Returns unsubscribe function.
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
const unsubscribe = i18n.onChange((lang) => {
|
|
474
|
+
console.log('Language changed to:', lang)
|
|
475
|
+
updateUI(lang)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
// Unsubscribe later
|
|
479
|
+
unsubscribe()
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
<br>
|
|
483
|
+
|
|
484
|
+
- ### LazyLoader Class
|
|
485
|
+
|
|
486
|
+
- #### `constructor(baseUrl: string, manager: I18nManager)`
|
|
487
|
+
> Create a lazy loader for on-demand language loading.
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
const manager = new I18nManager({
|
|
491
|
+
supportedLanguages: ['en', 'ar', 'fr']
|
|
492
|
+
})
|
|
493
|
+
const loader = new LazyLoader('https://cdn.com/i18n/', manager)
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
- #### `load(lang: LanguageCode): Promise<void>`
|
|
497
|
+
> Load a language file on-demand. Caches promises to prevent duplicate requests.
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
await loader.load('ar')
|
|
501
|
+
// Expects https://cdn.com/i18n/ar.json
|
|
502
|
+
|
|
503
|
+
// Subsequent calls return cached promise
|
|
504
|
+
await loader.load('ar') // Returns immediately
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
- #### `isLoaded(lang: LanguageCode): boolean`
|
|
508
|
+
> Check if language is already loaded.
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
if (!loader.isLoaded('ar')) {
|
|
512
|
+
await loader.load('ar')
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
<br>
|
|
517
|
+
|
|
518
|
+
- ### Storage Adapters
|
|
519
|
+
|
|
520
|
+
- #### `browserStorage: I18nStorage`
|
|
521
|
+
> Store language preference in browser localStorage.
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import { browserStorage } from '@minejs/i18n'
|
|
525
|
+
|
|
526
|
+
const i18n = new I18nManager({
|
|
527
|
+
storage: browserStorage
|
|
528
|
+
})
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
- #### `memoryStorage: I18nStorage`
|
|
532
|
+
> In-memory storage (useful for SSR and testing).
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
import { memoryStorage } from '@minejs/i18n'
|
|
536
|
+
|
|
537
|
+
const i18n = new I18nManager({
|
|
538
|
+
storage: memoryStorage
|
|
539
|
+
})
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
- #### `fetchTranslations(urls: string | string[], manager: I18nManager): Promise<void>`
|
|
543
|
+
> Fetch translations from remote URLs. Extracts language from filename (e.g., `en.json`).
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
import { fetchTranslations } from '@minejs/i18n'
|
|
547
|
+
|
|
548
|
+
const manager = new I18nManager()
|
|
549
|
+
|
|
550
|
+
// Single URL
|
|
551
|
+
await fetchTranslations('https://cdn.com/i18n/en.json', manager)
|
|
552
|
+
|
|
553
|
+
// Multiple URLs
|
|
554
|
+
await fetchTranslations([
|
|
555
|
+
'https://cdn.com/i18n/en.json',
|
|
556
|
+
'https://cdn.com/i18n/ar.json'
|
|
557
|
+
], manager)
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
<br>
|
|
561
|
+
|
|
562
|
+
- ### Global Instance Functions
|
|
563
|
+
|
|
564
|
+
- #### `getI18n(): I18nManager`
|
|
565
|
+
> Get or create global I18n instance.
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
const i18n = getI18n()
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
- #### `setupI18n(config: I18nConfig): Promise<I18nManager>`
|
|
572
|
+
> Initialize global instance with config.
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
const i18n = await setupI18n({
|
|
576
|
+
defaultLanguage: 'en',
|
|
577
|
+
supportedLanguages: ['en', 'ar', 'fr']
|
|
578
|
+
})
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
- #### `createLazyLoader(baseUrl: string, fileExtension?: string): LazyLoader`
|
|
582
|
+
> Create lazy loader for global instance with support for custom file extensions.
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
const loader = createLazyLoader('https://cdn.com/i18n/', 'json')
|
|
586
|
+
await loader.load('ar')
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
- #### `setupLazy(config: I18nConfig & {basePath?: string; baseUrl?: string}): Promise<LazyLoader>`
|
|
590
|
+
> Setup with lazy loading and load default language only. Supports both file paths and URLs.
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
const loader = await setupLazy({
|
|
594
|
+
defaultLanguage: 'en',
|
|
595
|
+
supportedLanguages: ['en', 'ar', 'fr'],
|
|
596
|
+
basePath: '/i18n/'
|
|
597
|
+
})
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
- #### `setupAuto(config: I18nConfig & {basePath: string}): Promise<LazyLoader>`
|
|
601
|
+
> Auto-setup with environment detection. Automatically uses fetch in browser and file system on server.
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
// Works in both browser and Node.js
|
|
605
|
+
const loader = await setupAuto({
|
|
606
|
+
defaultLanguage: 'en',
|
|
607
|
+
supportedLanguages: ['en', 'ar', 'fr'],
|
|
608
|
+
basePath: 'http://localhost:3000/i18n/',
|
|
609
|
+
fileExtension: 'json'
|
|
610
|
+
})
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
const loader = await setupLazy({
|
|
615
|
+
defaultLanguage: 'en',
|
|
616
|
+
supportedLanguages: ['en', 'ar', 'fr'],
|
|
617
|
+
baseUrl: 'https://cdn.com/i18n/'
|
|
618
|
+
})
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
<br>
|
|
622
|
+
|
|
623
|
+
- ### Convenience Functions (Global Instance)
|
|
624
|
+
|
|
625
|
+
- #### `t(key: string, params?: object): string`
|
|
626
|
+
> Translate using global instance.
|
|
627
|
+
|
|
628
|
+
- #### `tLang(key: string, lang: LanguageCode, params?: object): string`
|
|
629
|
+
> Translate with specific language using global instance.
|
|
630
|
+
|
|
631
|
+
- #### `tParse(key: string, params?: object): TranslationToken[]`
|
|
632
|
+
> Parse translation using global instance.
|
|
633
|
+
|
|
634
|
+
- #### `setLanguage(lang: LanguageCode): Promise<void>`
|
|
635
|
+
> Set language on global instance.
|
|
636
|
+
|
|
637
|
+
- #### `getLanguage(): LanguageCode`
|
|
638
|
+
> Get current language from global instance.
|
|
639
|
+
|
|
640
|
+
- #### `getSupportedLanguages(): LanguageCode[]`
|
|
641
|
+
> Get supported languages from global instance.
|
|
642
|
+
|
|
643
|
+
- #### `hasKey(key: string): boolean`
|
|
644
|
+
> Check if key exists in global instance.
|
|
645
|
+
|
|
646
|
+
- #### `isRTL(): boolean`
|
|
647
|
+
> Check if current language is RTL on global instance.
|
|
648
|
+
|
|
649
|
+
- #### `isRTLLanguage(lang: LanguageCode): boolean`
|
|
650
|
+
> Check if specific language is RTL.
|
|
651
|
+
|
|
652
|
+
- #### `onChange(callback: (lang: LanguageCode) => void): () => void`
|
|
653
|
+
> Subscribe to language changes on global instance.
|
|
654
|
+
|
|
655
|
+
- #### `loadLanguage(lang: LanguageCode, translations: object): void`
|
|
656
|
+
> Load translations using global instance.
|
|
657
|
+
|
|
658
|
+
- #### `loadTranslations(translations: TranslationSet): void`
|
|
659
|
+
> Load multiple languages using global instance.
|
|
660
|
+
|
|
661
|
+
<br>
|
|
662
|
+
|
|
663
|
+
- ### Utility Functions
|
|
664
|
+
|
|
665
|
+
- #### `genPageTitle(key: string, prefix?: string): string`
|
|
666
|
+
> Generate page title with app name and proper RTL formatting.
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
// LTR: "Page Name - App Name"
|
|
670
|
+
// RTL: "App Name - Page Name"
|
|
671
|
+
|
|
672
|
+
const title = genPageTitle('home', 'page.')
|
|
673
|
+
// Translates 'page.home' and 'app.name'
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
- #### `plural(count: number, singleKey: string, pluralKey: string): string`
|
|
677
|
+
> Select translation based on count and replace {count} parameter.
|
|
678
|
+
|
|
679
|
+
```typescript
|
|
680
|
+
loadLanguage('en', {
|
|
681
|
+
'item.single': '1 item',
|
|
682
|
+
'item.plural': '{count} items'
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
plural(1, 'item.single', 'item.plural') // "1 item"
|
|
686
|
+
plural(5, 'item.single', 'item.plural') // "5 items"
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
<br>
|
|
690
|
+
|
|
691
|
+
- ## Types 📘
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
type LanguageCode = string
|
|
695
|
+
|
|
696
|
+
interface I18nConfig {
|
|
697
|
+
defaultLanguage?: LanguageCode
|
|
698
|
+
supportedLanguages?: LanguageCode[]
|
|
699
|
+
fallbackLanguage?: LanguageCode
|
|
700
|
+
onLanguageChange?: (lang: LanguageCode) => void
|
|
701
|
+
storage?: I18nStorage
|
|
702
|
+
basePath?: string
|
|
703
|
+
fileExtension?: string
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
interface I18nStorage {
|
|
707
|
+
get(key: string): string | null | Promise<string | null>
|
|
708
|
+
set(key: string, value: string): void | Promise<void>
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
interface TranslationToken {
|
|
712
|
+
type: 'text' | 'tag'
|
|
713
|
+
tag?: string
|
|
714
|
+
content: string
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
type TranslationSet = Record<string, Record<string, string>>
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
<br>
|
|
721
|
+
|
|
722
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
<!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
|
|
727
|
+
|
|
728
|
+
<br>
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
<div align="center">
|
|
733
|
+
<a href="https://github.com/maysara-elshewehy"><img src="https://img.shields.io/badge/by-Maysara-black"/></a>
|
|
734
|
+
</div>
|
|
735
|
+
|
|
736
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|