@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 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"}
@@ -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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"}
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './context.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export * from './types.js';
2
+ export * from './context.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from './instance.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export * from './instance.js';
2
+ //# sourceMappingURL=index.js.map
@@ -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
+ }
@@ -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
@@ -0,0 +1,3 @@
1
+
2
+ export * from './types.js'
3
+ export * from './context.js'
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,2 @@
1
+
2
+ export * from './instance.js'
@@ -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"}