@minejs/i18n 0.0.4 β 0.0.6
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 +189 -584
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div align="center">
|
|
11
|
-
<img src="https://img.shields.io/badge/v-0.0.
|
|
12
|
-
<img src="https://img.shields.io/badge/π₯-@minejs-black"
|
|
13
|
-
<img src="https://img.shields.io/badge/zero-dependencies-black" alt="Test Coverage" />
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.0.6-black"/>
|
|
12
|
+
<a href="https://img.shields.io/github/stars/minejs-org"><img src="https://img.shields.io/badge/π₯-@minejs-black"/></a>
|
|
14
13
|
<br>
|
|
15
|
-
<img src="https://img.shields.io/badge/coverage-
|
|
14
|
+
<img src="https://img.shields.io/badge/coverage-94.40%25-brightgreen" alt="Test Coverage" />
|
|
16
15
|
<img src="https://img.shields.io/github/issues/minejs-org/i18n?style=flat" alt="Github Repo Issues" />
|
|
17
16
|
<img src="https://img.shields.io/github/stars/minejs-org/i18n?style=social" alt="GitHub Repo stars" />
|
|
18
17
|
</div>
|
|
@@ -24,700 +23,305 @@
|
|
|
24
23
|
|
|
25
24
|
<!-- βββββββββββββββββββββββββββββββ DOC βββββββββββββββββββββββββββββββ -->
|
|
26
25
|
|
|
27
|
-
- ##
|
|
28
|
-
|
|
29
|
-
> **_A lightweight, production-ready internationalization (i18n) library with zero dependencies._**
|
|
30
|
-
|
|
31
|
-
- ### Setup
|
|
32
|
-
|
|
33
|
-
> install [`hmm`](https://github.com/minejs-org/hmm) first.
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
hmm i @minejs/i18n
|
|
37
|
-
```
|
|
26
|
+
- ## Overview π
|
|
38
27
|
|
|
39
|
-
|
|
28
|
+
- #### Why ?
|
|
29
|
+
> To unify the translation system and languages ββon the server and client, faster, cleaner, more maintainable.
|
|
40
30
|
|
|
41
|
-
-
|
|
31
|
+
- #### When ?
|
|
32
|
+
> When you need to add a translation to your server or client.
|
|
42
33
|
|
|
43
|
-
|
|
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"
|
|
34
|
+
> When you use [@cruxjs/app](https://github.com/cruxjs-org/app).
|
|
90
35
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
name: 'userName', // References i18n.t('userName')
|
|
94
|
-
count: '3'
|
|
95
|
-
})
|
|
96
|
-
```
|
|
36
|
+
<br>
|
|
37
|
+
<br>
|
|
97
38
|
|
|
98
|
-
|
|
39
|
+
- ## Quick Start π₯
|
|
99
40
|
|
|
100
|
-
|
|
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
|
-
```
|
|
41
|
+
> install [`hmm`](https://github.com/minejs-org/hmm) first.
|
|
123
42
|
|
|
124
|
-
|
|
43
|
+
```bash
|
|
44
|
+
# in your terminal
|
|
45
|
+
hmm i @minejs/i18n
|
|
46
|
+
```
|
|
125
47
|
|
|
126
|
-
|
|
127
|
-
const i18n = new I18nManager()
|
|
48
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
|
|
128
49
|
|
|
129
|
-
|
|
130
|
-
message: 'Hello <strong>World</strong>'
|
|
131
|
-
})
|
|
50
|
+
- #### Setup
|
|
132
51
|
|
|
133
|
-
|
|
134
|
-
const tokens = i18n.tParse('message')
|
|
135
|
-
// [
|
|
136
|
-
// { type: 'text', content: 'Hello ' },
|
|
137
|
-
// { type: 'tag', tag: 'strong', content: 'World' }
|
|
138
|
-
// ]
|
|
52
|
+
- ##### JSON
|
|
139
53
|
|
|
140
|
-
|
|
141
|
-
i18n.loadLanguage('en', {
|
|
142
|
-
multiline: 'Line 1\\nLine 2\\nLine 3'
|
|
143
|
-
})
|
|
54
|
+
> Save the translation **keys and their values** ββin `.json` files.
|
|
144
55
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
56
|
+
```jsonc
|
|
57
|
+
// ./src/shared/dist/u18n/en.json
|
|
58
|
+
{
|
|
59
|
+
"key": "value"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
148
62
|
|
|
149
|
-
|
|
63
|
+
```jsonc
|
|
64
|
+
// ./src/shared/dist/u18n/ar.json
|
|
65
|
+
{
|
|
66
|
+
"key": "ΩΩΩΩ
Ψ©"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
150
69
|
|
|
151
|
-
|
|
152
|
-
const i18n = new I18nManager()
|
|
70
|
+
- ##### I18n
|
|
153
71
|
|
|
154
|
-
|
|
155
|
-
i18n.loadLanguage('ar', { title: 'ΨͺΨ·Ψ¨ΩΩΩ' })
|
|
72
|
+
> ***π If you are using [`@cruxjs/app`](https://github.com/cruxjs-org/app) you can skip this step. π***
|
|
156
73
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
74
|
+
> Then in your project ***(works with any environment: `server`, `browser`, ..)***
|
|
75
|
+
>
|
|
76
|
+
> Call the `setupI18n(..)` function **only once** in your application's lifecycle :
|
|
160
77
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
78
|
+
```ts
|
|
79
|
+
import { setupI18n } from `@minejs/i18n`;
|
|
80
|
+
```
|
|
164
81
|
|
|
165
|
-
|
|
166
|
-
|
|
82
|
+
```ts
|
|
83
|
+
await this.setupI18n({
|
|
84
|
+
defaultLanguage : 'en',
|
|
85
|
+
supportedLanguages : ['en', 'ar'],
|
|
86
|
+
basePath : '/static/dist/i18n', // for client side (or your custom public url)
|
|
87
|
+
: './src/shared/dist/i18n', // for server side (or your custom local path)
|
|
88
|
+
});
|
|
89
|
+
```
|
|
167
90
|
|
|
168
|
-
|
|
91
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
|
|
92
|
+
<br>
|
|
169
93
|
|
|
170
|
-
|
|
171
|
-
const i18n = new I18nManager()
|
|
94
|
+
- #### Usage
|
|
172
95
|
|
|
173
|
-
|
|
174
|
-
i18n.loadLanguage('ar', { greeting: 'Ω
Ψ±ΨΨ¨Ψ§' })
|
|
96
|
+
> Now you can call the `t(..)` function anywhere in your project :
|
|
175
97
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
})
|
|
98
|
+
```tsx
|
|
99
|
+
import { t } from `@minejs/i18n`;
|
|
100
|
+
```
|
|
180
101
|
|
|
181
|
-
|
|
182
|
-
|
|
102
|
+
```ts
|
|
103
|
+
t('key', { params }, fallback) // just it !
|
|
104
|
+
```
|
|
183
105
|
|
|
184
|
-
|
|
185
|
-
unsubscribe()
|
|
186
|
-
```
|
|
106
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
|
|
187
107
|
|
|
188
|
-
-
|
|
108
|
+
- #### Language Switching
|
|
189
109
|
|
|
190
110
|
```typescript
|
|
191
|
-
import {
|
|
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
|
|
111
|
+
import { setLanguage, onChange } from '@minejs/i18n';
|
|
206
112
|
|
|
207
|
-
|
|
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
|
|
113
|
+
// Listen to changes
|
|
229
114
|
onChange((lang) => {
|
|
230
|
-
console.log('
|
|
231
|
-
|
|
115
|
+
console.log('Language changed to:', lang);
|
|
116
|
+
document.documentElement.lang = lang;
|
|
117
|
+
document.dir = isRTL() ? 'rtl' : 'ltr';
|
|
118
|
+
});
|
|
232
119
|
|
|
233
|
-
|
|
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
|
-
}
|
|
120
|
+
// Change language
|
|
121
|
+
await setLanguage('ar');
|
|
256
122
|
```
|
|
257
123
|
|
|
258
|
-
-
|
|
124
|
+
- #### Parameterized Translations
|
|
259
125
|
|
|
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
126
|
```json
|
|
287
127
|
{
|
|
288
|
-
"greeting": "Hello"
|
|
289
|
-
"welcome": "Welcome to our app",
|
|
290
|
-
"app": {
|
|
291
|
-
"name": "MyApp",
|
|
292
|
-
"title": "Welcome"
|
|
293
|
-
}
|
|
128
|
+
"greeting": "Hello {name}, you have {count} messages"
|
|
294
129
|
}
|
|
295
130
|
```
|
|
296
131
|
|
|
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
132
|
```typescript
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
i18n.t('greeting', { name: 'John' }) // "Hello John"
|
|
374
|
-
i18n.t('missing.key') // "missing.key" (returns key if not found)
|
|
133
|
+
t('greeting', { name: 'John', count: '5' })
|
|
134
|
+
// "Hello John, you have 5 messages"
|
|
375
135
|
```
|
|
376
136
|
|
|
377
|
-
- ####
|
|
378
|
-
> Translate a key with a specific language temporarily.
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
i18n.loadLanguage('ar', { greeting: 'Ω
Ψ±ΨΨ¨Ψ§' })
|
|
137
|
+
- #### HTML Tag Parsing
|
|
382
138
|
|
|
383
|
-
|
|
384
|
-
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"terms": "I agree to the <link>Terms of Service</link>"
|
|
142
|
+
}
|
|
385
143
|
```
|
|
386
144
|
|
|
387
|
-
- #### `tParse(key: string, params?: object): TranslationToken[]`
|
|
388
|
-
> Parse translation with HTML tags into tokens. Converts \n and /n to <br> tags.
|
|
389
|
-
|
|
390
145
|
```typescript
|
|
391
|
-
|
|
392
|
-
message: 'Hello <strong>World</strong>\\nLine 2'
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
const tokens = i18n.tParse('message')
|
|
146
|
+
const tokens = tParse('terms');
|
|
396
147
|
// [
|
|
397
|
-
// { type: 'text', content: '
|
|
398
|
-
// { type: 'tag', tag: '
|
|
399
|
-
// { type: 'tag', tag: 'br', content: '' },
|
|
400
|
-
// { type: 'text', content: 'Line 2' }
|
|
148
|
+
// { type: 'text', content: 'I agree to the ' },
|
|
149
|
+
// { type: 'tag', tag: 'link', content: 'Terms of Service' }
|
|
401
150
|
// ]
|
|
402
151
|
```
|
|
403
152
|
|
|
404
|
-
- ####
|
|
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.
|
|
153
|
+
- #### Page Titles (with RTL support)
|
|
471
154
|
|
|
472
155
|
```typescript
|
|
473
|
-
|
|
474
|
-
console.log('Language changed to:', lang)
|
|
475
|
-
updateUI(lang)
|
|
476
|
-
})
|
|
156
|
+
import { genPageTitle } from '@minejs/i18n';
|
|
477
157
|
|
|
478
|
-
//
|
|
479
|
-
|
|
158
|
+
// en: "Settings - MyApp"
|
|
159
|
+
// ar: "MyApp - Ψ§ΩΨ₯ΨΉΨ―Ψ§Ψ―Ψ§Ψͺ"
|
|
160
|
+
const title = genPageTitle('settings');
|
|
480
161
|
```
|
|
481
162
|
|
|
482
163
|
<br>
|
|
164
|
+
<br>
|
|
483
165
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
- #### `constructor(baseUrl: string, manager: I18nManager)`
|
|
487
|
-
> Create a lazy loader for on-demand language loading.
|
|
166
|
+
- ## Documentation π
|
|
488
167
|
|
|
489
|
-
|
|
490
|
-
const manager = new I18nManager({
|
|
491
|
-
supportedLanguages: ['en', 'ar', 'fr']
|
|
492
|
-
})
|
|
493
|
-
const loader = new LazyLoader('https://cdn.com/i18n/', manager)
|
|
494
|
-
```
|
|
168
|
+
- ### API
|
|
495
169
|
|
|
496
|
-
- ####
|
|
497
|
-
> Load a language file on-demand. Caches promises to prevent duplicate requests.
|
|
170
|
+
- #### Types
|
|
498
171
|
|
|
499
172
|
```typescript
|
|
500
|
-
|
|
501
|
-
// Expects https://cdn.com/i18n/ar.json
|
|
502
|
-
|
|
503
|
-
// Subsequent calls return cached promise
|
|
504
|
-
await loader.load('ar') // Returns immediately
|
|
173
|
+
type LanguageCode = string;
|
|
505
174
|
```
|
|
506
175
|
|
|
507
|
-
- #### `isLoaded(lang: LanguageCode): boolean`
|
|
508
|
-
> Check if language is already loaded.
|
|
509
|
-
|
|
510
176
|
```typescript
|
|
511
|
-
|
|
512
|
-
|
|
177
|
+
interface I18nConfig {
|
|
178
|
+
defaultLanguage? : LanguageCode;
|
|
179
|
+
supportedLanguages? : LanguageCode[];
|
|
180
|
+
fallbackLanguage? : LanguageCode;
|
|
181
|
+
onLanguageChange? : (lang: LanguageCode) => void;
|
|
182
|
+
storage? : I18nStorage;
|
|
183
|
+
basePath? : string;
|
|
184
|
+
fileExtension? : string;
|
|
513
185
|
}
|
|
514
186
|
```
|
|
515
187
|
|
|
516
|
-
<br>
|
|
517
|
-
|
|
518
|
-
- ### Storage Adapters
|
|
519
|
-
|
|
520
|
-
- #### `browserStorage: I18nStorage`
|
|
521
|
-
> Store language preference in browser localStorage.
|
|
522
|
-
|
|
523
188
|
```typescript
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
189
|
+
interface TranslationToken {
|
|
190
|
+
type : 'text' | 'tag';
|
|
191
|
+
tag? : string;
|
|
192
|
+
content : string;
|
|
193
|
+
}
|
|
529
194
|
```
|
|
530
195
|
|
|
531
|
-
|
|
532
|
-
> In-memory storage (useful for SSR and testing).
|
|
196
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
|
|
533
197
|
|
|
534
|
-
|
|
535
|
-
import { memoryStorage } from '@minejs/i18n'
|
|
198
|
+
- #### Functions
|
|
536
199
|
|
|
537
|
-
|
|
538
|
-
storage: memoryStorage
|
|
539
|
-
})
|
|
540
|
-
```
|
|
200
|
+
- #### `setupI18n(config)`
|
|
541
201
|
|
|
542
|
-
|
|
543
|
-
> Fetch translations from remote URLs. Extracts language from filename (e.g., `en.json`).
|
|
202
|
+
> Initialize i18n with auto-detection
|
|
544
203
|
|
|
545
|
-
|
|
546
|
-
|
|
204
|
+
```typescript
|
|
205
|
+
await setupI18n({
|
|
206
|
+
defaultLanguage : 'en',
|
|
207
|
+
supportedLanguages : ['en', 'ar', 'fr'],
|
|
208
|
+
basePath : '/i18n/', // URL (browser) or path (server)
|
|
209
|
+
fileExtension : 'json' // optional
|
|
210
|
+
});
|
|
211
|
+
```
|
|
547
212
|
|
|
548
|
-
|
|
213
|
+
- #### `t(key, params?)`
|
|
549
214
|
|
|
550
|
-
|
|
551
|
-
await fetchTranslations('https://cdn.com/i18n/en.json', manager)
|
|
215
|
+
> Translate with parameter replacement
|
|
552
216
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
'https://cdn.com/i18n/ar.json'
|
|
557
|
-
], manager)
|
|
558
|
-
```
|
|
217
|
+
```typescript
|
|
218
|
+
t('greeting', { name: 'John' }) // "Hello John"
|
|
219
|
+
```
|
|
559
220
|
|
|
560
|
-
|
|
221
|
+
- #### `tLang(key, lang, params?)`
|
|
561
222
|
|
|
562
|
-
|
|
223
|
+
> Translate with specific language
|
|
563
224
|
|
|
564
|
-
|
|
565
|
-
|
|
225
|
+
```typescript
|
|
226
|
+
tLang('greeting', 'ar', { name: 'Ψ£ΨΩ
Ψ―' })
|
|
227
|
+
```
|
|
566
228
|
|
|
567
|
-
|
|
568
|
-
const i18n = getI18n()
|
|
569
|
-
```
|
|
229
|
+
- #### `tParse(key, params?)`
|
|
570
230
|
|
|
571
|
-
|
|
572
|
-
> Initialize global instance with config.
|
|
231
|
+
> Parse translation with HTML tags
|
|
573
232
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
supportedLanguages: ['en', 'ar', 'fr']
|
|
578
|
-
})
|
|
579
|
-
```
|
|
233
|
+
```typescript
|
|
234
|
+
tParse('message') // Returns TokenArray
|
|
235
|
+
```
|
|
580
236
|
|
|
581
|
-
|
|
582
|
-
> Create lazy loader for global instance with support for custom file extensions.
|
|
237
|
+
- #### `setLanguage(lang)`
|
|
583
238
|
|
|
584
|
-
|
|
585
|
-
const loader = createLazyLoader('https://cdn.com/i18n/', 'json')
|
|
586
|
-
await loader.load('ar')
|
|
587
|
-
```
|
|
239
|
+
> Change current language
|
|
588
240
|
|
|
589
|
-
|
|
590
|
-
|
|
241
|
+
```typescript
|
|
242
|
+
await setLanguage('ar')
|
|
243
|
+
```
|
|
591
244
|
|
|
592
|
-
|
|
593
|
-
const loader = await setupLazy({
|
|
594
|
-
defaultLanguage: 'en',
|
|
595
|
-
supportedLanguages: ['en', 'ar', 'fr'],
|
|
596
|
-
basePath: '/i18n/'
|
|
597
|
-
})
|
|
598
|
-
```
|
|
245
|
+
- #### `getLanguage()`
|
|
599
246
|
|
|
600
|
-
|
|
601
|
-
> Auto-setup with environment detection. Automatically uses fetch in browser and file system on server.
|
|
247
|
+
> Get current language code
|
|
602
248
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
defaultLanguage: 'en',
|
|
607
|
-
supportedLanguages: ['en', 'ar', 'fr'],
|
|
608
|
-
basePath: 'http://localhost:3000/i18n/',
|
|
609
|
-
fileExtension: 'json'
|
|
610
|
-
})
|
|
611
|
-
```
|
|
249
|
+
```typescript
|
|
250
|
+
const lang = getLanguage() // 'en'
|
|
251
|
+
```
|
|
612
252
|
|
|
613
|
-
|
|
614
|
-
const loader = await setupLazy({
|
|
615
|
-
defaultLanguage: 'en',
|
|
616
|
-
supportedLanguages: ['en', 'ar', 'fr'],
|
|
617
|
-
baseUrl: 'https://cdn.com/i18n/'
|
|
618
|
-
})
|
|
619
|
-
```
|
|
253
|
+
- #### `getSupportedLanguages()`
|
|
620
254
|
|
|
621
|
-
|
|
255
|
+
> Get all supported languages
|
|
622
256
|
|
|
623
|
-
|
|
257
|
+
```typescript
|
|
258
|
+
const langs = getSupportedLanguages() // ['en', 'ar', 'fr']
|
|
259
|
+
```
|
|
624
260
|
|
|
625
|
-
|
|
626
|
-
> Translate using global instance.
|
|
261
|
+
- #### `isRTL()`
|
|
627
262
|
|
|
628
|
-
|
|
629
|
-
> Translate with specific language using global instance.
|
|
263
|
+
> Check if current language is RTL
|
|
630
264
|
|
|
631
|
-
|
|
632
|
-
|
|
265
|
+
```typescript
|
|
266
|
+
if (isRTL()) { /* Handle RTL layout */ }
|
|
267
|
+
```
|
|
633
268
|
|
|
634
|
-
|
|
635
|
-
> Set language on global instance.
|
|
269
|
+
- #### `onChange(callback)`
|
|
636
270
|
|
|
637
|
-
|
|
638
|
-
> Get current language from global instance.
|
|
271
|
+
> Subscribe to language changes
|
|
639
272
|
|
|
640
|
-
|
|
641
|
-
|
|
273
|
+
```typescript
|
|
274
|
+
const unsubscribe = onChange((lang) => console.log('Changed to:', lang))
|
|
275
|
+
```
|
|
642
276
|
|
|
643
|
-
|
|
644
|
-
> Check if key exists in global instance.
|
|
277
|
+
- #### `genPageTitle(key, prefix?)`
|
|
645
278
|
|
|
646
|
-
|
|
647
|
-
> Check if current language is RTL on global instance.
|
|
279
|
+
> Generate page title with app name
|
|
648
280
|
|
|
649
|
-
|
|
650
|
-
|
|
281
|
+
```typescript
|
|
282
|
+
genPageTitle('home') // "Home - MyApp" or "MyApp - Ψ§ΩΨ±Ψ¦ΩΨ³ΩΨ©"
|
|
283
|
+
```
|
|
651
284
|
|
|
652
|
-
|
|
653
|
-
> Subscribe to language changes on global instance.
|
|
285
|
+
- #### `plural(count, singleKey, pluralKey)`
|
|
654
286
|
|
|
655
|
-
|
|
656
|
-
> Load translations using global instance.
|
|
287
|
+
> Handle pluralization
|
|
657
288
|
|
|
658
|
-
|
|
659
|
-
|
|
289
|
+
```typescript
|
|
290
|
+
plural(5, 'item.single', 'item.plural') // "5 items"
|
|
291
|
+
```
|
|
660
292
|
|
|
661
|
-
|
|
293
|
+
- #### `hasKey(key)`
|
|
662
294
|
|
|
663
|
-
|
|
295
|
+
> Check if translation exists
|
|
664
296
|
|
|
665
|
-
|
|
666
|
-
|
|
297
|
+
```typescript
|
|
298
|
+
if (hasKey('settings.theme')) { /* ... */ }
|
|
299
|
+
```
|
|
667
300
|
|
|
668
|
-
|
|
669
|
-
// LTR: "Page Name - App Name"
|
|
670
|
-
// RTL: "App Name - Page Name"
|
|
301
|
+
- #### `loadLanguage(lang, translations)`
|
|
671
302
|
|
|
672
|
-
|
|
673
|
-
// Translates 'page.home' and 'app.name'
|
|
674
|
-
```
|
|
303
|
+
> Load translations for a language
|
|
675
304
|
|
|
676
|
-
|
|
677
|
-
|
|
305
|
+
```typescript
|
|
306
|
+
loadLanguage('en', { greeting: 'Hello' })
|
|
307
|
+
```
|
|
678
308
|
|
|
679
|
-
|
|
680
|
-
loadLanguage('en', {
|
|
681
|
-
'item.single': '1 item',
|
|
682
|
-
'item.plural': '{count} items'
|
|
683
|
-
})
|
|
309
|
+
- #### `loadTranslations(translations)`
|
|
684
310
|
|
|
685
|
-
|
|
686
|
-
plural(5, 'item.single', 'item.plural') // "5 items"
|
|
687
|
-
```
|
|
311
|
+
> Load multiple languages at once
|
|
688
312
|
|
|
689
|
-
|
|
313
|
+
```typescript
|
|
314
|
+
loadTranslations({
|
|
315
|
+
en: { greeting: 'Hello' },
|
|
316
|
+
ar: { greeting: 'Ω
Ψ±ΨΨ¨Ψ§' }
|
|
317
|
+
})
|
|
318
|
+
```
|
|
690
319
|
|
|
691
|
-
|
|
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
|
-
```
|
|
320
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> </div>
|
|
719
321
|
|
|
720
|
-
|
|
322
|
+
- #### Related
|
|
323
|
+
|
|
324
|
+
- ##### [@cruxjs/app](https://github.com/cruxjs-org/app)
|
|
721
325
|
|
|
722
326
|
<!-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ -->
|
|
723
327
|
|
|
@@ -725,6 +329,7 @@
|
|
|
725
329
|
|
|
726
330
|
<!-- βββββββββββββββββββββββββββββββ END βββββββββββββββββββββββββββββββ -->
|
|
727
331
|
|
|
332
|
+
<br>
|
|
728
333
|
<br>
|
|
729
334
|
|
|
730
335
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minejs/i18n",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A lightweight, production-ready internationalization (i18n) library with zero dependencies.",
|
|
5
5
|
"keywords": ["minejs", "i18n"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@eslint/js": "^9.39.2",
|
|
44
|
-
"@stylistic/eslint-plugin": "^5.
|
|
44
|
+
"@stylistic/eslint-plugin": "^5.7.0",
|
|
45
45
|
"@types/bun": "^1.3.5",
|
|
46
46
|
"@types/node": "^20.19.27",
|
|
47
47
|
"bun-plugin-dts": "^0.3.0",
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
"ts-node": "^10.9.2",
|
|
50
50
|
"tsup": "^8.5.1",
|
|
51
51
|
"typescript": "^5.9.3",
|
|
52
|
-
"typescript-eslint": "^8.
|
|
52
|
+
"typescript-eslint": "^8.52.0"
|
|
53
53
|
}
|
|
54
54
|
}
|