@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/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
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->