@umituz/react-native-localization 2.0.1 → 2.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "English-only localization system for React Native apps with i18n support",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -2,79 +2,34 @@
2
2
  /* eslint-disable no-console */
3
3
 
4
4
  /**
5
- * Pre-Publish Script
6
- *
7
- * This script runs automatically before npm publish to ensure:
8
- * 1. en-US translation files are ready
9
- * 2. en-US index.ts loader is generated
10
- * 3. All required files are in place
11
- *
12
- * Runs automatically via "prepublishOnly" npm script
5
+ * Pre-Publish Script - Minimal Version
6
+ *
7
+ * Basic checks before publishing
13
8
  */
14
9
 
15
10
  const fs = require('fs');
16
11
  const path = require('path');
17
- const { execSync } = require('child_process');
18
12
 
19
13
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
20
14
  const EN_US_DIR = path.join(PACKAGE_ROOT, 'src/infrastructure/locales/en-US');
21
- const EN_US_INDEX = path.join(EN_US_DIR, 'index.ts');
22
15
 
23
16
  console.log('šŸ” Pre-publish checks...\n');
24
17
 
25
18
  // Check if en-US directory exists
26
19
  if (!fs.existsSync(EN_US_DIR)) {
27
20
  console.error('āŒ en-US directory not found!');
28
- console.error(` Expected: ${EN_US_DIR}`);
29
21
  process.exit(1);
30
22
  }
31
23
 
32
24
  // Check if en-US has JSON files
33
25
  const jsonFiles = fs.readdirSync(EN_US_DIR)
34
- .filter(file => file.endsWith('.json'))
35
- .sort();
26
+ .filter(file => file.endsWith('.json'));
36
27
 
37
28
  if (jsonFiles.length === 0) {
38
- console.error('āŒ No JSON translation files found in en-US directory!');
29
+ console.error('āŒ No JSON translation files found!');
39
30
  process.exit(1);
40
31
  }
41
32
 
42
- console.log(`āœ… Found ${jsonFiles.length} translation files in en-US:`);
43
- jsonFiles.forEach(file => console.log(` - ${file}`));
44
-
45
- // Generate index.ts if it doesn't exist or is outdated
46
- const needsIndexUpdate = !fs.existsSync(EN_US_INDEX) ||
47
- jsonFiles.some(file => {
48
- const filePath = path.join(EN_US_DIR, file);
49
- const indexStat = fs.statSync(EN_US_INDEX);
50
- const fileStat = fs.statSync(filePath);
51
- return fileStat.mtime > indexStat.mtime;
52
- });
53
-
54
- if (needsIndexUpdate) {
55
- console.log('\nšŸ“ Generating en-US index.ts loader...');
56
- try {
57
- // Run createLocaleLoaders script for en-US
58
- const createLoaderScript = path.join(PACKAGE_ROOT, 'scripts/createLocaleLoaders.js');
59
- execSync(`node "${createLoaderScript}" en-US`, {
60
- stdio: 'inherit',
61
- cwd: PACKAGE_ROOT,
62
- });
63
- console.log('āœ… en-US index.ts generated successfully');
64
- } catch (error) {
65
- console.error('āŒ Failed to generate en-US index.ts:', error.message);
66
- process.exit(1);
67
- }
68
- } else {
69
- console.log('\nāœ… en-US index.ts is up to date');
70
- }
71
-
72
- // Verify index.ts exists
73
- if (!fs.existsSync(EN_US_INDEX)) {
74
- console.error('āŒ en-US/index.ts not found after generation!');
75
- process.exit(1);
76
- }
77
-
78
- console.log('\nāœ… Pre-publish checks passed!');
79
- console.log(' Package is ready to publish.\n');
33
+ console.log(`āœ… Found ${jsonFiles.length} translation files`);
34
+ console.log('āœ… Pre-publish checks passed!\n');
80
35
 
@@ -2,7 +2,7 @@
2
2
  * i18n Initializer
3
3
  *
4
4
  * Handles i18n configuration and initialization
5
- * - Resource building
5
+ * - Auto-discovers project translations
6
6
  * - i18n setup
7
7
  * - React i18next integration
8
8
  */
