@ooneex/translation 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ooneex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,657 @@
1
+ # @ooneex/translation
2
+
3
+ A comprehensive TypeScript/JavaScript library for internationalization (i18n) and localization (l10n). This package provides powerful translation utilities with support for multiple languages, parameter interpolation, pluralization, and nested key structures.
4
+
5
+ ![Browser](https://img.shields.io/badge/Browser-Compatible-green?style=flat-square&logo=googlechrome)
6
+ ![Bun](https://img.shields.io/badge/Bun-Compatible-orange?style=flat-square&logo=bun)
7
+ ![Deno](https://img.shields.io/badge/Deno-Compatible-blue?style=flat-square&logo=deno)
8
+ ![Node.js](https://img.shields.io/badge/Node.js-Compatible-green?style=flat-square&logo=node.js)
9
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript)
10
+ ![MIT License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)
11
+
12
+ ## Features
13
+
14
+ ✅ **Multi-Language Support** - 31 supported locales including Arabic, Chinese, French, Spanish, and more
15
+
16
+ ✅ **Type-Safe** - Full TypeScript support with proper type definitions
17
+
18
+ ✅ **Parameter Interpolation** - Replace placeholders with dynamic values
19
+
20
+ ✅ **Pluralization Support** - Handle singular, plural, and zero forms automatically
21
+
22
+ ✅ **Nested Keys** - Support for dot notation in translation keys
23
+
24
+ ✅ **Flexible Input** - Accept both dictionary keys and direct translation objects
25
+
26
+ ✅ **Fallback System** - Graceful fallbacks to English or base keys
27
+
28
+ ✅ **Error Handling** - Custom exceptions for missing translations
29
+
30
+ ✅ **Lightweight** - Minimal dependencies and optimized bundle size
31
+
32
+ ✅ **Cross-Platform** - Works in Browser, Node.js, Bun, and Deno
33
+
34
+ ## Installation
35
+
36
+ ### Bun
37
+ ```bash
38
+ bun add @ooneex/translation
39
+ ```
40
+
41
+ ### pnpm
42
+ ```bash
43
+ pnpm add @ooneex/translation
44
+ ```
45
+
46
+ ### Yarn
47
+ ```bash
48
+ yarn add @ooneex/translation
49
+ ```
50
+
51
+ ### npm
52
+ ```bash
53
+ npm install @ooneex/translation
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ ### Basic Usage
59
+
60
+ ```typescript
61
+ import { trans } from '@ooneex/translation';
62
+
63
+ // Using dictionary with string keys
64
+ const dictionary = {
65
+ greeting: {
66
+ en: 'Hello, World!',
67
+ fr: 'Bonjour, le monde!',
68
+ es: 'Hola, mundo!'
69
+ }
70
+ };
71
+
72
+ // Get English translation (default)
73
+ const english = trans('greeting', { dict: dictionary });
74
+ console.log(english); // "Hello, World!"
75
+
76
+ // Get French translation
77
+ const french = trans('greeting', {
78
+ lang: 'fr',
79
+ dict: dictionary
80
+ });
81
+ console.log(french); // "Bonjour, le monde!"
82
+
83
+ // Using direct translation objects
84
+ const directTranslation = {
85
+ en: 'Welcome',
86
+ fr: 'Bienvenue',
87
+ es: 'Bienvenido'
88
+ };
89
+
90
+ const welcome = trans(directTranslation, { lang: 'fr' });
91
+ console.log(welcome); // "Bienvenue"
92
+ ```
93
+
94
+ ### Parameter Interpolation
95
+
96
+ ```typescript
97
+ import { trans } from '@ooneex/translation';
98
+
99
+ const dictionary = {
100
+ welcome: {
101
+ en: 'Welcome, {{ name }}!',
102
+ fr: 'Bienvenue, {{ name }}!',
103
+ es: '¡Bienvenido, {{ name }}!'
104
+ },
105
+ userInfo: {
106
+ en: 'User {{ name }} has {{ count }} points',
107
+ fr: 'L\'utilisateur {{ name }} a {{ count }} points'
108
+ }
109
+ };
110
+
111
+ // Single parameter
112
+ const greeting = trans('welcome', {
113
+ lang: 'en',
114
+ dict: dictionary,
115
+ params: { name: 'John' }
116
+ });
117
+ console.log(greeting); // "Welcome, John!"
118
+
119
+ // Multiple parameters
120
+ const info = trans('userInfo', {
121
+ lang: 'en',
122
+ dict: dictionary,
123
+ params: { name: 'Alice', count: 150 }
124
+ });
125
+ console.log(info); // "User Alice has 150 points"
126
+
127
+ // Different parameter types
128
+ const stats = trans('stats', {
129
+ dict: {
130
+ stats: {
131
+ en: 'Active: {{ active }}, Score: {{ score }}, ID: {{ id }}'
132
+ }
133
+ },
134
+ params: {
135
+ active: true,
136
+ score: 95.5,
137
+ id: 12345
138
+ }
139
+ });
140
+ console.log(stats); // "Active: true, Score: 95.5, ID: 12345"
141
+ ```
142
+
143
+ ### Pluralization
144
+
145
+ ```typescript
146
+ import { trans } from '@ooneex/translation';
147
+
148
+ const dictionary = {
149
+ item: {
150
+ en: '{{ count }} item',
151
+ fr: '{{ count }} élément'
152
+ },
153
+ item_plural: {
154
+ en: '{{ count }} items',
155
+ fr: '{{ count }} éléments'
156
+ },
157
+ message: {
158
+ en: '{{ count }} message',
159
+ fr: '{{ count }} message'
160
+ },
161
+ message_plural: {
162
+ en: '{{ count }} messages',
163
+ fr: '{{ count }} messages'
164
+ },
165
+ message_zero: {
166
+ en: 'No messages',
167
+ fr: 'Aucun message'
168
+ }
169
+ };
170
+
171
+ // Singular form (count = 1)
172
+ const singular = trans('item', {
173
+ lang: 'en',
174
+ dict: dictionary,
175
+ count: 1
176
+ });
177
+ console.log(singular); // "1 item"
178
+
179
+ // Plural form (count > 1)
180
+ const plural = trans('item', {
181
+ lang: 'en',
182
+ dict: dictionary,
183
+ count: 5
184
+ });
185
+ console.log(plural); // "5 items"
186
+
187
+ // Zero form (count = 0, when available)
188
+ const zero = trans('message', {
189
+ lang: 'en',
190
+ dict: dictionary,
191
+ count: 0
192
+ });
193
+ console.log(zero); // "No messages"
194
+
195
+ // Zero form fallback to plural (when zero form not available)
196
+ const zeroFallback = trans('item', {
197
+ lang: 'en',
198
+ dict: dictionary,
199
+ count: 0
200
+ });
201
+ console.log(zeroFallback); // "0 items"
202
+ ```
203
+
204
+ ### Nested Keys
205
+
206
+ ```typescript
207
+ import { trans } from '@ooneex/translation';
208
+
209
+ const dictionary = {
210
+ user: {
211
+ profile: {
212
+ name: {
213
+ en: 'Full Name',
214
+ fr: 'Nom complet',
215
+ es: 'Nombre completo'
216
+ },
217
+ email: {
218
+ en: 'Email Address',
219
+ fr: 'Adresse e-mail'
220
+ }
221
+ }
222
+ },
223
+ app: {
224
+ navigation: {
225
+ home: {
226
+ en: 'Home',
227
+ fr: 'Accueil'
228
+ },
229
+ about: {
230
+ en: 'About',
231
+ fr: 'À propos'
232
+ }
233
+ }
234
+ }
235
+ };
236
+
237
+ // Access nested keys with dot notation
238
+ const profileName = trans('user.profile.name', {
239
+ lang: 'fr',
240
+ dict: dictionary
241
+ });
242
+ console.log(profileName); // "Nom complet"
243
+
244
+ const homeNav = trans('app.navigation.home', {
245
+ lang: 'en',
246
+ dict: dictionary
247
+ });
248
+ console.log(homeNav); // "Home"
249
+ ```
250
+
251
+ ### Advanced Usage
252
+
253
+ ```typescript
254
+ import { trans, locales, TranslationException } from '@ooneex/translation';
255
+
256
+ // Check supported locales
257
+ console.log(locales); // ['ar', 'bg', 'cs', 'da', 'de', 'el', 'en', ...]
258
+
259
+ // Error handling
260
+ try {
261
+ const result = trans('nonexistent.key', {
262
+ dict: {},
263
+ lang: 'en'
264
+ });
265
+ } catch (error) {
266
+ if (error instanceof TranslationException) {
267
+ console.log('Translation not found:', error.message);
268
+ }
269
+ }
270
+
271
+ // Complex real-world example
272
+ const appDictionary = {
273
+ notifications: {
274
+ unread: {
275
+ en: 'You have {{ count }} unread notification',
276
+ fr: 'Vous avez {{ count }} notification non lue'
277
+ },
278
+ unread_plural: {
279
+ en: 'You have {{ count }} unread notifications',
280
+ fr: 'Vous avez {{ count }} notifications non lues'
281
+ },
282
+ unread_zero: {
283
+ en: 'No unread notifications',
284
+ fr: 'Aucune notification non lue'
285
+ }
286
+ },
287
+ validation: {
288
+ required: {
289
+ en: 'The {{ field }} field is required',
290
+ fr: 'Le champ {{ field }} est requis'
291
+ },
292
+ minLength: {
293
+ en: 'The {{ field }} must be at least {{ min }} characters',
294
+ fr: 'Le {{ field }} doit contenir au moins {{ min }} caractères'
295
+ }
296
+ }
297
+ };
298
+
299
+ // Notification with pluralization
300
+ const notification = trans('notifications.unread', {
301
+ lang: 'en',
302
+ dict: appDictionary,
303
+ count: 3
304
+ });
305
+ console.log(notification); // "You have 3 unread notifications"
306
+
307
+ // Form validation with parameters
308
+ const validation = trans('validation.minLength', {
309
+ lang: 'fr',
310
+ dict: appDictionary,
311
+ params: { field: 'mot de passe', min: 8 }
312
+ });
313
+ console.log(validation); // "Le mot de passe doit contenir au moins 8 caractères"
314
+ ```
315
+
316
+ ## API Reference
317
+
318
+ ### `trans<T extends string>(key, options?): T`
319
+
320
+ The main translation function that handles all translation scenarios.
321
+
322
+ **Parameters:**
323
+ - `key`: `string | Record<LocaleType, string>` - Translation key (dot notation supported) or direct translation object
324
+ - `options?`: `Object` - Translation options
325
+ - `lang?`: `LocaleType` - Target language (defaults to 'en')
326
+ - `params?`: `Record<string, boolean | number | bigint | string>` - Parameters for interpolation
327
+ - `dict?`: `Record<string, unknown>` - Translation dictionary (required when using string keys)
328
+ - `count?`: `number` - Count for pluralization
329
+
330
+ **Returns:** Translated string
331
+
332
+ **Example:**
333
+ ```typescript
334
+ // String key with dictionary
335
+ trans('greeting', { lang: 'fr', dict: dictionary });
336
+
337
+ // Direct translation object
338
+ trans({ en: 'Hello', fr: 'Bonjour' }, { lang: 'fr' });
339
+
340
+ // With parameters
341
+ trans('welcome', {
342
+ dict: dictionary,
343
+ params: { name: 'John' }
344
+ });
345
+
346
+ // With pluralization
347
+ trans('item', {
348
+ dict: dictionary,
349
+ count: 5
350
+ });
351
+ ```
352
+
353
+ ### Types
354
+
355
+ #### `LocaleType`
356
+ TypeScript type representing all supported locale codes.
357
+
358
+ **Supported locales:**
359
+ ```typescript
360
+ type LocaleType = 'ar' | 'bg' | 'cs' | 'da' | 'de' | 'el' | 'en' | 'eo' | 'es' | 'et' | 'eu' | 'fi' | 'fr' | 'hu' | 'hy' | 'it' | 'ja' | 'ko' | 'lt' | 'nl' | 'no' | 'pl' | 'pt' | 'ro' | 'ru' | 'sk' | 'sv' | 'th' | 'uk' | 'zh' | 'zh-tw';
361
+ ```
362
+
363
+ #### `LocaleInfoType`
364
+ Type for locale information including region data.
365
+
366
+ ```typescript
367
+ type LocaleInfoType = {
368
+ code: LocaleType;
369
+ region: string | null;
370
+ };
371
+ ```
372
+
373
+ ### Constants
374
+
375
+ #### `locales`
376
+ Array of all supported locale codes.
377
+
378
+ ```typescript
379
+ const locales: readonly LocaleType[];
380
+ ```
381
+
382
+ ### Exceptions
383
+
384
+ #### `TranslationException`
385
+ Custom exception thrown when translations are not found or invalid.
386
+
387
+ **Properties:**
388
+ - Extends base `Exception` class
389
+ - HTTP status: 404 (Not Found)
390
+ - Includes translation key and context information
391
+
392
+ **Example:**
393
+ ```typescript
394
+ try {
395
+ trans('missing.key', { dict: {} });
396
+ } catch (error) {
397
+ if (error instanceof TranslationException) {
398
+ console.log(error.message); // "Translation key 'missing.key' not found"
399
+ }
400
+ }
401
+ ```
402
+
403
+ ## Translation Dictionary Structure
404
+
405
+ ### Basic Structure
406
+ ```typescript
407
+ const dictionary = {
408
+ keyName: {
409
+ en: 'English translation',
410
+ fr: 'French translation',
411
+ es: 'Spanish translation'
412
+ // ... other locales
413
+ }
414
+ };
415
+ ```
416
+
417
+ ### Nested Structure
418
+ ```typescript
419
+ const dictionary = {
420
+ section: {
421
+ subsection: {
422
+ keyName: {
423
+ en: 'English translation',
424
+ fr: 'French translation'
425
+ }
426
+ }
427
+ }
428
+ };
429
+ ```
430
+
431
+ ### Pluralization Structure
432
+ ```typescript
433
+ const dictionary = {
434
+ // Singular form (count = 1)
435
+ item: {
436
+ en: '{{ count }} item',
437
+ fr: '{{ count }} élément'
438
+ },
439
+ // Plural form (count != 1, except 0 if zero form exists)
440
+ item_plural: {
441
+ en: '{{ count }} items',
442
+ fr: '{{ count }} éléments'
443
+ },
444
+ // Zero form (count = 0, optional)
445
+ item_zero: {
446
+ en: 'No items',
447
+ fr: 'Aucun élément'
448
+ }
449
+ };
450
+ ```
451
+
452
+ ## Pluralization Rules
453
+
454
+ 1. **count = 1**: Uses the base key (singular form)
455
+ 2. **count = 0**: Uses `key_zero` if available, otherwise falls back to `key_plural`
456
+ 3. **count > 1 or count < 0**: Uses `key_plural`
457
+ 4. **Fallback**: If plural forms don't exist, falls back to singular form
458
+
459
+ ## Fallback Strategy
460
+
461
+ 1. **Language fallback**: If requested language not available, falls back to English ('en')
462
+ 2. **Key fallback**: If translation key not found, returns the key itself
463
+ 3. **Pluralization fallback**: If plural forms don't exist, uses singular form
464
+ 4. **Empty translation**: Throws `TranslationException` for empty translations
465
+
466
+ ## Best Practices
467
+
468
+ ### 1. Organize Dictionary Structure
469
+ ```typescript
470
+ // Good: Organized by feature/section
471
+ const dictionary = {
472
+ auth: {
473
+ login: { en: 'Log In', fr: 'Se connecter' },
474
+ logout: { en: 'Log Out', fr: 'Se déconnecter' }
475
+ },
476
+ navigation: {
477
+ home: { en: 'Home', fr: 'Accueil' },
478
+ about: { en: 'About', fr: 'À propos' }
479
+ }
480
+ };
481
+ ```
482
+
483
+ ### 2. Use Consistent Parameter Names
484
+ ```typescript
485
+ // Good: Consistent naming
486
+ const dictionary = {
487
+ welcome: { en: 'Welcome, {{ username }}!' },
488
+ goodbye: { en: 'Goodbye, {{ username }}!' }
489
+ };
490
+ ```
491
+
492
+ ### 3. Handle Pluralization Properly
493
+ ```typescript
494
+ // Good: Complete pluralization support
495
+ const dictionary = {
496
+ notification: { en: '{{ count }} notification' },
497
+ notification_plural: { en: '{{ count }} notifications' },
498
+ notification_zero: { en: 'No notifications' }
499
+ };
500
+ ```
501
+
502
+ ### 4. Provide Fallbacks
503
+ ```typescript
504
+ // Good: Always include English translations
505
+ const dictionary = {
506
+ greeting: {
507
+ en: 'Hello',
508
+ fr: 'Bonjour',
509
+ es: 'Hola'
510
+ // en is always available as fallback
511
+ }
512
+ };
513
+ ```
514
+
515
+ ## Error Handling
516
+
517
+ The package throws `TranslationException` in the following cases:
518
+
519
+ - Translation key not found in dictionary
520
+ - Nested key path doesn't exist
521
+ - Translation value is empty
522
+ - String key used without dictionary
523
+
524
+ ```typescript
525
+ try {
526
+ const result = trans('missing.key', { dict: dictionary });
527
+ } catch (error) {
528
+ if (error instanceof TranslationException) {
529
+ // Handle translation error
530
+ console.warn('Translation missing:', error.message);
531
+ // Provide fallback or default value
532
+ return 'Fallback text';
533
+ }
534
+ }
535
+ ```
536
+
537
+ ## Performance Considerations
538
+
539
+ - **Dictionary Caching**: Cache translation dictionaries to avoid repeated parsing
540
+ - **Lazy Loading**: Load only required translations for better performance
541
+ - **Nested Key Optimization**: Avoid deeply nested structures when possible
542
+ - **Parameter Validation**: Validate parameters before passing to avoid runtime errors
543
+
544
+ ## Real-world Examples
545
+
546
+ ### E-commerce Application
547
+ ```typescript
548
+ const ecommerceDict = {
549
+ product: {
550
+ price: {
551
+ en: 'Price: ${{ amount }}',
552
+ fr: 'Prix : {{ amount }} $'
553
+ },
554
+ availability: {
555
+ en: '{{ count }} in stock',
556
+ fr: '{{ count }} en stock'
557
+ },
558
+ availability_zero: {
559
+ en: 'Out of stock',
560
+ fr: 'Rupture de stock'
561
+ }
562
+ },
563
+ cart: {
564
+ item: {
565
+ en: '{{ count }} item',
566
+ fr: '{{ count }} article'
567
+ },
568
+ item_plural: {
569
+ en: '{{ count }} items',
570
+ fr: '{{ count }} articles'
571
+ },
572
+ total: {
573
+ en: 'Total: ${{ amount }}',
574
+ fr: 'Total : {{ amount }} $'
575
+ }
576
+ }
577
+ };
578
+
579
+ // Usage
580
+ const price = trans('product.price', {
581
+ dict: ecommerceDict,
582
+ lang: 'en',
583
+ params: { amount: '29.99' }
584
+ });
585
+
586
+ const cartItems = trans('cart.item', {
587
+ dict: ecommerceDict,
588
+ lang: 'fr',
589
+ count: 3
590
+ });
591
+ ```
592
+
593
+ ### User Dashboard
594
+ ```typescript
595
+ const dashboardDict = {
596
+ dashboard: {
597
+ welcome: {
598
+ en: 'Welcome back, {{ username }}!',
599
+ fr: 'Bon retour, {{ username }} !'
600
+ },
601
+ stats: {
602
+ unread: {
603
+ en: 'You have {{ count }} unread message',
604
+ fr: 'Vous avez {{ count }} message non lu'
605
+ },
606
+ unread_plural: {
607
+ en: 'You have {{ count }} unread messages',
608
+ fr: 'Vous avez {{ count }} messages non lus'
609
+ },
610
+ unread_zero: {
611
+ en: 'No unread messages',
612
+ fr: 'Aucun message non lu'
613
+ }
614
+ }
615
+ }
616
+ };
617
+
618
+ // Usage
619
+ const welcome = trans('dashboard.welcome', {
620
+ dict: dashboardDict,
621
+ lang: 'fr',
622
+ params: { username: 'Marie' }
623
+ });
624
+
625
+ const messages = trans('dashboard.stats.unread', {
626
+ dict: dashboardDict,
627
+ lang: 'en',
628
+ count: 0
629
+ });
630
+ ```
631
+
632
+ ## License
633
+
634
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
635
+
636
+ ## Contributing
637
+
638
+ Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
639
+
640
+ ### Development Setup
641
+
642
+ 1. Clone the repository
643
+ 2. Install dependencies: `bun install`
644
+ 3. Run tests: `bun run test`
645
+ 4. Build the project: `bun run build`
646
+
647
+ ### Guidelines
648
+
649
+ - Write tests for new features
650
+ - Follow the existing code style
651
+ - Update documentation for API changes
652
+ - Ensure all tests pass before submitting PR
653
+ - Add new locales to the supported list when needed
654
+
655
+ ---
656
+
657
+ Made with ❤️ by the Ooneex team
@@ -0,0 +1,17 @@
1
+ declare const locales: readonly ["ar", "bg", "cs", "da", "de", "el", "en", "eo", "es", "et", "eu", "fi", "fr", "hu", "hy", "it", "ja", "ko", "lt", "nl", "no", "pl", "pt", "ro", "ru", "sk", "sv", "th", "uk", "zh", "zh-tw"];
2
+ import { Exception } from "@ooneex/exception";
3
+ declare class TranslationException extends Exception {
4
+ constructor(message: string, data?: Record<string, unknown>);
5
+ }
6
+ type LocaleType = (typeof locales)[number];
7
+ type LocaleInfoType = {
8
+ code: LocaleType;
9
+ region: string | null;
10
+ };
11
+ declare const trans: <T extends string>(key: string | Record<LocaleType, string>, options?: {
12
+ lang?: LocaleType;
13
+ params?: Record<string, boolean | number | bigint | string>;
14
+ dict?: Record<string, unknown>;
15
+ count?: number;
16
+ }) => T;
17
+ export { trans, locales, TranslationException, LocaleType, LocaleInfoType };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ var q=["ar","bg","cs","da","de","el","en","eo","es","et","eu","fi","fr","hu","hy","it","ja","ko","lt","nl","no","pl","pt","ro","ru","sk","sv","th","uk","zh","zh-tw"];import{Exception as I}from"@ooneex/exception";import{HttpStatus as S}from"@ooneex/http-status";class C extends I{constructor(o,h={}){super(o,{status:S.Code.NotFound,data:h});this.name="TranslationException"}}var H=(o,h)=>h.split(".").reduce((p,L)=>p&&typeof p==="object"&&p!==null&&(L in p)?p[L]:void 0,o),J=(o,h)=>{let{lang:p="en",params:L,dict:F,count:w}=h||{},m="";if(typeof o==="string"&&F){let f=o;if(w!==void 0)if(w===0){let s=`${o}_zero`;if(H(F,s))f=s;else f=`${o}_plural`}else if(w===1)f=o;else f=`${o}_plural`;let j=H(F,f);if(j)m=j[p]||j.en||o;else if(f!==o){let s=H(F,o);if(s)m=s[p]||s.en||o;else throw new C(`Translation key "${o}" not found`)}else throw new C(`Translation key "${o}" not found`)}else if(typeof o==="object")m=o[p]||o.en;if(!m)throw new C("Translation value is empty");if(L)for(let[f,j]of Object.entries(L))m=m.replace(`{{ ${f} }}`,j.toString());if(w!==void 0)m=m.replace(/\{\{ count \}\}/g,w.toString());return m};export{J as trans,q as locales,C as TranslationException};
2
+
3
+ //# debugId=9CE459E1BD46321164756E2164756E21
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["src/locales.ts", "src/TranslationException.ts", "src/trans.ts"],
4
+ "sourcesContent": [
5
+ "export const locales = [\n \"ar\",\n \"bg\",\n \"cs\",\n \"da\",\n \"de\",\n \"el\",\n \"en\",\n \"eo\",\n \"es\",\n \"et\",\n \"eu\",\n \"fi\",\n \"fr\",\n \"hu\",\n \"hy\",\n \"it\",\n \"ja\",\n \"ko\",\n \"lt\",\n \"nl\",\n \"no\",\n \"pl\",\n \"pt\",\n \"ro\",\n \"ru\",\n \"sk\",\n \"sv\",\n \"th\",\n \"uk\",\n \"zh\",\n \"zh-tw\",\n] as const;\n",
6
+ "import { Exception } from \"@ooneex/exception\";\nimport { HttpStatus } from \"@ooneex/http-status\";\n\nexport class TranslationException extends Exception {\n constructor(message: string, data: Record<string, unknown> = {}) {\n super(message, {\n status: HttpStatus.Code.NotFound,\n data,\n });\n this.name = \"TranslationException\";\n }\n}\n",
7
+ "import { TranslationException } from \"./TranslationException\";\nimport type { LocaleType } from \"./types\";\n\nconst getNestedValue = (dict: unknown, path: string): unknown =>\n path\n .split(\".\")\n .reduce(\n (current, key) =>\n current && typeof current === \"object\" && current !== null && key in current\n ? (current as Record<string, unknown>)[key]\n : undefined,\n dict,\n );\n\nexport const trans = <T extends string>(\n key: string | Record<LocaleType, string>,\n options?: {\n lang?: LocaleType;\n params?: Record<string, boolean | number | bigint | string>;\n dict?: Record<string, unknown>;\n count?: number;\n },\n): T => {\n const { lang = \"en\", params, dict, count } = options || {};\n let text = \"\";\n\n if (typeof key === \"string\" && dict) {\n let translationKey = key;\n\n // Handle pluralization\n if (count !== undefined) {\n if (count === 0) {\n // Try zero form first, fallback to plural\n const zeroKey = `${key}_zero`;\n const zeroEntry = getNestedValue(dict, zeroKey) as Record<LocaleType, string>;\n if (zeroEntry) {\n translationKey = zeroKey;\n } else {\n translationKey = `${key}_plural`;\n }\n } else if (count === 1) {\n // Use singular form (original key)\n translationKey = key;\n } else {\n // Use plural form\n translationKey = `${key}_plural`;\n }\n }\n\n const translationEntry = getNestedValue(dict, translationKey) as Record<LocaleType, string>;\n\n if (translationEntry) {\n text = translationEntry[lang as keyof typeof translationEntry] || translationEntry.en || key;\n } else {\n // Fallback to original key if plural form not found\n if (translationKey !== key) {\n const fallbackEntry = getNestedValue(dict, key) as Record<LocaleType, string>;\n if (fallbackEntry) {\n text = fallbackEntry[lang as keyof typeof fallbackEntry] || fallbackEntry.en || key;\n } else {\n throw new TranslationException(`Translation key \"${key}\" not found`);\n }\n } else {\n throw new TranslationException(`Translation key \"${key}\" not found`);\n }\n }\n } else if (typeof key === \"object\") {\n text = key[lang as keyof typeof key] || key.en;\n }\n\n if (!text) {\n throw new TranslationException(\"Translation value is empty\");\n }\n\n // Replace parameters\n if (params) {\n for (const [paramKey, value] of Object.entries(params)) {\n text = (text as string).replace(`{{ ${paramKey} }}`, value.toString());\n }\n }\n\n // Replace count parameter if provided\n if (count !== undefined) {\n text = (text as string).replace(/\\{\\{ count \\}\\}/g, count.toString());\n }\n\n return text as T;\n};\n"
8
+ ],
9
+ "mappings": "AAAO,IAAM,EAAU,CACrB,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,OACF,EChCA,oBAAS,0BACT,qBAAS,4BAEF,MAAM,UAA6B,CAAU,CAClD,WAAW,CAAC,EAAiB,EAAgC,CAAC,EAAG,CAC/D,MAAM,EAAS,CACb,OAAQ,EAAW,KAAK,SACxB,MACF,CAAC,EACD,KAAK,KAAO,uBAEhB,CCRA,IAAM,EAAiB,CAAC,EAAe,IACrC,EACG,MAAM,GAAG,EACT,OACC,CAAC,EAAS,IACR,GAAW,OAAO,IAAY,UAAY,IAAY,OAAQ,KAAO,GAChE,EAAoC,GACrC,OACN,CACF,EAES,EAAQ,CACnB,EACA,IAMM,CACN,IAAQ,OAAO,KAAM,SAAQ,OAAM,SAAU,GAAW,CAAC,EACrD,EAAO,GAEX,GAAI,OAAO,IAAQ,UAAY,EAAM,CACnC,IAAI,EAAiB,EAGrB,GAAI,IAAU,OACZ,GAAI,IAAU,EAAG,CAEf,IAAM,EAAU,GAAG,SAEnB,GADkB,EAAe,EAAM,CAAO,EAE5C,EAAiB,EAEjB,OAAiB,GAAG,WAEjB,QAAI,IAAU,EAEnB,EAAiB,EAGjB,OAAiB,GAAG,WAIxB,IAAM,EAAmB,EAAe,EAAM,CAAc,EAE5D,GAAI,EACF,EAAO,EAAiB,IAA0C,EAAiB,IAAM,EAGzF,QAAI,IAAmB,EAAK,CAC1B,IAAM,EAAgB,EAAe,EAAM,CAAG,EAC9C,GAAI,EACF,EAAO,EAAc,IAAuC,EAAc,IAAM,EAEhF,WAAM,IAAI,EAAqB,oBAAoB,cAAgB,EAGrE,WAAM,IAAI,EAAqB,oBAAoB,cAAgB,EAGlE,QAAI,OAAO,IAAQ,SACxB,EAAO,EAAI,IAA6B,EAAI,GAG9C,GAAI,CAAC,EACH,MAAM,IAAI,EAAqB,4BAA4B,EAI7D,GAAI,EACF,QAAY,EAAU,KAAU,OAAO,QAAQ,CAAM,EACnD,EAAQ,EAAgB,QAAQ,MAAM,OAAe,EAAM,SAAS,CAAC,EAKzE,GAAI,IAAU,OACZ,EAAQ,EAAgB,QAAQ,mBAAoB,EAAM,SAAS,CAAC,EAGtE,OAAO",
10
+ "debugId": "9CE459E1BD46321164756E2164756E21",
11
+ "names": []
12
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@ooneex/translation",
3
+ "description": "",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "files": [
7
+ "dist",
8
+ "LICENSE",
9
+ "README.md",
10
+ "package.json"
11
+ ],
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "license": "MIT",
24
+ "scripts": {
25
+ "test": "bun test tests",
26
+ "build": "bunup",
27
+ "lint": "tsgo --noEmit && bunx biome lint",
28
+ "publish:prod": "bun publish --tolerate-republish --access public",
29
+ "publish:pack": "bun pm pack --destination ./dist",
30
+ "publish:dry": "bun publish --dry-run"
31
+ },
32
+ "dependencies": {
33
+ "@ooneex/exception": "0.0.1",
34
+ "@ooneex/http-status": "0.0.1"
35
+ },
36
+ "devDependencies": {}
37
+ }