@umituz/react-native-localization 1.11.0 → 1.13.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/README.md +267 -2
- package/package.json +1 -1
- package/src/infrastructure/config/i18n.ts +24 -45
package/README.md
CHANGED
|
@@ -12,6 +12,36 @@ English-only localization system for React Native apps with i18n support. Built
|
|
|
12
12
|
- **Zero Configuration**: Works out of the box with sensible defaults
|
|
13
13
|
- **Production Ready**: Battle-tested in production apps
|
|
14
14
|
- **Lightweight**: Minimal dependencies with tree-shakeable exports
|
|
15
|
+
- **Domain-Driven Design**: Follows DDD principles with clear separation of concerns
|
|
16
|
+
|
|
17
|
+
## Architecture Overview
|
|
18
|
+
|
|
19
|
+
This package follows Domain-Driven Design principles:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
📁 src/
|
|
23
|
+
├── domain/ # Business entities and interfaces
|
|
24
|
+
├── infrastructure/ # Storage, config, and external services
|
|
25
|
+
└── presentation/ # Hooks and components for UI
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Package vs Project Translations
|
|
29
|
+
|
|
30
|
+
**Package Translations (This Package):**
|
|
31
|
+
- Core UI translations (buttons, alerts, navigation)
|
|
32
|
+
- Device-specific translations (camera, location, etc.)
|
|
33
|
+
- Generic business logic translations
|
|
34
|
+
|
|
35
|
+
**Project Translations (Your App):**
|
|
36
|
+
- App-specific translations
|
|
37
|
+
- Business domain translations
|
|
38
|
+
- Feature-specific content
|
|
39
|
+
|
|
40
|
+
**Why This Separation?**
|
|
41
|
+
- Package stays lightweight and reusable
|
|
42
|
+
- Projects maintain full control over their translations
|
|
43
|
+
- Easy updates without affecting project-specific content
|
|
44
|
+
- Clear separation of concerns
|
|
15
45
|
|
|
16
46
|
## Installation
|
|
17
47
|
|
|
@@ -33,10 +63,143 @@ npm install zustand i18next react-i18next expo-localization @umituz/react-native
|
|
|
33
63
|
|
|
34
64
|
## Quick Start
|
|
35
65
|
|
|
36
|
-
### 1
|
|
66
|
+
### Step 1: Install Package Translations (Core UI)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install @umituz/react-native-localization
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Step 2: Create Your Project's Localization Domain
|
|
73
|
+
|
|
74
|
+
Create a localization domain in your project following DDD principles:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
📁 src/domains/localization/
|
|
78
|
+
├── index.ts # Domain exports
|
|
79
|
+
├── infrastructure/
|
|
80
|
+
│ ├── locales/
|
|
81
|
+
│ │ ├── en-US/
|
|
82
|
+
│ │ │ ├── index.ts # Auto-loader (uses filesystem package)
|
|
83
|
+
│ │ │ ├── auth.json # Authentication translations
|
|
84
|
+
│ │ │ ├── home.json # Home screen translations
|
|
85
|
+
│ │ │ ├── settings.json # Settings translations
|
|
86
|
+
│ │ │ └── [feature].json # Feature-specific translations
|
|
87
|
+
│ │ └── [other-languages]/ # Future language support
|
|
88
|
+
│ └── config/
|
|
89
|
+
│ └── i18n.ts # Project i18n configuration
|
|
90
|
+
└── presentation/
|
|
91
|
+
└── hooks/
|
|
92
|
+
└── useLocalization.ts # Project-specific hooks (optional)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Example from Vivoim App:**
|
|
96
|
+
```
|
|
97
|
+
📁 src/domains/localization/
|
|
98
|
+
├── index.ts
|
|
99
|
+
└── infrastructure/
|
|
100
|
+
└── locales/
|
|
101
|
+
└── en-US/
|
|
102
|
+
├── auth.json # Login, signup, password
|
|
103
|
+
├── chat.json # Chat messages, typing
|
|
104
|
+
├── common.json # Shared UI elements
|
|
105
|
+
├── community.json # Social features
|
|
106
|
+
├── creations.json # Content creation
|
|
107
|
+
├── editor.json # Image/video editing
|
|
108
|
+
├── home.json # Dashboard, navigation
|
|
109
|
+
├── index.ts # Auto-loader
|
|
110
|
+
├── navigation.json # App navigation
|
|
111
|
+
├── paywall.json # Subscription features
|
|
112
|
+
├── premium.json # Premium content
|
|
113
|
+
├── profile.json # User profiles
|
|
114
|
+
├── projects.json # Project management
|
|
115
|
+
├── settings.json # App settings
|
|
116
|
+
├── support.json # Help & support
|
|
117
|
+
├── templates.json # Content templates
|
|
118
|
+
├── text2image.json # AI image generation
|
|
119
|
+
└── wallet.json # Payments, credits
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Why This Structure?**
|
|
123
|
+
- **Separation of Concerns**: Package handles core UI, project handles business logic
|
|
124
|
+
- **Scalability**: Easy to add new features without touching core package
|
|
125
|
+
- **Maintainability**: Clear ownership of translations
|
|
126
|
+
- **Reusability**: Same package works across hundreds of apps
|
|
127
|
+
|
|
128
|
+
### Step 3: Set Up Project Translations
|
|
129
|
+
|
|
130
|
+
**src/domains/localization/infrastructure/locales/en-US/index.ts:**
|
|
131
|
+
```typescript
|
|
132
|
+
import { loadJsonModules } from "@umituz/react-native-filesystem";
|
|
133
|
+
|
|
134
|
+
// Metro bundler require.context - auto-discover all .json files
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
136
|
+
const translationContext = (require as any).context("./", false, /\.json$/);
|
|
137
|
+
|
|
138
|
+
// Load all JSON modules using filesystem package utilities
|
|
139
|
+
const translations = loadJsonModules(translationContext);
|
|
140
|
+
|
|
141
|
+
export default translations;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**src/domains/localization/infrastructure/locales/en-US/your-feature.json:**
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"title": "Your Feature Title",
|
|
148
|
+
"description": "Feature description",
|
|
149
|
+
"button": {
|
|
150
|
+
"save": "Save Changes",
|
|
151
|
+
"cancel": "Cancel"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Step 4: Configure Project i18n
|
|
157
|
+
|
|
158
|
+
**src/domains/localization/infrastructure/config/i18n.ts:**
|
|
159
|
+
```typescript
|
|
160
|
+
import i18n from 'i18next';
|
|
161
|
+
import { initReactI18next } from 'react-i18next';
|
|
162
|
+
import { DEFAULT_LANGUAGE } from '@umituz/react-native-localization';
|
|
163
|
+
|
|
164
|
+
// Load project translations
|
|
165
|
+
const loadProjectTranslations = () => {
|
|
166
|
+
try {
|
|
167
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
168
|
+
return require('../locales/en-US');
|
|
169
|
+
} catch {
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const projectTranslations = loadProjectTranslations();
|
|
175
|
+
|
|
176
|
+
// Configure i18n with project translations
|
|
177
|
+
i18n
|
|
178
|
+
.use(initReactI18next)
|
|
179
|
+
.init({
|
|
180
|
+
resources: {
|
|
181
|
+
'en-US': {
|
|
182
|
+
translation: projectTranslations.default || projectTranslations
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
lng: DEFAULT_LANGUAGE,
|
|
186
|
+
fallbackLng: DEFAULT_LANGUAGE,
|
|
187
|
+
interpolation: {
|
|
188
|
+
escapeValue: false,
|
|
189
|
+
},
|
|
190
|
+
react: {
|
|
191
|
+
useSuspense: false,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export default i18n;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Step 5: Wrap Your App
|
|
37
199
|
|
|
38
200
|
```tsx
|
|
39
201
|
import { LocalizationProvider } from '@umituz/react-native-localization';
|
|
202
|
+
import './domains/localization/infrastructure/config/i18n'; // Initialize project i18n
|
|
40
203
|
|
|
41
204
|
export default function App() {
|
|
42
205
|
return (
|
|
@@ -47,7 +210,109 @@ export default function App() {
|
|
|
47
210
|
}
|
|
48
211
|
```
|
|
49
212
|
|
|
50
|
-
###
|
|
213
|
+
### Step 6: Use in Components
|
|
214
|
+
|
|
215
|
+
**Option A: Use Package Localization (Recommended for UI components)**
|
|
216
|
+
```tsx
|
|
217
|
+
import { useLocalization } from '@umituz/react-native-localization';
|
|
218
|
+
|
|
219
|
+
function MyComponent() {
|
|
220
|
+
const { t } = useLocalization();
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<View>
|
|
224
|
+
<Text>{t('general.save')}</Text> {/* From package */}
|
|
225
|
+
<Text>{t('yourFeature.title')}</Text> {/* From your project */}
|
|
226
|
+
</View>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Option B: Use Project Localization (For business logic)**
|
|
232
|
+
```tsx
|
|
233
|
+
import { useTranslation } from 'react-i18next';
|
|
234
|
+
|
|
235
|
+
function MyComponent() {
|
|
236
|
+
const { t } = useTranslation();
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<View>
|
|
240
|
+
<Text>{t('yourFeature.title')}</Text> {/* From your project */}
|
|
241
|
+
</View>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### **Add Project Translations to Package i18n**
|
|
247
|
+
|
|
248
|
+
For better integration, add your project translations to the package i18n instance:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { addTranslationResources } from '@umituz/react-native-localization';
|
|
252
|
+
|
|
253
|
+
// In your project's i18n config
|
|
254
|
+
const projectTranslations = require('./locales/en-US');
|
|
255
|
+
|
|
256
|
+
addTranslationResources({
|
|
257
|
+
'en-US': {
|
|
258
|
+
translation: projectTranslations.default || projectTranslations,
|
|
259
|
+
},
|
|
260
|
+
// Add other languages as needed
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
This ensures all translations work through the same i18n instance, preventing conflicts.
|
|
265
|
+
|
|
266
|
+
### Step 7: Translation Management
|
|
267
|
+
|
|
268
|
+
#### Adding New Translations
|
|
269
|
+
|
|
270
|
+
1. **Create JSON file** in `src/domains/localization/infrastructure/locales/en-US/`
|
|
271
|
+
2. **Add translations** following flat or nested structure
|
|
272
|
+
3. **Auto-loading** happens automatically via `index.ts`
|
|
273
|
+
|
|
274
|
+
#### Translation File Structure
|
|
275
|
+
|
|
276
|
+
**Flat Structure (Recommended for simple features):**
|
|
277
|
+
```json
|
|
278
|
+
// settings.json
|
|
279
|
+
{
|
|
280
|
+
"title": "Settings",
|
|
281
|
+
"language": "Language",
|
|
282
|
+
"theme": "Theme",
|
|
283
|
+
"notifications": "Notifications"
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Nested Structure (For complex features):**
|
|
288
|
+
```json
|
|
289
|
+
// auth.json
|
|
290
|
+
{
|
|
291
|
+
"login": {
|
|
292
|
+
"title": "Welcome Back",
|
|
293
|
+
"email": "Email Address",
|
|
294
|
+
"password": "Password",
|
|
295
|
+
"forgotPassword": "Forgot Password?",
|
|
296
|
+
"signIn": "Sign In"
|
|
297
|
+
},
|
|
298
|
+
"register": {
|
|
299
|
+
"title": "Create Account",
|
|
300
|
+
"confirmPassword": "Confirm Password",
|
|
301
|
+
"signUp": "Sign Up"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### Usage in Components
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
// Flat structure
|
|
310
|
+
t('settings.title') // "Settings"
|
|
311
|
+
|
|
312
|
+
// Nested structure
|
|
313
|
+
t('auth.login.title') // "Welcome Back"
|
|
314
|
+
t('auth.register.signUp') // "Sign Up"
|
|
315
|
+
```
|
|
51
316
|
|
|
52
317
|
```tsx
|
|
53
318
|
import { useLocalization } from '@umituz/react-native-localization';
|
package/package.json
CHANGED
|
@@ -31,33 +31,14 @@ const packageTranslations = loadPackageTranslations();
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Load project translations for all supported languages
|
|
34
|
-
*
|
|
34
|
+
* This function is a placeholder for future extensibility
|
|
35
|
+
* Currently returns empty translations as projects should manage their own translations
|
|
35
36
|
*/
|
|
36
37
|
const loadProjectTranslations = (): Record<string, any> => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
// Direct require for nutrition translations
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
|
-
const nutritionTranslations = require('../../../../../../src/locales/en-US');
|
|
44
|
-
if (nutritionTranslations?.default || nutritionTranslations) {
|
|
45
|
-
translations['en-US'] = nutritionTranslations.default || nutritionTranslations;
|
|
46
|
-
}
|
|
47
|
-
} catch (error) {
|
|
48
|
-
// If nutrition translations not found, try to load them directly
|
|
49
|
-
try {
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
51
|
-
const nutritionJson = require('../../../../../../src/locales/en-US/nutrition.json');
|
|
52
|
-
if (nutritionJson) {
|
|
53
|
-
translations['en-US'] = { nutrition: nutritionJson };
|
|
54
|
-
}
|
|
55
|
-
} catch (fallbackError) {
|
|
56
|
-
// Silent fallback - no nutrition translations available
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return translations;
|
|
38
|
+
// Projects should create their own localization domains
|
|
39
|
+
// and manage translations within their app structure
|
|
40
|
+
// This package provides only the core i18n infrastructure
|
|
41
|
+
return {};
|
|
61
42
|
};
|
|
62
43
|
|
|
63
44
|
const projectTranslations = loadProjectTranslations();
|
|
@@ -103,33 +84,17 @@ const mergeTranslations = (packageTranslations: any, projectTranslations: any):
|
|
|
103
84
|
const buildResources = (): Record<string, { translation: any }> => {
|
|
104
85
|
const resources: Record<string, { translation: any }> = {};
|
|
105
86
|
|
|
106
|
-
// Try to load nutrition translations directly for en-US
|
|
107
|
-
let nutritionTranslations: any = {};
|
|
108
|
-
try {
|
|
109
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
110
|
-
const nutritionJson = require('../../../../../../src/locales/en-US/nutrition.json');
|
|
111
|
-
if (nutritionJson) {
|
|
112
|
-
nutritionTranslations = { nutrition: nutritionJson };
|
|
113
|
-
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
// Silent fallback - no nutrition translations
|
|
116
|
-
}
|
|
117
|
-
|
|
118
87
|
// Build resources for each supported language
|
|
119
88
|
for (const lang of SUPPORTED_LANGUAGES) {
|
|
120
89
|
const langCode = lang.code;
|
|
121
90
|
const packageTranslation = langCode === 'en-US' ? (packageTranslations['en-US'] || {}) : {};
|
|
122
91
|
const projectTranslation = projectTranslations[langCode] || {};
|
|
123
92
|
|
|
124
|
-
// For en-US, merge package
|
|
93
|
+
// For en-US, merge package and project translations
|
|
125
94
|
// For other languages, use project translations only (fallback to en-US handled by i18n)
|
|
126
95
|
if (langCode === 'en-US') {
|
|
127
|
-
const mergedTranslation = mergeTranslations(
|
|
128
|
-
mergeTranslations(packageTranslation, projectTranslation),
|
|
129
|
-
nutritionTranslations
|
|
130
|
-
);
|
|
131
96
|
resources[langCode] = {
|
|
132
|
-
translation:
|
|
97
|
+
translation: mergeTranslations(packageTranslation, projectTranslation),
|
|
133
98
|
};
|
|
134
99
|
} else if (projectTranslation && Object.keys(projectTranslation).length > 0) {
|
|
135
100
|
resources[langCode] = {
|
|
@@ -140,9 +105,8 @@ const buildResources = (): Record<string, { translation: any }> => {
|
|
|
140
105
|
|
|
141
106
|
// Ensure en-US is always present
|
|
142
107
|
if (!resources['en-US']) {
|
|
143
|
-
const baseTranslation = packageTranslations['en-US'] || {};
|
|
144
108
|
resources['en-US'] = {
|
|
145
|
-
translation:
|
|
109
|
+
translation: packageTranslations['en-US'] || {},
|
|
146
110
|
};
|
|
147
111
|
}
|
|
148
112
|
|
|
@@ -233,4 +197,19 @@ const initializeI18n = () => {
|
|
|
233
197
|
// CRITICAL: i18n.isInitialized check prevents multiple initializations
|
|
234
198
|
initializeI18n();
|
|
235
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Add additional translation resources to the existing i18n instance
|
|
202
|
+
* This allows projects to add their own translations to the package translations
|
|
203
|
+
*/
|
|
204
|
+
export const addTranslationResources = (resources: Record<string, { translation: any }>) => {
|
|
205
|
+
for (const [langCode, resource] of Object.entries(resources)) {
|
|
206
|
+
if (resource.translation) {
|
|
207
|
+
// Merge with existing translations if any
|
|
208
|
+
const existingTranslations = i18n.getResourceBundle(langCode, 'translation') || {};
|
|
209
|
+
const mergedTranslations = { ...existingTranslations, ...resource.translation };
|
|
210
|
+
i18n.addResourceBundle(langCode, 'translation', mergedTranslations, true, true);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
236
215
|
export default i18n;
|