@@ -15,39 +15,44 @@ import { TranslationLoader } from './TranslationLoader';
15
15
  export class I18nInitializer {
16
16
  private static reactI18nextInitialized = false;
17
17
 
18
+ /**
19
+ * Auto-discover project translations from common paths
20
+ */
21
+ private static loadProjectTranslations(): Record<string, any> {
22
+ const possiblePaths = [
23
+ './src/locales/en-US', // App structure
24
+ './locales/en-US', // Alternative app structure
25
+ '../src/locales/en-US', // Relative from package
26
+ ];
27
+
28
+ for (const path of possiblePaths) {
29
+ try {
30
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
31
+ const translations = require(path);
32
+ return translations.default || translations;
33
+ } catch {
34
+ // Try next path
35
+ }
36
+ }
37
+
38
+ return {};
39
+ }
40
+
18
41
  /**
19
42
  * Build resources object for all supported languages
20
43
  */
21
44
  private static buildResources(): Record<string, { translation: any }> {
22
45
  const resources: Record<string, { translation: any }> = {};
23
46
  const packageTranslations = TranslationLoader.loadPackageTranslations();
24
- const projectTranslations = TranslationLoader.loadProjectTranslations();
25
-
26
- // Build resources for each supported language
27
- for (const lang of SUPPORTED_LANGUAGES) {
28
- const langCode = lang.code;
29
- const packageTranslation = langCode === 'en-US' ? (packageTranslations['en-US'] || {}) : {};
30
- const projectTranslation = projectTranslations[langCode] || {};
31
-
32
- // For en-US, merge package and project translations
33
- // For other languages, use project translations only (fallback to en-US handled by i18n)
34
- if (langCode === 'en-US') {
35
- resources[langCode] = {
36
- translation: TranslationLoader.mergeTranslations(packageTranslation, projectTranslation),
37
- };
38
- } else if (projectTranslation && Object.keys(projectTranslation).length > 0) {
39
- resources[langCode] = {
40
- translation: projectTranslation,
41
- };
42
- }
43
- }
44
-
45
- // Ensure en-US is always present
46
- if (!resources['en-US']) {
47
- resources['en-US'] = {
48
- translation: packageTranslations['en-US'] || {},
49
- };
50
- }
47
+ const projectTranslations = this.loadProjectTranslations();
48
+
49
+ // For en-US, merge package and project translations
50
+ resources['en-US'] = {
51
+ translation: TranslationLoader.mergeTranslations(
52
+ packageTranslations['en-US'] || {},
53
+ projectTranslations
54
+ ),
55
+ };
51
56
 
52
57
  return resources;
53
58
  }
@@ -1,10 +1,7 @@
1
1
  /**
2
2
  * Translation Loader
3
3
  *
4
- * Handles loading of translations from different sources
5
- * - Package translations
6
- * - Project translations
7
- * - Resource merging
4
+ * Handles loading of translations from package only
8
5
  */
9
6
 
10
7
  export class TranslationLoader {
@@ -21,14 +18,6 @@ export class TranslationLoader {
21
18
  }
22
19
  }
23
20
 
24
- /**
25
- * Load project translations for all supported languages
26
- * Currently returns empty as projects manage their own translations
27
- */
28
- static loadProjectTranslations(): Record<string, any> {
29
- return {};
30
- }
31
-
32
21
  /**
33
22
  * Merge package defaults with project-specific translations
34
23
  */
@@ -1,17 +1,15 @@
1
1
  /**
2
- * i18n Configuration Entry Point
2
+ * i18n Configuration
3
3
  *
4
- * Delegates to I18nInitializer for setup
5
- * Exports i18n instance and utility functions
4
+ * Auto-initializes i18n with project translations
6
5
  */
7
6
 
8
- import i18n from 'i18next';
9
7
  import { I18nInitializer } from './I18nInitializer';
8
+ import i18n from 'i18next';
10
9
 
11
- // Initialize i18n immediately
10
+ // Initialize i18n automatically
12
11
  I18nInitializer.initialize();
13
12
 
14
- // Export utility functions
13
+ // Export for advanced usage
15
14
  export const addTranslationResources = I18nInitializer.addTranslationResources;
16
-
17
15
  export default i18n;
@@ -1,230 +0,0 @@
1
- #!/usr/bin/env node
2
- /* eslint-disable no-console */
3
-
4
- /**
5
- * Translation Key Analyzer
6
- *
7
- * Analyzes React Native apps to find all translation keys used
8
- * in screens and compares them against available translations.
9
- *
10
- * Usage:
11
- * npm run i18n:analyze
12
- * node node_modules/@umituz/react-native-localization/scripts/analyze-keys.js
13
- *
14
- * Features:
15
- * - Extracts all t('...') calls from .tsx/.ts files
16
- * - Validates against en-US translation files
17
- * - Reports missing keys
18
- * - Reports unused keys (in JSON but not in code)
19
- * - Generates comprehensive report
20
- */
21
-
22
- const fs = require('fs');
23
- const path = require('path');
24
- const { getLocalesDir } = require('./utils/findLocalesDir');
25
-
26
- /**
27
- * Extract translation keys from a TypeScript/TSX file
28
- * Looks for patterns: t('key'), t("key"), t(`key`)
29
- */
30
- function extractKeysFromFile(filePath) {
31
- const content = fs.readFileSync(filePath, 'utf8');
32
- const keys = new Set();
33
-
34
- // Regex patterns for t('key'), t("key"), t(`key`)
35
- const patterns = [
36
- /t\(['"]([^'"]+)['"]\)/g, // t('key') or t("key")
37
- /t\(`([^`]+)`\)/g, // t(`key`)
38
- ];
39
-
40
- for (const pattern of patterns) {
41
- let match;
42
- while ((match = pattern.exec(content)) !== null) {
43
- const key = match[1].trim();
44
-
45
- // Skip invalid keys
46
- if (
47
- !key || // Empty
48
- key.length < 3 || // Too short (single chars like '/', '-')
49
- key.includes('${') || // Variables ${var}
50
- key.includes('{{') || // Handlebars {{var}}
51
- /^[A-Z][a-z]+$/.test(key) || // Single words like "Main", "Settings" (likely screen names)
52
- /^[a-z]+$/.test(key) || // Single words like "button", "icon" (likely prop values)
53
- /^[a-z]+-[a-z]+$/.test(key) // Kebab-case like "onboarding-complete" (likely testIDs)
54
- ) {
55
- continue;
56
- }
57
-
58
- keys.add(key);
59
- }
60
- }
61
-
62
- return keys;
63
- }
64
-
65
- /**
66
- * Recursively find all TypeScript/TSX files in a directory
67
- */
68
- function findSourceFiles(dir, files = []) {
69
- if (!fs.existsSync(dir)) {
70
- return files;
71
- }
72
-
73
- const entries = fs.readdirSync(dir, { withFileTypes: true });
74
-
75
- for (const entry of entries) {
76
- const fullPath = path.join(dir, entry.name);
77
-
78
- // Skip node_modules, .git, build directories
79
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'build' || entry.name === 'lib') {
80
- continue;
81
- }
82
-
83
- if (entry.isDirectory()) {
84
- findSourceFiles(fullPath, files);
85
- } else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
86
- files.push(fullPath);
87
- }
88
- }
89
-
90
- return files;
91
- }
92
-
93
- /**
94
- * Load all translation keys from JSON files
95
- */
96
- function loadTranslationKeys(localesDir, locale = 'en-US') {
97
- const localeDir = path.join(localesDir, locale);
98
- const keys = new Set();
99
-
100
- if (!fs.existsSync(localeDir)) {
101
- return keys;
102
- }
103
-
104
- const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.json'));
105
-
106
- for (const file of files) {
107
- const filePath = path.join(localeDir, file);
108
- const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
109
- const namespace = file.replace('.json', '');
110
-
111
- extractKeysFromObject(content, namespace, keys);
112
- }
113
-
114
- return keys;
115
- }
116
-
117
- /**
118
- * Recursively extract keys from translation object
119
- */
120
- function extractKeysFromObject(obj, prefix, keys) {
121
- for (const key in obj) {
122
- const fullKey = prefix ? `${prefix}.${key}` : key;
123
-
124
- if (typeof obj[key] === 'object' && obj[key] !== null) {
125
- extractKeysFromObject(obj[key], fullKey, keys);
126
- } else if (typeof obj[key] === 'string') {
127
- keys.add(fullKey);
128
- }
129
- }
130
- }
131
-
132
- /**
133
- * Analyze translation keys for current project
134
- */
135
- function analyzeProject() {
136
- console.log(`\nšŸ” Analyzing translation keys for current project...\n`);
137
-
138
- // Find project's locales directory
139
- const localesDir = getLocalesDir();
140
- const projectRoot = process.cwd();
141
-
142
- console.log(`šŸ“ Project root: ${projectRoot}`);
143
- console.log(`šŸ“ Locales directory: ${localesDir}\n`);
144
-
145
- // Find all source files in src directory
146
- const srcDir = path.join(projectRoot, 'src');
147
- const sourceFiles = findSourceFiles(srcDir);
148
- console.log(`šŸ“„ Found ${sourceFiles.length} source files\n`);
149
-
150
- // Extract keys from source code
151
- const usedKeys = new Set();
152
- for (const file of sourceFiles) {
153
- const keys = extractKeysFromFile(file);
154
- keys.forEach(key => usedKeys.add(key));
155
- }
156
-
157
- console.log(`šŸ”‘ Found ${usedKeys.size} translation keys in source code\n`);
158
-
159
- // Load translation keys
160
- const translationKeys = loadTranslationKeys(localesDir);
161
- console.log(`šŸ“š Found ${translationKeys.size} translation keys in en-US JSON files\n`);
162
-
163
- // Find missing keys (used in code but not in translations)
164
- const missingKeys = [];
165
- usedKeys.forEach(key => {
166
- if (!translationKeys.has(key)) {
167
- missingKeys.push(key);
168
- }
169
- });
170
-
171
- // Find unused keys (in translations but not used in code)
172
- const unusedKeys = [];
173
- translationKeys.forEach(key => {
174
- if (!usedKeys.has(key)) {
175
- unusedKeys.push(key);
176
- }
177
- });
178
-
179
- // Generate report
180
- console.log('═══════════════════════════════════════════════════════════\n');
181
- console.log('šŸ“Š TRANSLATION KEY ANALYSIS REPORT\n');
182
- console.log('═══════════════════════════════════════════════════════════\n');
183
-
184
- if (missingKeys.length === 0) {
185
- console.log('āœ… All translation keys found in JSON files!\n');
186
- } else {
187
- console.log(`āŒ Missing Keys: ${missingKeys.length}\n`);
188
- console.log('Keys used in code but NOT found in translation files:\n');
189
- missingKeys.sort().forEach(key => {
190
- console.log(` - ${key}`);
191
- });
192
- console.log('');
193
- }
194
-
195
- if (unusedKeys.length > 0) {
196
- console.log(`āš ļø Unused Keys: ${unusedKeys.length}\n`);
197
- console.log('Keys in translation files but NOT used in code:\n');
198
- unusedKeys.sort().forEach(key => {
199
- console.log(` - ${key}`);
200
- });
201
- console.log('');
202
- }
203
-
204
- console.log('═══════════════════════════════════════════════════════════\n');
205
- console.log('šŸ“ˆ Summary:\n');
206
- console.log(` Total keys in code: ${usedKeys.size}`);
207
- console.log(` Total keys in translations: ${translationKeys.size}`);
208
- console.log(` Missing keys: ${missingKeys.length}`);
209
- console.log(` Unused keys: ${unusedKeys.length}`);
210
- if (usedKeys.size > 0) {
211
- console.log(` Coverage: ${Math.round((1 - missingKeys.length / usedKeys.size) * 100)}%`);
212
- }
213
- console.log('');
214
- console.log('═══════════════════════════════════════════════════════════\n');
215
-
216
- if (missingKeys.length > 0) {
217
- console.log('šŸ’” Next Steps:\n');
218
- console.log(' 1. Add missing keys to en-US JSON files');
219
- console.log(' 2. Run: npm run i18n:translate to auto-translate');
220
- console.log(' 3. Re-run this analyzer to verify\n');
221
- process.exit(1);
222
- } else {
223
- console.log('āœ… Translation check passed!\n');
224
- process.exit(0);
225
- }
226
- }
227
-
228
- // Main
229
- analyzeProject();
230
-