next-i18n-lite 1.0.3

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 Your Name
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,254 @@
1
+ next-i18n-lite
2
+
3
+ 🌍 Lightweight internationalization (i18n) library for Next.js with TypeScript support.
4
+
5
+ ## Features
6
+
7
+ - ✅ TypeScript support with full type safety
8
+ - ✅ React Context API integration
9
+ - ✅ Automatic RTL support for Arabic.
10
+ - ✅ Nested translation keys with dot notation
11
+ - ✅ Dynamic parameter replacement
12
+ - ✅ localStorage persistence
13
+ - ✅ Browser language detection
14
+ - ✅ Zero dependencies (except React peer deps)
15
+ - ✅ Tree-shakeable
16
+ - ✅ Works with Next.js 13+ App Router
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install next-i18n-lite
22
+ # or
23
+ yarn add next-i18n-lite
24
+ # or
25
+ pnpm add next-i18n-lite
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ File Structure
31
+ create those files inside app
32
+ 1- I18nBoundary.tsx (inside lib/)
33
+ 2- LocaleSwitcher.tsx (inside components)
34
+ 3- lang files inside lib/locales/en.ts and ar.ts
35
+ that's all :D
36
+ app/
37
+ ├─ layout.tsx # Root layout
38
+ ├─ globals.css # Global styles
39
+ ├─ Header.tsx # Header component (contains nav + LocaleSwitcher)
40
+ ├─ components/
41
+ │ └─ i18n/
42
+ │ └─ LocaleSwitcher.tsx # Language switch button
43
+ └─ lib/
44
+ ├─ I18nBoundary.tsx # Client-only i18n wrapper
45
+ └─ locales/
46
+ ├─ en.ts # English translations
47
+ └─ ar.ts # Arabic translations
48
+ ### 1. Create translation files
49
+
50
+ ```typescript
51
+ // lib/locales/en.ts
52
+ export const en = {
53
+ common: {
54
+ welcome: 'Welcome',
55
+ loading: 'Loading...',
56
+ },
57
+ product: {
58
+ addToCart: 'Add to Cart',
59
+ price: 'Price: ${amount}',
60
+ },
61
+ };
62
+
63
+ // lib/locales/ar.ts
64
+ export const ar = {
65
+ common: {
66
+ welcome: 'مرحباً',
67
+ loading: 'جاري التحميل...',
68
+ },
69
+ product: {
70
+ addToCart: 'أضف إلى السلة',
71
+ price: 'السعر: ${amount}',
72
+ },
73
+ };
74
+ ```
75
+ - ✅ feel free to deal with object:
76
+ ```typescript
77
+ export const en = {
78
+ welcome: 'Welcome',
79
+ loading: 'Loading...',
80
+ }
81
+ ```
82
+ ### 2. Setup Provider (Next.js App Router)
83
+
84
+ #### I18nBoundary.tsx
85
+ ```typescript
86
+ // lib/I18nBoundary.tsx
87
+ 'use client';
88
+
89
+ import { ReactNode, useEffect, useState } from 'react';
90
+ import { I18nProvider } from 'next-i18n-lite/react';
91
+
92
+ import { en } from './locales/en';
93
+ import { ar } from './locales/ar';
94
+
95
+ const translations = { en, ar };
96
+
97
+ export function I18nBoundary({ children }: { children: ReactNode }) {
98
+ const [locale, setLocale] = useState<'en' | 'ar' | null>(null);
99
+
100
+ useEffect(() => {
101
+ const saved = localStorage.getItem('locale');
102
+ setLocale(saved === 'ar' ? 'ar' : 'en');
103
+ }, []);
104
+
105
+ if (!locale) {
106
+ return null;
107
+ }
108
+ return (
109
+ <I18nProvider translations={translations} defaultLocale={locale}>
110
+ {children}
111
+ </I18nProvider>
112
+ );
113
+ }
114
+ ```
115
+ just created it not to fall in 'use client' in layout.tsx while you cannot use it with Metadata and similar issues ^^
116
+ still may face issue for setLocale
117
+ - Error: Calling setState synchronously within an effect can trigger cascading renders
118
+
119
+ Fix: not actually fixed yet but if you don't like red flags in code just
120
+
121
+ ```typescript
122
+ // eslint-disable-next-line react-hooks/set-state-in-effect
123
+ ```
124
+ yup! ignore it :D
125
+ ```typescript
126
+ // app/layout.tsx
127
+ import { I18nBoundary } from "./lib/I18nBoundary";
128
+
129
+ export default function RootLayout({ children }) {
130
+ return (
131
+ <html>
132
+ <body>
133
+ <I18nBoundary>
134
+ {children}
135
+ </I18nBoundary>
136
+ </body>
137
+ </html>
138
+ );
139
+ }
140
+ ```
141
+ ### 3. Create LocaleSwitcher.tsx
142
+ /components/ui/LocaleSwitcher.tsx (recommended)
143
+ also i provided 2 options:
144
+ - Choice 1: Single-click toggle button
145
+ - Choice 2:Hover dropdown list
146
+
147
+ you can ignore styling & lucide-react library both only for example ^^
148
+ ```typescript
149
+ 'use client';
150
+ import { Globe } from "lucide-react";
151
+ import { useI18n } from "next-i18n-lite/react";
152
+ import { useEffect, useState } from "react";
153
+
154
+ const languages = [
155
+ { code: 'en', name: 'English' },
156
+ { code: 'ar', name: 'العربية' },
157
+ ];
158
+
159
+ export function LocaleSwitcher() {
160
+ const { locale, setLocale, isRTL } = useI18n();
161
+ const [mounted, setMounted] = useState(false);
162
+
163
+ useEffect(() => {
164
+ // eslint-disable-next-line react-hooks/set-state-in-effect
165
+ setMounted(true);
166
+ }, []);
167
+
168
+ if (!mounted) return null;
169
+
170
+ // Toggle between languages on single click
171
+ const toggleLocale = () => {
172
+ const persist = locale === 'en' ? 'ar' : 'en';
173
+ setLocale(persist);
174
+ localStorage.setItem('locale', persist); // persist choice
175
+ };
176
+
177
+ // Positioning
178
+ const dropdownPosition = isRTL ? 'left-0' : 'right-0';
179
+
180
+ return (
181
+ <div className="relative group inline-block">
182
+ {/* Choice 1: Single-click toggle button */}
183
+ <Globe color="white" onClick={toggleLocale} className="top-5 "/>
184
+
185
+ {/* Choice 2:Hover dropdown list */}
186
+ <div
187
+ className={`absolute ${dropdownPosition} mt-2 bg-card border border-border rounded-xl shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50`}
188
+ >
189
+ {languages.map((lang) => (
190
+ <button
191
+ key={lang.code}
192
+ onClick={() => {
193
+ setLocale(lang.code);
194
+ localStorage.setItem('locale', lang.code);
195
+ }}
196
+ className={`w-full flex items-center gap-3 px-4 py-3 transition-colors first:rounded-t-xl last:rounded-b-xl
197
+ ${locale === lang.code ? 'bg-primary/10 text-primary' : 'hover:bg-gray-600'}
198
+ `}
199
+ >
200
+ <span className="text-sm font-medium">{lang.name}</span>
201
+ </button>
202
+ ))}
203
+ </div>
204
+ </div>
205
+ );
206
+ }
207
+ ```
208
+ ### 4. Use in Components
209
+ ### `useI18n()`
210
+ const { t, locale, setLocale, isRTL } = useI18n();
211
+
212
+ Returns:
213
+ - `t(key, params?)` - Translation function
214
+ - `locale` - Current locale
215
+ - `setLocale(locale)` - Change locale
216
+ - `isRTL` - Boolean indicating RTL direction
217
+ ```typescript
218
+ 'use client';
219
+ import { useI18n } from "next-i18n-lite/react";
220
+
221
+ export function page() {
222
+ const { t } = useI18n();
223
+
224
+ return (
225
+ <div>
226
+ <h1>{t('common.welcome')}</h1>
227
+ <p>{t('product.price', { amount: '99' })}</p>
228
+ </div>
229
+ );
230
+ }
231
+ ```
232
+ and that's it!!!
233
+ ## API Reference
234
+
235
+ ### `useI18n()`
236
+
237
+ Returns:
238
+ - `t(key, params?)` - Translation function
239
+ - `locale` - Current locale
240
+ - `setLocale(locale)` - Change locale
241
+ - `isRTL` - Boolean indicating RTL direction
242
+
243
+ ## Compatibility
244
+
245
+ - ✅ Node.js >= 18
246
+ - ✅ React >= 18
247
+ - ✅ Next.js >= 13
248
+ - ✅ TypeScript >= 5
249
+
250
+ ## contact
251
+ codex410@gmail.com
252
+ ## License
253
+
254
+ MIT © IslamAbozeed
@@ -0,0 +1,22 @@
1
+ import { T as TranslationsRecord, L as Locale } from './types-DrfIsMVB.mjs';
2
+ export { I as I18nConfig, N as NestedTranslations } from './types-DrfIsMVB.mjs';
3
+
4
+ declare class I18n {
5
+ private locale;
6
+ private fallbackLocale;
7
+ private translations;
8
+ constructor(translations: TranslationsRecord, defaultLocale: Locale, fallbackLocale?: Locale);
9
+ private isValidLocale;
10
+ private getNestedValue;
11
+ t(key: string, params?: Record<string, string | number>): string;
12
+ setLocale(locale: Locale): void;
13
+ getLocale(): Locale;
14
+ isRTL(locale?: Locale): boolean;
15
+ }
16
+
17
+ declare function formatDate(date: Date, locale: string): string;
18
+ declare function formatNumber(num: number, locale: string): string;
19
+ declare function formatCurrency(amount: number, locale: string, currency?: string): string;
20
+ declare function pluralize(count: number, singular: string, plural: string): string;
21
+
22
+ export { I18n, Locale, TranslationsRecord, formatCurrency, formatDate, formatNumber, pluralize };
@@ -0,0 +1,22 @@
1
+ import { T as TranslationsRecord, L as Locale } from './types-DrfIsMVB.js';
2
+ export { I as I18nConfig, N as NestedTranslations } from './types-DrfIsMVB.js';
3
+
4
+ declare class I18n {
5
+ private locale;
6
+ private fallbackLocale;
7
+ private translations;
8
+ constructor(translations: TranslationsRecord, defaultLocale: Locale, fallbackLocale?: Locale);
9
+ private isValidLocale;
10
+ private getNestedValue;
11
+ t(key: string, params?: Record<string, string | number>): string;
12
+ setLocale(locale: Locale): void;
13
+ getLocale(): Locale;
14
+ isRTL(locale?: Locale): boolean;
15
+ }
16
+
17
+ declare function formatDate(date: Date, locale: string): string;
18
+ declare function formatNumber(num: number, locale: string): string;
19
+ declare function formatCurrency(amount: number, locale: string, currency?: string): string;
20
+ declare function pluralize(count: number, singular: string, plural: string): string;
21
+
22
+ export { I18n, Locale, TranslationsRecord, formatCurrency, formatDate, formatNumber, pluralize };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var i=class{constructor(t,e,n){if(this.translations=t,this.locale=e,this.fallbackLocale=n||e,typeof window<"u"){let r=localStorage.getItem("i18n-locale");r&&this.isValidLocale(r)&&(this.locale=r);}}isValidLocale(t){return t in this.translations}getNestedValue(t,e){let n=e.split("."),r=t;for(let s of n){if(!r||typeof r=="string")return;r=r[s];}return typeof r=="string"?r:void 0}t(t,e){let n=this.getNestedValue(this.translations[this.locale],t);return n||(n=this.getNestedValue(this.translations[this.fallbackLocale],t)),n?(e&&Object.keys(e).forEach(r=>{n=n.replace(new RegExp(`\\$\\{${r}\\}`,"g"),String(e[r]));}),n):(console.warn(`Translation missing for key: ${t}`),t)}setLocale(t){this.isValidLocale(t)&&(this.locale=t,typeof window<"u"&&(localStorage.setItem("i18n-locale",t),document.documentElement.setAttribute("lang",t),document.documentElement.setAttribute("dir",this.isRTL(t)?"rtl":"ltr")));}getLocale(){return this.locale}isRTL(t){let e=["ar","he","fa","ur"],n=t||this.locale;return e.includes(n)}};function a(o,t){return new Intl.DateTimeFormat(t).format(o)}function l(o,t){return new Intl.NumberFormat(t).format(o)}function c(o,t,e="USD"){return new Intl.NumberFormat(t,{style:"currency",currency:e}).format(o)}function u(o,t,e){return o===1?t:e}exports.I18n=i;exports.formatCurrency=c;exports.formatDate=a;exports.formatNumber=l;exports.pluralize=u;//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/I18n.ts","../src/utils/formatters.ts"],"names":["I18n","translations","defaultLocale","fallbackLocale","savedLocale","locale","obj","path","keys","current","key","params","translation","param","rtlLocales","checkLocale","formatDate","date","formatNumber","num","formatCurrency","amount","currency","pluralize","count","singular","plural"],"mappings":"aAEO,IAAMA,CAAAA,CAAN,KAAW,CAKhB,WAAA,CACEC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACA,CAKA,GAJA,IAAA,CAAK,YAAA,CAAeF,CAAAA,CACpB,KAAK,MAAA,CAASC,CAAAA,CACd,IAAA,CAAK,cAAA,CAAiBC,CAAAA,EAAkBD,CAAAA,CAEpC,OAAO,MAAA,CAAW,GAAA,CAAa,CACjC,IAAME,CAAAA,CAAc,YAAA,CAAa,OAAA,CAAQ,aAAa,CAAA,CAClDA,GAAe,IAAA,CAAK,aAAA,CAAcA,CAAW,CAAA,GAC/C,IAAA,CAAK,MAAA,CAASA,CAAAA,EAElB,CACF,CAEQ,aAAA,CAAcC,CAAAA,CAAyB,CAC7C,OAAOA,CAAAA,IAAU,IAAA,CAAK,YACxB,CAEQ,cAAA,CACNC,CAAAA,CACAC,CAAAA,CACoB,CACpB,IAAMC,CAAAA,CAAOD,CAAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CACvBE,CAAAA,CAAmDH,CAAAA,CAEvD,IAAA,IAAWI,CAAAA,IAAOF,CAAAA,CAAM,CACtB,GAAI,CAACC,CAAAA,EAAW,OAAOA,CAAAA,EAAY,QAAA,CAAU,OAC7CA,CAAAA,CAAUA,CAAAA,CAAQC,CAAG,EACvB,CAEA,OAAO,OAAOD,CAAAA,EAAY,QAAA,CAAWA,CAAAA,CAAU,MACjD,CAEA,CAAA,CAAEC,CAAAA,CAAaC,CAAAA,CAAkD,CAC/D,IAAIC,CAAAA,CAAc,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,MAAM,CAAA,CAAGF,CAAG,CAAA,CASzE,OAPKE,IACHA,CAAAA,CAAc,IAAA,CAAK,cAAA,CACjB,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,cAAc,CAAA,CACrCF,CACF,CAAA,CAAA,CAGGE,CAAAA,EAKDD,CAAAA,EACF,MAAA,CAAO,IAAA,CAAKA,CAAM,CAAA,CAAE,QAASE,CAAAA,EAAU,CACrCD,CAAAA,CAAcA,CAAAA,CAAa,OAAA,CACzB,IAAI,MAAA,CAAO,CAAA,MAAA,EAASC,CAAK,CAAA,GAAA,CAAA,CAAO,GAAG,CAAA,CACnC,MAAA,CAAOF,CAAAA,CAAOE,CAAK,CAAC,CACtB,EACF,CAAC,CAAA,CAGID,CAAAA,GAbL,OAAA,CAAQ,IAAA,CAAK,CAAA,6BAAA,EAAgCF,CAAG,CAAA,CAAE,CAAA,CAC3CA,CAAAA,CAaX,CAEA,SAAA,CAAUL,CAAAA,CAAsB,CAC1B,IAAA,CAAK,aAAA,CAAcA,CAAM,CAAA,GAC3B,IAAA,CAAK,MAAA,CAASA,CAAAA,CACV,OAAO,MAAA,CAAW,GAAA,GACpB,YAAA,CAAa,OAAA,CAAQ,aAAA,CAAeA,CAAM,CAAA,CAC1C,QAAA,CAAS,eAAA,CAAgB,YAAA,CAAa,MAAA,CAAQA,CAAM,CAAA,CACpD,QAAA,CAAS,eAAA,CAAgB,YAAA,CACvB,KAAA,CACA,IAAA,CAAK,KAAA,CAAMA,CAAM,CAAA,CAAI,KAAA,CAAQ,KAC/B,CAAA,CAAA,EAGN,CAEA,SAAA,EAAoB,CAClB,OAAO,KAAK,MACd,CAEA,KAAA,CAAMA,CAAAA,CAA0B,CAC9B,IAAMS,CAAAA,CAAa,CAAC,KAAM,IAAA,CAAM,IAAA,CAAM,IAAI,CAAA,CACpCC,CAAAA,CAAcV,CAAAA,EAAU,IAAA,CAAK,MAAA,CACnC,OAAOS,CAAAA,CAAW,QAAA,CAASC,CAAW,CACxC,CACF,EC7FO,SAASC,CAAAA,CAAWC,CAAAA,CAAYZ,CAAAA,CAAwB,CAC7D,OAAO,IAAI,IAAA,CAAK,cAAA,CAAeA,CAAM,EAAE,MAAA,CAAOY,CAAI,CACpD,CAEO,SAASC,CAAAA,CAAaC,CAAAA,CAAad,CAAAA,CAAwB,CAChE,OAAO,IAAI,IAAA,CAAK,YAAA,CAAaA,CAAM,CAAA,CAAE,MAAA,CAAOc,CAAG,CACjD,CAEO,SAASC,CAAAA,CACdC,CAAAA,CACAhB,CAAAA,CACAiB,CAAAA,CAAW,KAAA,CACH,CACR,OAAO,IAAI,IAAA,CAAK,YAAA,CAAajB,CAAAA,CAAQ,CACnC,KAAA,CAAO,WACP,QAAA,CAAAiB,CACF,CAAC,CAAA,CAAE,MAAA,CAAOD,CAAM,CAClB,CAEO,SAASE,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACQ,CACR,OAAOF,CAAAA,GAAU,CAAA,CAAIC,EAAWC,CAClC","file":"index.js","sourcesContent":["import { Locale, NestedTranslations, TranslationsRecord } from '../types';\r\n\r\nexport class I18n {\r\n private locale: Locale;\r\n private fallbackLocale: Locale;\r\n private translations: TranslationsRecord;\r\n\r\n constructor(\r\n translations: TranslationsRecord,\r\n defaultLocale: Locale,\r\n fallbackLocale?: Locale\r\n ) {\r\n this.translations = translations;\r\n this.locale = defaultLocale;\r\n this.fallbackLocale = fallbackLocale || defaultLocale;\r\n\r\n if (typeof window !== 'undefined') {\r\n const savedLocale = localStorage.getItem('i18n-locale');\r\n if (savedLocale && this.isValidLocale(savedLocale)) {\r\n this.locale = savedLocale;\r\n }\r\n }\r\n }\r\n\r\n private isValidLocale(locale: string): boolean {\r\n return locale in this.translations;\r\n }\r\n\r\n private getNestedValue(\r\n obj: NestedTranslations,\r\n path: string\r\n ): string | undefined {\r\n const keys = path.split('.');\r\n let current: string | NestedTranslations | undefined = obj;\r\n\r\n for (const key of keys) {\r\n if (!current || typeof current === 'string') return undefined;\r\n current = current[key];\r\n }\r\n\r\n return typeof current === 'string' ? current : undefined;\r\n }\r\n\r\n t(key: string, params?: Record<string, string | number>): string {\r\n let translation = this.getNestedValue(this.translations[this.locale], key);\r\n\r\n if (!translation) {\r\n translation = this.getNestedValue(\r\n this.translations[this.fallbackLocale],\r\n key\r\n );\r\n }\r\n\r\n if (!translation) {\r\n console.warn(`Translation missing for key: ${key}`);\r\n return key;\r\n }\r\n\r\n if (params) {\r\n Object.keys(params).forEach((param) => {\r\n translation = translation!.replace(\r\n new RegExp(`\\\\$\\\\{${param}\\\\}`, 'g'),\r\n String(params[param])\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n setLocale(locale: Locale): void {\r\n if (this.isValidLocale(locale)) {\r\n this.locale = locale;\r\n if (typeof window !== 'undefined') {\r\n localStorage.setItem('i18n-locale', locale);\r\n document.documentElement.setAttribute('lang', locale);\r\n document.documentElement.setAttribute(\r\n 'dir',\r\n this.isRTL(locale) ? 'rtl' : 'ltr'\r\n );\r\n }\r\n }\r\n }\r\n\r\n getLocale(): Locale {\r\n return this.locale;\r\n }\r\n\r\n isRTL(locale?: Locale): boolean {\r\n const rtlLocales = ['ar', 'he', 'fa', 'ur'];\r\n const checkLocale = locale || this.locale;\r\n return rtlLocales.includes(checkLocale);\r\n }\r\n}","export function formatDate(date: Date, locale: string): string {\r\n return new Intl.DateTimeFormat(locale).format(date);\r\n}\r\n\r\nexport function formatNumber(num: number, locale: string): string {\r\n return new Intl.NumberFormat(locale).format(num);\r\n}\r\n\r\nexport function formatCurrency(\r\n amount: number,\r\n locale: string,\r\n currency = 'USD'\r\n): string {\r\n return new Intl.NumberFormat(locale, {\r\n style: 'currency',\r\n currency,\r\n }).format(amount);\r\n}\r\n\r\nexport function pluralize(\r\n count: number,\r\n singular: string,\r\n plural: string\r\n): string {\r\n return count === 1 ? singular : plural;\r\n}"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ var i=class{constructor(t,e,n){if(this.translations=t,this.locale=e,this.fallbackLocale=n||e,typeof window<"u"){let r=localStorage.getItem("i18n-locale");r&&this.isValidLocale(r)&&(this.locale=r);}}isValidLocale(t){return t in this.translations}getNestedValue(t,e){let n=e.split("."),r=t;for(let s of n){if(!r||typeof r=="string")return;r=r[s];}return typeof r=="string"?r:void 0}t(t,e){let n=this.getNestedValue(this.translations[this.locale],t);return n||(n=this.getNestedValue(this.translations[this.fallbackLocale],t)),n?(e&&Object.keys(e).forEach(r=>{n=n.replace(new RegExp(`\\$\\{${r}\\}`,"g"),String(e[r]));}),n):(console.warn(`Translation missing for key: ${t}`),t)}setLocale(t){this.isValidLocale(t)&&(this.locale=t,typeof window<"u"&&(localStorage.setItem("i18n-locale",t),document.documentElement.setAttribute("lang",t),document.documentElement.setAttribute("dir",this.isRTL(t)?"rtl":"ltr")));}getLocale(){return this.locale}isRTL(t){let e=["ar","he","fa","ur"],n=t||this.locale;return e.includes(n)}};function a(o,t){return new Intl.DateTimeFormat(t).format(o)}function l(o,t){return new Intl.NumberFormat(t).format(o)}function c(o,t,e="USD"){return new Intl.NumberFormat(t,{style:"currency",currency:e}).format(o)}function u(o,t,e){return o===1?t:e}export{i as I18n,c as formatCurrency,a as formatDate,l as formatNumber,u as pluralize};//# sourceMappingURL=index.mjs.map
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/I18n.ts","../src/utils/formatters.ts"],"names":["I18n","translations","defaultLocale","fallbackLocale","savedLocale","locale","obj","path","keys","current","key","params","translation","param","rtlLocales","checkLocale","formatDate","date","formatNumber","num","formatCurrency","amount","currency","pluralize","count","singular","plural"],"mappings":"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","file":"index.mjs","sourcesContent":["import { Locale, NestedTranslations, TranslationsRecord } from '../types';\r\n\r\nexport class I18n {\r\n private locale: Locale;\r\n private fallbackLocale: Locale;\r\n private translations: TranslationsRecord;\r\n\r\n constructor(\r\n translations: TranslationsRecord,\r\n defaultLocale: Locale,\r\n fallbackLocale?: Locale\r\n ) {\r\n this.translations = translations;\r\n this.locale = defaultLocale;\r\n this.fallbackLocale = fallbackLocale || defaultLocale;\r\n\r\n if (typeof window !== 'undefined') {\r\n const savedLocale = localStorage.getItem('i18n-locale');\r\n if (savedLocale && this.isValidLocale(savedLocale)) {\r\n this.locale = savedLocale;\r\n }\r\n }\r\n }\r\n\r\n private isValidLocale(locale: string): boolean {\r\n return locale in this.translations;\r\n }\r\n\r\n private getNestedValue(\r\n obj: NestedTranslations,\r\n path: string\r\n ): string | undefined {\r\n const keys = path.split('.');\r\n let current: string | NestedTranslations | undefined = obj;\r\n\r\n for (const key of keys) {\r\n if (!current || typeof current === 'string') return undefined;\r\n current = current[key];\r\n }\r\n\r\n return typeof current === 'string' ? current : undefined;\r\n }\r\n\r\n t(key: string, params?: Record<string, string | number>): string {\r\n let translation = this.getNestedValue(this.translations[this.locale], key);\r\n\r\n if (!translation) {\r\n translation = this.getNestedValue(\r\n this.translations[this.fallbackLocale],\r\n key\r\n );\r\n }\r\n\r\n if (!translation) {\r\n console.warn(`Translation missing for key: ${key}`);\r\n return key;\r\n }\r\n\r\n if (params) {\r\n Object.keys(params).forEach((param) => {\r\n translation = translation!.replace(\r\n new RegExp(`\\\\$\\\\{${param}\\\\}`, 'g'),\r\n String(params[param])\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n setLocale(locale: Locale): void {\r\n if (this.isValidLocale(locale)) {\r\n this.locale = locale;\r\n if (typeof window !== 'undefined') {\r\n localStorage.setItem('i18n-locale', locale);\r\n document.documentElement.setAttribute('lang', locale);\r\n document.documentElement.setAttribute(\r\n 'dir',\r\n this.isRTL(locale) ? 'rtl' : 'ltr'\r\n );\r\n }\r\n }\r\n }\r\n\r\n getLocale(): Locale {\r\n return this.locale;\r\n }\r\n\r\n isRTL(locale?: Locale): boolean {\r\n const rtlLocales = ['ar', 'he', 'fa', 'ur'];\r\n const checkLocale = locale || this.locale;\r\n return rtlLocales.includes(checkLocale);\r\n }\r\n}","export function formatDate(date: Date, locale: string): string {\r\n return new Intl.DateTimeFormat(locale).format(date);\r\n}\r\n\r\nexport function formatNumber(num: number, locale: string): string {\r\n return new Intl.NumberFormat(locale).format(num);\r\n}\r\n\r\nexport function formatCurrency(\r\n amount: number,\r\n locale: string,\r\n currency = 'USD'\r\n): string {\r\n return new Intl.NumberFormat(locale, {\r\n style: 'currency',\r\n currency,\r\n }).format(amount);\r\n}\r\n\r\nexport function pluralize(\r\n count: number,\r\n singular: string,\r\n plural: string\r\n): string {\r\n return count === 1 ? singular : plural;\r\n}"]}
@@ -0,0 +1,20 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { T as TranslationsRecord, L as Locale } from '../types-DrfIsMVB.mjs';
4
+
5
+ interface I18nContextType {
6
+ locale: Locale;
7
+ setLocale: (locale: Locale) => void;
8
+ t: (key: string, params?: Record<string, string | number>) => string;
9
+ isRTL: boolean;
10
+ }
11
+ interface I18nProviderProps {
12
+ children: ReactNode;
13
+ translations: TranslationsRecord;
14
+ defaultLocale: Locale;
15
+ fallbackLocale?: Locale;
16
+ }
17
+ declare function I18nProvider({ children, translations, defaultLocale, fallbackLocale, }: I18nProviderProps): react_jsx_runtime.JSX.Element;
18
+ declare function useI18n(): I18nContextType;
19
+
20
+ export { I18nProvider, useI18n };
@@ -0,0 +1,20 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { T as TranslationsRecord, L as Locale } from '../types-DrfIsMVB.js';
4
+
5
+ interface I18nContextType {
6
+ locale: Locale;
7
+ setLocale: (locale: Locale) => void;
8
+ t: (key: string, params?: Record<string, string | number>) => string;
9
+ isRTL: boolean;
10
+ }
11
+ interface I18nProviderProps {
12
+ children: ReactNode;
13
+ translations: TranslationsRecord;
14
+ defaultLocale: Locale;
15
+ fallbackLocale?: Locale;
16
+ }
17
+ declare function I18nProvider({ children, translations, defaultLocale, fallbackLocale, }: I18nProviderProps): react_jsx_runtime.JSX.Element;
18
+ declare function useI18n(): I18nContextType;
19
+
20
+ export { I18nProvider, useI18n };
@@ -0,0 +1,2 @@
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var a=class{constructor(e,o,n){if(this.translations=e,this.locale=o,this.fallbackLocale=n||o,typeof window<"u"){let t=localStorage.getItem("i18n-locale");t&&this.isValidLocale(t)&&(this.locale=t);}}isValidLocale(e){return e in this.translations}getNestedValue(e,o){let n=o.split("."),t=e;for(let s of n){if(!t||typeof t=="string")return;t=t[s];}return typeof t=="string"?t:void 0}t(e,o){let n=this.getNestedValue(this.translations[this.locale],e);return n||(n=this.getNestedValue(this.translations[this.fallbackLocale],e)),n?(o&&Object.keys(o).forEach(t=>{n=n.replace(new RegExp(`\\$\\{${t}\\}`,"g"),String(o[t]));}),n):(console.warn(`Translation missing for key: ${e}`),e)}setLocale(e){this.isValidLocale(e)&&(this.locale=e,typeof window<"u"&&(localStorage.setItem("i18n-locale",e),document.documentElement.setAttribute("lang",e),document.documentElement.setAttribute("dir",this.isRTL(e)?"rtl":"ltr")));}getLocale(){return this.locale}isRTL(e){let o=["ar","he","fa","ur"],n=e||this.locale;return o.includes(n)}};var d=react.createContext(void 0);function b({children:r,translations:e,defaultLocale:o,fallbackLocale:n}){let[t]=react.useState(()=>new a(e,o,n)),[s,f]=react.useState(t.getLocale()),[l,L]=react.useState(t.isRTL()),g=i=>{t.setLocale(i),f(i),L(t.isRTL(i));},m=(i,p)=>t.t(i,p);return react.useEffect(()=>{document.documentElement.setAttribute("dir",l?"rtl":"ltr"),document.documentElement.setAttribute("lang",s);},[s,l]),jsxRuntime.jsx(d.Provider,{value:{locale:s,setLocale:g,t:m,isRTL:l},children:r})}function u(){let r=react.useContext(d);if(!r)throw new Error("useI18n must be used within I18nProvider");return r}exports.I18nProvider=b;exports.useI18n=u;//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/I18n.ts","../../src/react/I18nProvider.tsx"],"names":["I18n","translations","defaultLocale","fallbackLocale","savedLocale","locale","obj","path","keys","current","key","params","translation","param","rtlLocales","checkLocale","I18nContext","createContext","I18nProvider","children","i18n","useState","setLocaleState","isRTL","setIsRTL","setLocale","newLocale","t","useEffect","jsx","useI18n","context","useContext"],"mappings":"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","file":"index.js","sourcesContent":["import { Locale, NestedTranslations, TranslationsRecord } from '../types';\r\n\r\nexport class I18n {\r\n private locale: Locale;\r\n private fallbackLocale: Locale;\r\n private translations: TranslationsRecord;\r\n\r\n constructor(\r\n translations: TranslationsRecord,\r\n defaultLocale: Locale,\r\n fallbackLocale?: Locale\r\n ) {\r\n this.translations = translations;\r\n this.locale = defaultLocale;\r\n this.fallbackLocale = fallbackLocale || defaultLocale;\r\n\r\n if (typeof window !== 'undefined') {\r\n const savedLocale = localStorage.getItem('i18n-locale');\r\n if (savedLocale && this.isValidLocale(savedLocale)) {\r\n this.locale = savedLocale;\r\n }\r\n }\r\n }\r\n\r\n private isValidLocale(locale: string): boolean {\r\n return locale in this.translations;\r\n }\r\n\r\n private getNestedValue(\r\n obj: NestedTranslations,\r\n path: string\r\n ): string | undefined {\r\n const keys = path.split('.');\r\n let current: string | NestedTranslations | undefined = obj;\r\n\r\n for (const key of keys) {\r\n if (!current || typeof current === 'string') return undefined;\r\n current = current[key];\r\n }\r\n\r\n return typeof current === 'string' ? current : undefined;\r\n }\r\n\r\n t(key: string, params?: Record<string, string | number>): string {\r\n let translation = this.getNestedValue(this.translations[this.locale], key);\r\n\r\n if (!translation) {\r\n translation = this.getNestedValue(\r\n this.translations[this.fallbackLocale],\r\n key\r\n );\r\n }\r\n\r\n if (!translation) {\r\n console.warn(`Translation missing for key: ${key}`);\r\n return key;\r\n }\r\n\r\n if (params) {\r\n Object.keys(params).forEach((param) => {\r\n translation = translation!.replace(\r\n new RegExp(`\\\\$\\\\{${param}\\\\}`, 'g'),\r\n String(params[param])\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n setLocale(locale: Locale): void {\r\n if (this.isValidLocale(locale)) {\r\n this.locale = locale;\r\n if (typeof window !== 'undefined') {\r\n localStorage.setItem('i18n-locale', locale);\r\n document.documentElement.setAttribute('lang', locale);\r\n document.documentElement.setAttribute(\r\n 'dir',\r\n this.isRTL(locale) ? 'rtl' : 'ltr'\r\n );\r\n }\r\n }\r\n }\r\n\r\n getLocale(): Locale {\r\n return this.locale;\r\n }\r\n\r\n isRTL(locale?: Locale): boolean {\r\n const rtlLocales = ['ar', 'he', 'fa', 'ur'];\r\n const checkLocale = locale || this.locale;\r\n return rtlLocales.includes(checkLocale);\r\n }\r\n}","'use client';\r\n\r\nimport { createContext, useContext, useState, useEffect, ReactNode } from 'react';\r\nimport { I18n } from '../core/I18n';\r\nimport { Locale, TranslationsRecord } from '../types';\r\n\r\nexport interface I18nContextType {\r\n locale: Locale;\r\n setLocale: (locale: Locale) => void;\r\n t: (key: string, params?: Record<string, string | number>) => string;\r\n isRTL: boolean;\r\n}\r\n\r\nexport const I18nContext = createContext<I18nContextType | undefined>(undefined);\r\n\r\ninterface I18nProviderProps {\r\n children: ReactNode;\r\n translations: TranslationsRecord;\r\n defaultLocale: Locale;\r\n fallbackLocale?: Locale;\r\n}\r\n\r\nexport function I18nProvider({\r\n children,\r\n translations,\r\n defaultLocale,\r\n fallbackLocale,\r\n}: I18nProviderProps) {\r\n const [i18n] = useState(\r\n () => new I18n(translations, defaultLocale, fallbackLocale)\r\n );\r\n const [locale, setLocaleState] = useState<Locale>(i18n.getLocale());\r\n const [isRTL, setIsRTL] = useState(i18n.isRTL());\r\n\r\n const setLocale = (newLocale: Locale) => {\r\n i18n.setLocale(newLocale);\r\n setLocaleState(newLocale);\r\n setIsRTL(i18n.isRTL(newLocale));\r\n };\r\n\r\n const t = (key: string, params?: Record<string, string | number>) => {\r\n return i18n.t(key, params);\r\n };\r\n\r\n useEffect(() => {\r\n document.documentElement.setAttribute('dir', isRTL ? 'rtl' : 'ltr');\r\n document.documentElement.setAttribute('lang', locale);\r\n }, [locale, isRTL]);\r\n\r\n return (\r\n <I18nContext.Provider value={{ locale, setLocale, t, isRTL }}>\r\n {children}\r\n </I18nContext.Provider>\r\n );\r\n}\r\n\r\n// Export useI18n hook here as well\r\nexport function useI18n() {\r\n const context = useContext(I18nContext);\r\n if (!context) {\r\n throw new Error('useI18n must be used within I18nProvider');\r\n }\r\n return context;\r\n}"]}
@@ -0,0 +1,2 @@
1
+ import {createContext,useState,useEffect,useContext}from'react';import {jsx}from'react/jsx-runtime';var a=class{constructor(e,o,n){if(this.translations=e,this.locale=o,this.fallbackLocale=n||o,typeof window<"u"){let t=localStorage.getItem("i18n-locale");t&&this.isValidLocale(t)&&(this.locale=t);}}isValidLocale(e){return e in this.translations}getNestedValue(e,o){let n=o.split("."),t=e;for(let s of n){if(!t||typeof t=="string")return;t=t[s];}return typeof t=="string"?t:void 0}t(e,o){let n=this.getNestedValue(this.translations[this.locale],e);return n||(n=this.getNestedValue(this.translations[this.fallbackLocale],e)),n?(o&&Object.keys(o).forEach(t=>{n=n.replace(new RegExp(`\\$\\{${t}\\}`,"g"),String(o[t]));}),n):(console.warn(`Translation missing for key: ${e}`),e)}setLocale(e){this.isValidLocale(e)&&(this.locale=e,typeof window<"u"&&(localStorage.setItem("i18n-locale",e),document.documentElement.setAttribute("lang",e),document.documentElement.setAttribute("dir",this.isRTL(e)?"rtl":"ltr")));}getLocale(){return this.locale}isRTL(e){let o=["ar","he","fa","ur"],n=e||this.locale;return o.includes(n)}};var d=createContext(void 0);function b({children:r,translations:e,defaultLocale:o,fallbackLocale:n}){let[t]=useState(()=>new a(e,o,n)),[s,f]=useState(t.getLocale()),[l,L]=useState(t.isRTL()),g=i=>{t.setLocale(i),f(i),L(t.isRTL(i));},m=(i,p)=>t.t(i,p);return useEffect(()=>{document.documentElement.setAttribute("dir",l?"rtl":"ltr"),document.documentElement.setAttribute("lang",s);},[s,l]),jsx(d.Provider,{value:{locale:s,setLocale:g,t:m,isRTL:l},children:r})}function u(){let r=useContext(d);if(!r)throw new Error("useI18n must be used within I18nProvider");return r}export{b as I18nProvider,u as useI18n};//# sourceMappingURL=index.mjs.map
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/I18n.ts","../../src/react/I18nProvider.tsx"],"names":["I18n","translations","defaultLocale","fallbackLocale","savedLocale","locale","obj","path","keys","current","key","params","translation","param","rtlLocales","checkLocale","I18nContext","createContext","I18nProvider","children","i18n","useState","setLocaleState","isRTL","setIsRTL","setLocale","newLocale","t","useEffect","jsx","useI18n","context","useContext"],"mappings":"oGAEO,IAAMA,CAAAA,CAAN,KAAW,CAKhB,WAAA,CACEC,EACAC,CAAAA,CACAC,CAAAA,CACA,CAKA,GAJA,IAAA,CAAK,YAAA,CAAeF,CAAAA,CACpB,KAAK,MAAA,CAASC,CAAAA,CACd,KAAK,cAAA,CAAiBC,CAAAA,EAAkBD,EAEpC,OAAO,MAAA,CAAW,IAAa,CACjC,IAAME,EAAc,YAAA,CAAa,OAAA,CAAQ,aAAa,CAAA,CAClDA,CAAAA,EAAe,KAAK,aAAA,CAAcA,CAAW,CAAA,GAC/C,IAAA,CAAK,OAASA,CAAAA,EAElB,CACF,CAEQ,aAAA,CAAcC,CAAAA,CAAyB,CAC7C,OAAOA,CAAAA,IAAU,IAAA,CAAK,YACxB,CAEQ,cAAA,CACNC,CAAAA,CACAC,EACoB,CACpB,IAAMC,EAAOD,CAAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CACvBE,EAAmDH,CAAAA,CAEvD,IAAA,IAAWI,KAAOF,CAAAA,CAAM,CACtB,GAAI,CAACC,CAAAA,EAAW,OAAOA,CAAAA,EAAY,QAAA,CAAU,OAC7CA,CAAAA,CAAUA,CAAAA,CAAQC,CAAG,EACvB,CAEA,OAAO,OAAOD,CAAAA,EAAY,QAAA,CAAWA,CAAAA,CAAU,MACjD,CAEA,CAAA,CAAEC,EAAaC,CAAAA,CAAkD,CAC/D,IAAIC,CAAAA,CAAc,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,aAAa,IAAA,CAAK,MAAM,EAAGF,CAAG,CAAA,CASzE,OAPKE,CAAAA,GACHA,CAAAA,CAAc,IAAA,CAAK,cAAA,CACjB,KAAK,YAAA,CAAa,IAAA,CAAK,cAAc,CAAA,CACrCF,CACF,GAGGE,CAAAA,EAKDD,CAAAA,EACF,OAAO,IAAA,CAAKA,CAAM,EAAE,OAAA,CAASE,CAAAA,EAAU,CACrCD,CAAAA,CAAcA,CAAAA,CAAa,QACzB,IAAI,MAAA,CAAO,CAAA,MAAA,EAASC,CAAK,MAAO,GAAG,CAAA,CACnC,OAAOF,CAAAA,CAAOE,CAAK,CAAC,CACtB,EACF,CAAC,CAAA,CAGID,IAbL,OAAA,CAAQ,IAAA,CAAK,gCAAgCF,CAAG,CAAA,CAAE,EAC3CA,CAAAA,CAaX,CAEA,SAAA,CAAUL,CAAAA,CAAsB,CAC1B,IAAA,CAAK,aAAA,CAAcA,CAAM,CAAA,GAC3B,IAAA,CAAK,OAASA,CAAAA,CACV,OAAO,OAAW,GAAA,GACpB,YAAA,CAAa,QAAQ,aAAA,CAAeA,CAAM,EAC1C,QAAA,CAAS,eAAA,CAAgB,aAAa,MAAA,CAAQA,CAAM,CAAA,CACpD,QAAA,CAAS,gBAAgB,YAAA,CACvB,KAAA,CACA,KAAK,KAAA,CAAMA,CAAM,EAAI,KAAA,CAAQ,KAC/B,CAAA,CAAA,EAGN,CAEA,WAAoB,CAClB,OAAO,KAAK,MACd,CAEA,MAAMA,CAAAA,CAA0B,CAC9B,IAAMS,CAAAA,CAAa,CAAC,IAAA,CAAM,IAAA,CAAM,KAAM,IAAI,CAAA,CACpCC,EAAcV,CAAAA,EAAU,IAAA,CAAK,OACnC,OAAOS,CAAAA,CAAW,SAASC,CAAW,CACxC,CACF,CAAA,CChFO,IAAMC,CAAAA,CAAcC,aAAAA,CAA2C,MAAS,CAAA,CASxE,SAASC,EAAa,CAC3B,QAAA,CAAAC,EACA,YAAA,CAAAlB,CAAAA,CACA,aAAA,CAAAC,CAAAA,CACA,eAAAC,CACF,CAAA,CAAsB,CACpB,GAAM,CAACiB,CAAI,CAAA,CAAIC,QAAAA,CACb,IAAM,IAAIrB,EAAKC,CAAAA,CAAcC,CAAAA,CAAeC,CAAc,CAC5D,CAAA,CACM,CAACE,CAAAA,CAAQiB,CAAc,EAAID,QAAAA,CAAiBD,CAAAA,CAAK,WAAW,CAAA,CAC5D,CAACG,CAAAA,CAAOC,CAAQ,EAAIH,QAAAA,CAASD,CAAAA,CAAK,KAAA,EAAO,EAEzCK,CAAAA,CAAaC,CAAAA,EAAsB,CACvCN,CAAAA,CAAK,SAAA,CAAUM,CAAS,CAAA,CACxBJ,CAAAA,CAAeI,CAAS,CAAA,CACxBF,EAASJ,CAAAA,CAAK,KAAA,CAAMM,CAAS,CAAC,EAChC,EAEMC,CAAAA,CAAI,CAACjB,CAAAA,CAAaC,CAAAA,GACfS,EAAK,CAAA,CAAEV,CAAAA,CAAKC,CAAM,CAAA,CAG3B,OAAAiB,UAAU,IAAM,CACd,SAAS,eAAA,CAAgB,YAAA,CAAa,MAAOL,CAAAA,CAAQ,KAAA,CAAQ,KAAK,CAAA,CAClE,QAAA,CAAS,gBAAgB,YAAA,CAAa,MAAA,CAAQlB,CAAM,EACtD,EAAG,CAACA,CAAAA,CAAQkB,CAAK,CAAC,CAAA,CAGhBM,IAACb,CAAAA,CAAY,QAAA,CAAZ,CAAqB,KAAA,CAAO,CAAE,OAAAX,CAAAA,CAAQ,SAAA,CAAAoB,EAAW,CAAA,CAAAE,CAAAA,CAAG,MAAAJ,CAAM,CAAA,CACxD,QAAA,CAAAJ,CAAAA,CACH,CAEJ,CAGO,SAASW,GAAU,CACxB,IAAMC,EAAUC,UAAAA,CAAWhB,CAAW,EACtC,GAAI,CAACe,EACH,MAAM,IAAI,MAAM,0CAA0C,CAAA,CAE5D,OAAOA,CACT","file":"index.mjs","sourcesContent":["import { Locale, NestedTranslations, TranslationsRecord } from '../types';\r\n\r\nexport class I18n {\r\n private locale: Locale;\r\n private fallbackLocale: Locale;\r\n private translations: TranslationsRecord;\r\n\r\n constructor(\r\n translations: TranslationsRecord,\r\n defaultLocale: Locale,\r\n fallbackLocale?: Locale\r\n ) {\r\n this.translations = translations;\r\n this.locale = defaultLocale;\r\n this.fallbackLocale = fallbackLocale || defaultLocale;\r\n\r\n if (typeof window !== 'undefined') {\r\n const savedLocale = localStorage.getItem('i18n-locale');\r\n if (savedLocale && this.isValidLocale(savedLocale)) {\r\n this.locale = savedLocale;\r\n }\r\n }\r\n }\r\n\r\n private isValidLocale(locale: string): boolean {\r\n return locale in this.translations;\r\n }\r\n\r\n private getNestedValue(\r\n obj: NestedTranslations,\r\n path: string\r\n ): string | undefined {\r\n const keys = path.split('.');\r\n let current: string | NestedTranslations | undefined = obj;\r\n\r\n for (const key of keys) {\r\n if (!current || typeof current === 'string') return undefined;\r\n current = current[key];\r\n }\r\n\r\n return typeof current === 'string' ? current : undefined;\r\n }\r\n\r\n t(key: string, params?: Record<string, string | number>): string {\r\n let translation = this.getNestedValue(this.translations[this.locale], key);\r\n\r\n if (!translation) {\r\n translation = this.getNestedValue(\r\n this.translations[this.fallbackLocale],\r\n key\r\n );\r\n }\r\n\r\n if (!translation) {\r\n console.warn(`Translation missing for key: ${key}`);\r\n return key;\r\n }\r\n\r\n if (params) {\r\n Object.keys(params).forEach((param) => {\r\n translation = translation!.replace(\r\n new RegExp(`\\\\$\\\\{${param}\\\\}`, 'g'),\r\n String(params[param])\r\n );\r\n });\r\n }\r\n\r\n return translation;\r\n }\r\n\r\n setLocale(locale: Locale): void {\r\n if (this.isValidLocale(locale)) {\r\n this.locale = locale;\r\n if (typeof window !== 'undefined') {\r\n localStorage.setItem('i18n-locale', locale);\r\n document.documentElement.setAttribute('lang', locale);\r\n document.documentElement.setAttribute(\r\n 'dir',\r\n this.isRTL(locale) ? 'rtl' : 'ltr'\r\n );\r\n }\r\n }\r\n }\r\n\r\n getLocale(): Locale {\r\n return this.locale;\r\n }\r\n\r\n isRTL(locale?: Locale): boolean {\r\n const rtlLocales = ['ar', 'he', 'fa', 'ur'];\r\n const checkLocale = locale || this.locale;\r\n return rtlLocales.includes(checkLocale);\r\n }\r\n}","'use client';\r\n\r\nimport { createContext, useContext, useState, useEffect, ReactNode } from 'react';\r\nimport { I18n } from '../core/I18n';\r\nimport { Locale, TranslationsRecord } from '../types';\r\n\r\nexport interface I18nContextType {\r\n locale: Locale;\r\n setLocale: (locale: Locale) => void;\r\n t: (key: string, params?: Record<string, string | number>) => string;\r\n isRTL: boolean;\r\n}\r\n\r\nexport const I18nContext = createContext<I18nContextType | undefined>(undefined);\r\n\r\ninterface I18nProviderProps {\r\n children: ReactNode;\r\n translations: TranslationsRecord;\r\n defaultLocale: Locale;\r\n fallbackLocale?: Locale;\r\n}\r\n\r\nexport function I18nProvider({\r\n children,\r\n translations,\r\n defaultLocale,\r\n fallbackLocale,\r\n}: I18nProviderProps) {\r\n const [i18n] = useState(\r\n () => new I18n(translations, defaultLocale, fallbackLocale)\r\n );\r\n const [locale, setLocaleState] = useState<Locale>(i18n.getLocale());\r\n const [isRTL, setIsRTL] = useState(i18n.isRTL());\r\n\r\n const setLocale = (newLocale: Locale) => {\r\n i18n.setLocale(newLocale);\r\n setLocaleState(newLocale);\r\n setIsRTL(i18n.isRTL(newLocale));\r\n };\r\n\r\n const t = (key: string, params?: Record<string, string | number>) => {\r\n return i18n.t(key, params);\r\n };\r\n\r\n useEffect(() => {\r\n document.documentElement.setAttribute('dir', isRTL ? 'rtl' : 'ltr');\r\n document.documentElement.setAttribute('lang', locale);\r\n }, [locale, isRTL]);\r\n\r\n return (\r\n <I18nContext.Provider value={{ locale, setLocale, t, isRTL }}>\r\n {children}\r\n </I18nContext.Provider>\r\n );\r\n}\r\n\r\n// Export useI18n hook here as well\r\nexport function useI18n() {\r\n const context = useContext(I18nContext);\r\n if (!context) {\r\n throw new Error('useI18n must be used within I18nProvider');\r\n }\r\n return context;\r\n}"]}
@@ -0,0 +1,12 @@
1
+ type Locale = string;
2
+ type NestedTranslations = {
3
+ [key: string]: string | NestedTranslations;
4
+ };
5
+ type TranslationsRecord = Record<Locale, NestedTranslations>;
6
+ interface I18nConfig {
7
+ defaultLocale: Locale;
8
+ fallbackLocale?: Locale;
9
+ translations: TranslationsRecord;
10
+ }
11
+
12
+ export type { I18nConfig as I, Locale as L, NestedTranslations as N, TranslationsRecord as T };
@@ -0,0 +1,12 @@
1
+ type Locale = string;
2
+ type NestedTranslations = {
3
+ [key: string]: string | NestedTranslations;
4
+ };
5
+ type TranslationsRecord = Record<Locale, NestedTranslations>;
6
+ interface I18nConfig {
7
+ defaultLocale: Locale;
8
+ fallbackLocale?: Locale;
9
+ translations: TranslationsRecord;
10
+ }
11
+
12
+ export type { I18nConfig as I, Locale as L, NestedTranslations as N, TranslationsRecord as T };
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "next-i18n-lite",
3
+ "version": "1.0.3",
4
+ "description": "Lightweight i18n library for Next.js with TypeScript support",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./react": {
15
+ "types": "./dist/react/index.d.ts",
16
+ "import": "./dist/react/index.mjs",
17
+ "require": "./dist/react/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "prepublishOnly": "npm run build",
29
+ "test": "echo \"Error: no test specified\" && exit 1"
30
+ },
31
+ "keywords": [
32
+ "i18n",
33
+ "internationalization",
34
+ "nextjs",
35
+ "react",
36
+ "typescript",
37
+ "localization",
38
+ "translation"
39
+ ],
40
+ "author": "codexpro410 <codexpro410@gmail.com>",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/codexpro410/next-i18n-lite.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/codexpro410/next-i18n-lite/issues"
48
+ },
49
+ "homepage": "https://github.com/codexpro410/next-i18n-lite#readme",
50
+ "peerDependencies": {
51
+ "react": ">=18.0.0",
52
+ "react-dom": ">=18.0.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "react": {
56
+ "optional": false
57
+ },
58
+ "react-dom": {
59
+ "optional": false
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^20.0.0",
64
+ "@types/react": "^18.0.0",
65
+ "@types/react-dom": "^18.0.0",
66
+ "react": "^18.0.0",
67
+ "react-dom": "^18.0.0",
68
+ "tsup": "^8.0.0",
69
+ "typescript": "^5.0.0"
70
+ },
71
+ "engines": {
72
+ "node": ">=18.0.0",
73
+ "npm": ">=9.0.0"
74
+ }
75
+ }