@owlmeans/client-i18n 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +488 -0
- package/build/context.d.ts +8 -0
- package/build/context.d.ts.map +1 -0
- package/build/context.js +39 -0
- package/build/context.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/types.d.ts +15 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/index.d.ts +2 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +2 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/instance.d.ts +5 -0
- package/build/utils/instance.d.ts.map +1 -0
- package/build/utils/instance.js +24 -0
- package/build/utils/instance.js.map +1 -0
- package/package.json +48 -0
- package/src/context.tsx +56 -0
- package/src/index.ts +3 -0
- package/src/types.ts +17 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/instance.ts +35 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
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,488 @@
|
|
|
1
|
+
# @owlmeans/client-i18n
|
|
2
|
+
|
|
3
|
+
The **@owlmeans/client-i18n** package provides React-based internationalization functionality for OwlMeans Common Libraries, designed for fullstack microservices and microclients development with focus on security and proper authentication and authorization.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package serves as the client-side React integration layer for the OwlMeans i18n system that:
|
|
8
|
+
|
|
9
|
+
- **Integrates with React applications** using React Context and hooks
|
|
10
|
+
- **Provides React hooks for translations** with automatic resource loading
|
|
11
|
+
- **Supports dynamic resource loading** with automatic caching
|
|
12
|
+
- **Integrates with client context** for configuration-driven internationalization
|
|
13
|
+
- **Offers multi-level translation support** (library, app, service levels)
|
|
14
|
+
- **Enables namespace-based organization** for different translation contexts
|
|
15
|
+
|
|
16
|
+
## Core Concepts
|
|
17
|
+
|
|
18
|
+
### I18n Context Provider
|
|
19
|
+
A React Context Provider that initializes and manages the i18next instance for the entire React application, integrated with OwlMeans client configuration.
|
|
20
|
+
|
|
21
|
+
### Automatic Resource Loading
|
|
22
|
+
Translation resources are automatically loaded and registered with i18next when requested through hooks, with intelligent caching to prevent duplicate loading.
|
|
23
|
+
|
|
24
|
+
### Multi-Level Translation Hooks
|
|
25
|
+
Different hooks for accessing translations at different levels:
|
|
26
|
+
- Library-level translations (`useI18nLib`)
|
|
27
|
+
- Application-level translations (`useI18nApp`)
|
|
28
|
+
- Service-level translations (`useCommonI18n`)
|
|
29
|
+
|
|
30
|
+
### Client Context Integration
|
|
31
|
+
Seamless integration with OwlMeans client context system for configuration-driven internationalization setup.
|
|
32
|
+
|
|
33
|
+
## API Reference
|
|
34
|
+
|
|
35
|
+
### Components
|
|
36
|
+
|
|
37
|
+
#### `I18nContext`
|
|
38
|
+
|
|
39
|
+
React Context Provider component that provides i18n functionality to the entire React application.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { I18nContext } from '@owlmeans/client-i18n'
|
|
43
|
+
import { ClientConfig } from '@owlmeans/client-context'
|
|
44
|
+
|
|
45
|
+
const App: React.FC = () => {
|
|
46
|
+
const config = useClientConfig() // Your client configuration
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<I18nContext config={config}>
|
|
50
|
+
<AppContent />
|
|
51
|
+
</I18nContext>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Props:**
|
|
57
|
+
- `config`: ClientConfig - The client configuration object
|
|
58
|
+
- `children`: ReactNode - Child components
|
|
59
|
+
|
|
60
|
+
### Hooks
|
|
61
|
+
|
|
62
|
+
#### `useCommonI18n(resourceName, ns?, prefix?): TFunction`
|
|
63
|
+
|
|
64
|
+
Main hook for accessing translations with automatic resource loading.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { useCommonI18n } from '@owlmeans/client-i18n'
|
|
68
|
+
|
|
69
|
+
const MyComponent: React.FC = () => {
|
|
70
|
+
const t = useCommonI18n('user-forms', 'ui', 'profile')
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
<h1>{t('title')}</h1>
|
|
75
|
+
<p>{t('description')}</p>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Parameters:**
|
|
82
|
+
- `resourceName`: string - Name of the translation resource to load
|
|
83
|
+
- `ns`: string (optional) - Namespace for the translations (defaults to default namespace)
|
|
84
|
+
- `prefix`: string (optional) - Key prefix for translation lookups
|
|
85
|
+
|
|
86
|
+
**Returns:** `TFunction` - i18next translation function
|
|
87
|
+
|
|
88
|
+
**Behavior:**
|
|
89
|
+
- Automatically loads translation resources for the current language
|
|
90
|
+
- Caches loaded resources to prevent duplicate loading
|
|
91
|
+
- Falls back to default language if current language resources are not available
|
|
92
|
+
- Supports multi-level translation priority (service > app > library)
|
|
93
|
+
|
|
94
|
+
#### `useI18nLib(libName, prefix?): TFunction`
|
|
95
|
+
|
|
96
|
+
Hook for accessing library-level translations in the 'lib' namespace.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { useI18nLib } from '@owlmeans/client-i18n'
|
|
100
|
+
|
|
101
|
+
const LibraryComponent: React.FC = () => {
|
|
102
|
+
const t = useI18nLib('common-ui', 'buttons')
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<button>{t('save')}</button>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Parameters:**
|
|
111
|
+
- `libName`: string - Name of the library translation resource
|
|
112
|
+
- `prefix`: string (optional) - Key prefix for translation lookups
|
|
113
|
+
|
|
114
|
+
**Returns:** `TFunction` - i18next translation function configured for library namespace
|
|
115
|
+
|
|
116
|
+
#### `useI18nApp(appName?, prefix?): TFunction`
|
|
117
|
+
|
|
118
|
+
Hook for accessing application-level translations using the service name from context.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { useI18nApp } from '@owlmeans/client-i18n'
|
|
122
|
+
|
|
123
|
+
const AppComponent: React.FC = () => {
|
|
124
|
+
const t = useI18nApp(undefined, 'navigation')
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<nav>
|
|
128
|
+
<a href="/">{t('home')}</a>
|
|
129
|
+
<a href="/profile">{t('profile')}</a>
|
|
130
|
+
</nav>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Parameters:**
|
|
136
|
+
- `appName`: string (optional) - Name of the app (defaults to service name from context)
|
|
137
|
+
- `prefix`: string (optional) - Key prefix for translation lookups
|
|
138
|
+
|
|
139
|
+
**Returns:** `TFunction` - i18next translation function configured for app namespace
|
|
140
|
+
|
|
141
|
+
### Types
|
|
142
|
+
|
|
143
|
+
#### `I18nContextProps`
|
|
144
|
+
|
|
145
|
+
Props interface for the I18nContext component.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
interface I18nContextProps extends PropsWithChildren {
|
|
149
|
+
config: ClientConfig
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### `I18nProps`
|
|
154
|
+
|
|
155
|
+
Generic interface for components that accept i18n properties.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
interface I18nProps {
|
|
159
|
+
i18n?: I18nBaseProps
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `I18nBaseProps`
|
|
164
|
+
|
|
165
|
+
Base properties for i18n configuration.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
interface I18nBaseProps {
|
|
169
|
+
resource?: string // Resource name override
|
|
170
|
+
ns?: string // Namespace override
|
|
171
|
+
prefix?: string // Key prefix override
|
|
172
|
+
suppress?: boolean // Suppress translation loading
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Utilities
|
|
177
|
+
|
|
178
|
+
The package also exports utilities under the `/utils` subpackage:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { useI18nInstance } from '@owlmeans/client-i18n/utils'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `useI18nInstance(config): i18n`
|
|
185
|
+
|
|
186
|
+
Hook that creates and configures an i18next instance based on client configuration.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { useI18nInstance } from '@owlmeans/client-i18n/utils'
|
|
190
|
+
|
|
191
|
+
const config = useClientConfig()
|
|
192
|
+
const i18n = useI18nInstance(config)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Usage Examples
|
|
196
|
+
|
|
197
|
+
### Basic Setup
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import React from 'react'
|
|
201
|
+
import { render } from 'react-dom'
|
|
202
|
+
import { I18nContext } from '@owlmeans/client-i18n'
|
|
203
|
+
import { makeClientConfig, ClientContext } from '@owlmeans/client-context'
|
|
204
|
+
import { AppType } from '@owlmeans/context'
|
|
205
|
+
|
|
206
|
+
const config = makeClientConfig(AppType.Frontend, 'my-app', {
|
|
207
|
+
// Client configuration
|
|
208
|
+
defaultLng: 'en',
|
|
209
|
+
debug: { i18n: true }
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const App: React.FC = () => (
|
|
213
|
+
<ClientContext.Provider value={context}>
|
|
214
|
+
<I18nContext config={config}>
|
|
215
|
+
<MainApp />
|
|
216
|
+
</I18nContext>
|
|
217
|
+
</ClientContext.Provider>
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
render(<App />, document.getElementById('root'))
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Component with Translations
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import React from 'react'
|
|
227
|
+
import { useCommonI18n, useI18nLib, useI18nApp } from '@owlmeans/client-i18n'
|
|
228
|
+
|
|
229
|
+
const UserProfileForm: React.FC = () => {
|
|
230
|
+
// Different translation levels
|
|
231
|
+
const tLib = useI18nLib('forms') // Library translations
|
|
232
|
+
const tApp = useI18nApp() // App-specific translations
|
|
233
|
+
const tUser = useCommonI18n('user-profile', 'ui') // Service translations
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<form>
|
|
237
|
+
<h1>{tApp('profile.title')}</h1>
|
|
238
|
+
|
|
239
|
+
<label>
|
|
240
|
+
{tUser('name.label')}
|
|
241
|
+
<input placeholder={tUser('name.placeholder')} />
|
|
242
|
+
</label>
|
|
243
|
+
|
|
244
|
+
<label>
|
|
245
|
+
{tUser('email.label')}
|
|
246
|
+
<input type="email" placeholder={tUser('email.placeholder')} />
|
|
247
|
+
</label>
|
|
248
|
+
|
|
249
|
+
<div>
|
|
250
|
+
<button type="submit">{tLib('save')}</button>
|
|
251
|
+
<button type="button">{tLib('cancel')}</button>
|
|
252
|
+
</div>
|
|
253
|
+
</form>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Custom Hook for Component Translations
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { useCommonI18n } from '@owlmeans/client-i18n'
|
|
262
|
+
|
|
263
|
+
const useProductTranslations = (prefix?: string) => {
|
|
264
|
+
return useCommonI18n('products', 'ecommerce', prefix)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ProductCard: React.FC<{ product: Product }> = ({ product }) => {
|
|
268
|
+
const t = useProductTranslations('card')
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className="product-card">
|
|
272
|
+
<h3>{product.name}</h3>
|
|
273
|
+
<p>{t('price')}: ${product.price}</p>
|
|
274
|
+
<button>{t('addToCart')}</button>
|
|
275
|
+
</div>
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Dynamic Resource Loading
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import React, { useState } from 'react'
|
|
284
|
+
import { useCommonI18n } from '@owlmeans/client-i18n'
|
|
285
|
+
|
|
286
|
+
const DynamicContent: React.FC = () => {
|
|
287
|
+
const [selectedModule, setSelectedModule] = useState('dashboard')
|
|
288
|
+
|
|
289
|
+
// Translations are loaded dynamically based on selected module
|
|
290
|
+
const t = useCommonI18n(selectedModule, 'modules')
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div>
|
|
294
|
+
<select
|
|
295
|
+
value={selectedModule}
|
|
296
|
+
onChange={(e) => setSelectedModule(e.target.value)}
|
|
297
|
+
>
|
|
298
|
+
<option value="dashboard">Dashboard</option>
|
|
299
|
+
<option value="analytics">Analytics</option>
|
|
300
|
+
<option value="settings">Settings</option>
|
|
301
|
+
</select>
|
|
302
|
+
|
|
303
|
+
<div>
|
|
304
|
+
<h2>{t('title')}</h2>
|
|
305
|
+
<p>{t('description')}</p>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Error Handling and Fallbacks
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import React from 'react'
|
|
316
|
+
import { useCommonI18n } from '@owlmeans/client-i18n'
|
|
317
|
+
|
|
318
|
+
const SafeTranslatedComponent: React.FC = () => {
|
|
319
|
+
const t = useCommonI18n('user-interface', 'ui')
|
|
320
|
+
|
|
321
|
+
// Translation function handles missing keys gracefully
|
|
322
|
+
return (
|
|
323
|
+
<div>
|
|
324
|
+
{/* Will show key if translation missing */}
|
|
325
|
+
<h1>{t('welcome', 'Welcome')}</h1>
|
|
326
|
+
|
|
327
|
+
{/* Will use fallback value */}
|
|
328
|
+
<p>{t('description', { defaultValue: 'Default description' })}</p>
|
|
329
|
+
|
|
330
|
+
{/* With interpolation */}
|
|
331
|
+
<p>{t('greeting', { name: 'User', defaultValue: 'Hello, {{name}}!' })}</p>
|
|
332
|
+
</div>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Multi-Language Support
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import React from 'react'
|
|
341
|
+
import { useTranslation } from 'react-i18next'
|
|
342
|
+
import { useCommonI18n } from '@owlmeans/client-i18n'
|
|
343
|
+
|
|
344
|
+
const LanguageSwitcher: React.FC = () => {
|
|
345
|
+
const { i18n } = useTranslation()
|
|
346
|
+
const t = useCommonI18n('language-switcher', 'ui')
|
|
347
|
+
|
|
348
|
+
const changeLanguage = (lng: string) => {
|
|
349
|
+
i18n.changeLanguage(lng)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<div>
|
|
354
|
+
<h3>{t('selectLanguage')}</h3>
|
|
355
|
+
<button onClick={() => changeLanguage('en')}>
|
|
356
|
+
{t('languages.english')}
|
|
357
|
+
</button>
|
|
358
|
+
<button onClick={() => changeLanguage('es')}>
|
|
359
|
+
{t('languages.spanish')}
|
|
360
|
+
</button>
|
|
361
|
+
<button onClick={() => changeLanguage('fr')}>
|
|
362
|
+
{t('languages.french')}
|
|
363
|
+
</button>
|
|
364
|
+
</div>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Integration Patterns
|
|
370
|
+
|
|
371
|
+
### With Component Libraries
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// Create a wrapper component for your design system
|
|
375
|
+
import React from 'react'
|
|
376
|
+
import { useI18nLib } from '@owlmeans/client-i18n'
|
|
377
|
+
|
|
378
|
+
interface ButtonProps {
|
|
379
|
+
variant: 'primary' | 'secondary'
|
|
380
|
+
i18nKey: string
|
|
381
|
+
children?: React.ReactNode
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const TranslatedButton: React.FC<ButtonProps> = ({
|
|
385
|
+
variant,
|
|
386
|
+
i18nKey,
|
|
387
|
+
children
|
|
388
|
+
}) => {
|
|
389
|
+
const t = useI18nLib('ui-components', 'buttons')
|
|
390
|
+
|
|
391
|
+
return (
|
|
392
|
+
<button className={`btn btn-${variant}`}>
|
|
393
|
+
{children || t(i18nKey)}
|
|
394
|
+
</button>
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Usage
|
|
399
|
+
<TranslatedButton variant="primary" i18nKey="save" />
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### With Forms
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import React from 'react'
|
|
406
|
+
import { useForm } from 'react-hook-form'
|
|
407
|
+
import { useCommonI18n } from '@owlmeans/client-i18n'
|
|
408
|
+
|
|
409
|
+
const LoginForm: React.FC = () => {
|
|
410
|
+
const t = useCommonI18n('auth-forms', 'auth')
|
|
411
|
+
const { register, handleSubmit, formState: { errors } } = useForm()
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
415
|
+
<div>
|
|
416
|
+
<label>{t('email.label')}</label>
|
|
417
|
+
<input
|
|
418
|
+
{...register('email', { required: t('email.required') })}
|
|
419
|
+
placeholder={t('email.placeholder')}
|
|
420
|
+
/>
|
|
421
|
+
{errors.email && <span>{errors.email.message}</span>}
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<div>
|
|
425
|
+
<label>{t('password.label')}</label>
|
|
426
|
+
<input
|
|
427
|
+
type="password"
|
|
428
|
+
{...register('password', { required: t('password.required') })}
|
|
429
|
+
placeholder={t('password.placeholder')}
|
|
430
|
+
/>
|
|
431
|
+
{errors.password && <span>{errors.password.message}</span>}
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<button type="submit">{t('submit')}</button>
|
|
435
|
+
</form>
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Performance Considerations
|
|
441
|
+
|
|
442
|
+
### Resource Loading Caching
|
|
443
|
+
The package implements intelligent caching:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// Resources are cached per language and resource name
|
|
447
|
+
const i18nLoadingCache = new Set<string>()
|
|
448
|
+
|
|
449
|
+
// Cache key format: "{language}:{resourceName}:{namespace}"
|
|
450
|
+
const key = `${i18n.language}:${resourceName}:${ns}`
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Automatic Resource Management
|
|
454
|
+
- **Lazy Loading** - Resources are only loaded when first requested
|
|
455
|
+
- **Duplicate Prevention** - Multiple components using the same resource won't trigger multiple loads
|
|
456
|
+
- **Fallback Loading** - Default language resources are loaded as fallbacks
|
|
457
|
+
|
|
458
|
+
### Best Practices
|
|
459
|
+
|
|
460
|
+
1. **Use specific resource names** - Avoid loading large translation bundles
|
|
461
|
+
2. **Leverage namespaces** - Organize translations by functional area
|
|
462
|
+
3. **Implement prefixes** - Use prefixes to scope translations to specific components
|
|
463
|
+
4. **Cache translation functions** - Don't recreate translation functions unnecessarily
|
|
464
|
+
|
|
465
|
+
## Error Handling
|
|
466
|
+
|
|
467
|
+
The package handles various error scenarios gracefully:
|
|
468
|
+
|
|
469
|
+
- **Missing resources** - Falls back to translation keys or default values
|
|
470
|
+
- **Network failures** - Uses cached resources or fallback languages
|
|
471
|
+
- **Invalid configurations** - Provides sensible defaults
|
|
472
|
+
- **Component unmounting** - Prevents memory leaks from resource loading
|
|
473
|
+
|
|
474
|
+
## Dependencies
|
|
475
|
+
|
|
476
|
+
This package depends on:
|
|
477
|
+
- `@owlmeans/client` - Client-side context hooks
|
|
478
|
+
- `@owlmeans/client-context` - Client context management
|
|
479
|
+
- `@owlmeans/i18n` - Core i18n functionality and resource management
|
|
480
|
+
- `react` - React framework (peer dependency)
|
|
481
|
+
- `react-i18next` - React integration for i18next
|
|
482
|
+
- `i18next` - Core i18n library
|
|
483
|
+
|
|
484
|
+
## Related Packages
|
|
485
|
+
|
|
486
|
+
- [`@owlmeans/i18n`](../i18n) - Core i18n functionality and resource management
|
|
487
|
+
- [`@owlmeans/client-context`](../client-context) - Client-side context management
|
|
488
|
+
- [`@owlmeans/client`](../client) - Client-side utilities and hooks
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { I18nContextProps } from './types.js';
|
|
3
|
+
import type { TFunction } from 'i18next';
|
|
4
|
+
export declare const I18nContext: FC<I18nContextProps>;
|
|
5
|
+
export declare const useCommonI18n: (resourceName: string, ns?: string, prefix?: string) => TFunction;
|
|
6
|
+
export declare const useI18nLib: (libName: string, prefix?: string) => TFunction;
|
|
7
|
+
export declare const useI18nApp: (appName?: string, prefix?: string) => TFunction;
|
|
8
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAKxC,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAI3C,CAAA;AAIF,eAAO,MAAM,aAAa,iBAAkB,MAAM,OAAO,MAAM,WAAW,MAAM,KAAG,SA2BlF,CAAA;AAED,eAAO,MAAM,UAAU,YAAa,MAAM,WAAW,MAAM,KAAG,SAE7D,CAAA;AAED,eAAO,MAAM,UAAU,aAAc,MAAM,WAAW,MAAM,KAAG,SAK9D,CAAA"}
|
package/build/context.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { I18nextProvider, useTranslation } from 'react-i18next';
|
|
4
|
+
import { useI18nInstance } from './utils/instance.js';
|
|
5
|
+
import { DEFAULT_LNG, DEFAULT_NAMESPACE, initI18nResource, LIB_NAMESPACE } from '@owlmeans/i18n';
|
|
6
|
+
import { useContext } from '@owlmeans/client';
|
|
7
|
+
export const I18nContext = memo(({ config, children }) => {
|
|
8
|
+
const i18n = useI18nInstance(config);
|
|
9
|
+
return _jsx(I18nextProvider, { i18n: i18n, children: children });
|
|
10
|
+
});
|
|
11
|
+
const i18nLoadingCache = new Set();
|
|
12
|
+
export const useCommonI18n = (resourceName, ns, prefix) => {
|
|
13
|
+
const { i18n } = useTranslation();
|
|
14
|
+
ns = ns ?? (Array.isArray(i18n.options.defaultNS)
|
|
15
|
+
? i18n.options.defaultNS?.[0] : i18n.options.defaultNS)
|
|
16
|
+
?? DEFAULT_NAMESPACE;
|
|
17
|
+
const key = `${i18n.language}:${resourceName}:${ns}`;
|
|
18
|
+
if (!i18nLoadingCache.has(key)) {
|
|
19
|
+
i18nLoadingCache.add(key);
|
|
20
|
+
const resources = initI18nResource(i18n.language, resourceName, ns);
|
|
21
|
+
if (resources != null) {
|
|
22
|
+
resources.forEach(resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true));
|
|
23
|
+
const defLng = typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : DEFAULT_LNG;
|
|
24
|
+
const defResources = initI18nResource(defLng, resourceName, ns);
|
|
25
|
+
defResources?.forEach(resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const { t } = useTranslation(ns, { keyPrefix: `${resourceName}${prefix != null && prefix != '' ? `.${prefix}` : ''}` });
|
|
29
|
+
return t;
|
|
30
|
+
};
|
|
31
|
+
export const useI18nLib = (libName, prefix) => {
|
|
32
|
+
return useCommonI18n(libName, LIB_NAMESPACE, prefix);
|
|
33
|
+
};
|
|
34
|
+
export const useI18nApp = (appName, prefix) => {
|
|
35
|
+
const context = useContext();
|
|
36
|
+
appName = appName ?? context.cfg.service;
|
|
37
|
+
return useCommonI18n(appName, appName, prefix);
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ClientConfig } from '@owlmeans/client-context';
|
|
2
|
+
import type { PropsWithChildren } from 'react';
|
|
3
|
+
export interface I18nContextProps extends PropsWithChildren {
|
|
4
|
+
config: ClientConfig;
|
|
5
|
+
}
|
|
6
|
+
export interface I18nProps {
|
|
7
|
+
i18n?: I18nBaseProps;
|
|
8
|
+
}
|
|
9
|
+
export interface I18nBaseProps {
|
|
10
|
+
resource?: string;
|
|
11
|
+
ns?: string;
|
|
12
|
+
prefix?: string;
|
|
13
|
+
suppress?: boolean;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAE9C,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,MAAM,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,eAAe,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { i18n } from 'i18next';
|
|
2
|
+
import type { ClientConfig } from '@owlmeans/client-context';
|
|
3
|
+
export declare const useI18nInstance: (config: ClientConfig) => i18n;
|
|
4
|
+
export declare const getI18nInstance: (config: ClientConfig) => i18n;
|
|
5
|
+
//# sourceMappingURL=instance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/utils/instance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAEnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAK5D,eAAO,MAAM,eAAe,WAAY,YAAY,KAAG,IAItD,CAAA;AAED,eAAO,MAAM,eAAe,WAAY,YAAY,KAAG,IAC3B,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createInstance } from 'i18next';
|
|
2
|
+
import { initReactI18next } from 'react-i18next';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { DEFAULT_LNG, DEFAULT_NAMESPACE } from '@owlmeans/i18n';
|
|
5
|
+
let i18nInstance = null;
|
|
6
|
+
export const useI18nInstance = (config) => {
|
|
7
|
+
const instance = useMemo(() => createI18nInstance(config), []);
|
|
8
|
+
return instance;
|
|
9
|
+
};
|
|
10
|
+
export const getI18nInstance = (config) => createI18nInstance(config);
|
|
11
|
+
const createI18nInstance = (config) => {
|
|
12
|
+
if (i18nInstance != null) {
|
|
13
|
+
return i18nInstance;
|
|
14
|
+
}
|
|
15
|
+
const instance = createInstance({
|
|
16
|
+
compatibilityJSON: 'v3',
|
|
17
|
+
defaultNS: config.i18n?.defaultNs ?? DEFAULT_NAMESPACE,
|
|
18
|
+
fallbackLng: config.i18n?.defaultLng ?? DEFAULT_LNG,
|
|
19
|
+
debug: config.debug?.all ?? config.debug?.i18n ?? false,
|
|
20
|
+
});
|
|
21
|
+
instance.use(initReactI18next).init();
|
|
22
|
+
return i18nInstance = instance;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=instance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../../src/utils/instance.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAE/D,IAAI,YAAY,GAAgB,IAAI,CAAA;AAEpC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAoB,EAAQ,EAAE;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IAE9D,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAoB,EAAQ,EAAE,CAC5D,kBAAkB,CAAC,MAAM,CAAC,CAAA;AAE5B,MAAM,kBAAkB,GAAG,CAAC,MAAoB,EAAQ,EAAE;IACxD,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC;QACzB,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,MAAM,QAAQ,GAAG,cAAc,CAAC;QAC9B,iBAAiB,EAAE,IAAI;QACvB,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,IAAI,iBAAiB;QACtD,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,IAAI,WAAW;QACnD,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,KAAK;KACxD,CAAC,CAAA;IAEF,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,EAAE,CAAA;IAErC,OAAO,YAAY,GAAG,QAAQ,CAAA;AAChC,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/client-i18n",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 96 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./utils": {
|
|
22
|
+
"import": "./build/utils/index.js",
|
|
23
|
+
"require": "./build/utils/index.js",
|
|
24
|
+
"default": "./build/utils/index.js",
|
|
25
|
+
"module": "./build/utils/index.js",
|
|
26
|
+
"types": "./build/utils/index.d.ts"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"react": "*"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^18.3.11",
|
|
34
|
+
"nodemon": "^3.1.7",
|
|
35
|
+
"typescript": "^5.6.3"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@owlmeans/client": "^0.1.0",
|
|
39
|
+
"@owlmeans/client-context": "^0.1.0",
|
|
40
|
+
"@owlmeans/i18n": "^0.1.0",
|
|
41
|
+
"i18next": "^23.15.1",
|
|
42
|
+
"react-i18next": "^15.0.2"
|
|
43
|
+
},
|
|
44
|
+
"private": false,
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { memo } from 'react'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import type { I18nContextProps } from './types.js'
|
|
4
|
+
import { I18nextProvider, useTranslation } from 'react-i18next'
|
|
5
|
+
import type { TFunction } from 'i18next'
|
|
6
|
+
import { useI18nInstance } from './utils/instance.js'
|
|
7
|
+
import { DEFAULT_LNG, DEFAULT_NAMESPACE, initI18nResource, LIB_NAMESPACE } from '@owlmeans/i18n'
|
|
8
|
+
import { useContext } from '@owlmeans/client'
|
|
9
|
+
|
|
10
|
+
export const I18nContext: FC<I18nContextProps> = memo(({ config, children }) => {
|
|
11
|
+
const i18n = useI18nInstance(config)
|
|
12
|
+
|
|
13
|
+
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const i18nLoadingCache = new Set<string>()
|
|
17
|
+
|
|
18
|
+
export const useCommonI18n = (resourceName: string, ns?: string, prefix?: string): TFunction => {
|
|
19
|
+
const { i18n } = useTranslation()
|
|
20
|
+
|
|
21
|
+
ns = ns ?? (Array.isArray(i18n.options.defaultNS)
|
|
22
|
+
? i18n.options.defaultNS?.[0] : i18n.options.defaultNS)
|
|
23
|
+
?? DEFAULT_NAMESPACE
|
|
24
|
+
|
|
25
|
+
const key = `${i18n.language}:${resourceName}:${ns}`
|
|
26
|
+
if (!i18nLoadingCache.has(key)) {
|
|
27
|
+
i18nLoadingCache.add(key)
|
|
28
|
+
const resources = initI18nResource(i18n.language, resourceName, ns)
|
|
29
|
+
if (resources != null) {
|
|
30
|
+
resources.forEach(
|
|
31
|
+
resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const defLng = typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : DEFAULT_LNG
|
|
35
|
+
const defResources = initI18nResource(defLng, resourceName, ns)
|
|
36
|
+
defResources?.forEach(
|
|
37
|
+
resource => i18n.addResourceBundle(i18n.language, ns ?? DEFAULT_NAMESPACE, { [resourceName]: resource.data }, true, true)
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { t } = useTranslation(ns, { keyPrefix: `${resourceName}${prefix != null && prefix != '' ? `.${prefix}` : ''}` })
|
|
43
|
+
|
|
44
|
+
return t
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const useI18nLib = (libName: string, prefix?: string): TFunction => {
|
|
48
|
+
return useCommonI18n(libName, LIB_NAMESPACE, prefix)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const useI18nApp = (appName?: string, prefix?: string): TFunction => {
|
|
52
|
+
const context = useContext()
|
|
53
|
+
appName = appName ?? context.cfg.service
|
|
54
|
+
|
|
55
|
+
return useCommonI18n(appName, appName, prefix)
|
|
56
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ClientConfig } from '@owlmeans/client-context'
|
|
2
|
+
import type { PropsWithChildren } from 'react'
|
|
3
|
+
|
|
4
|
+
export interface I18nContextProps extends PropsWithChildren {
|
|
5
|
+
config: ClientConfig
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface I18nProps {
|
|
9
|
+
i18n?: I18nBaseProps
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface I18nBaseProps {
|
|
13
|
+
resource?: string
|
|
14
|
+
ns?: string
|
|
15
|
+
prefix?: string
|
|
16
|
+
suppress?: boolean
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
import { createInstance } from 'i18next'
|
|
3
|
+
import { initReactI18next } from 'react-i18next'
|
|
4
|
+
import type { i18n } from 'i18next'
|
|
5
|
+
import { useMemo } from 'react'
|
|
6
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
7
|
+
import { DEFAULT_LNG, DEFAULT_NAMESPACE } from '@owlmeans/i18n'
|
|
8
|
+
|
|
9
|
+
let i18nInstance: i18n | null = null
|
|
10
|
+
|
|
11
|
+
export const useI18nInstance = (config: ClientConfig): i18n => {
|
|
12
|
+
const instance = useMemo(() => createI18nInstance(config), [])
|
|
13
|
+
|
|
14
|
+
return instance
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getI18nInstance = (config: ClientConfig): i18n =>
|
|
18
|
+
createI18nInstance(config)
|
|
19
|
+
|
|
20
|
+
const createI18nInstance = (config: ClientConfig): i18n => {
|
|
21
|
+
if (i18nInstance != null) {
|
|
22
|
+
return i18nInstance
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const instance = createInstance({
|
|
26
|
+
compatibilityJSON: 'v3',
|
|
27
|
+
defaultNS: config.i18n?.defaultNs ?? DEFAULT_NAMESPACE,
|
|
28
|
+
fallbackLng: config.i18n?.defaultLng ?? DEFAULT_LNG,
|
|
29
|
+
debug: config.debug?.all ?? config.debug?.i18n ?? false,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
instance.use(initReactI18next).init()
|
|
33
|
+
|
|
34
|
+
return i18nInstance = instance
|
|
35
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
"../tsconfig.react.json",
|
|
5
|
+
],
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
8
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
9
|
+
"moduleResolution": "Bundler"
|
|
10
|
+
},
|
|
11
|
+
"exclude": [
|
|
12
|
+
"./dist/**/*",
|
|
13
|
+
"./build/**/*",
|
|
14
|
+
"./*.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/context.tsx","./src/index.ts","./src/types.ts","./src/utils/index.ts","./src/utils/instance.ts"],"version":"5.6.3"}
|