@umituz/react-native-localization 3.7.7 → 3.7.10

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,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "3.7.7",
3
+ "version": "3.7.10",
4
+ "type": "module",
4
5
  "description": "Generic localization system for React Native apps with i18n support",
5
6
  "main": "./src/index.ts",
6
7
  "types": "./src/index.ts",
@@ -5,30 +5,32 @@
5
5
  * Basic checks before publishing
6
6
  */
7
7
 
8
- const fs = require('fs');
9
- const path = require('path');
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
10
14
 
11
15
  const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
12
16
  const SRC_DIR = path.join(PACKAGE_ROOT, 'src');
13
17
 
14
18
  if (!fs.existsSync(SRC_DIR)) {
19
+ console.error('❌ src directory not found');
15
20
  process.exit(1);
16
21
  }
17
22
 
18
23
  const mainFiles = [
19
24
  'src/index.ts',
20
25
  'src/infrastructure/config/i18n.ts',
21
- 'src/infrastructure/storage/LocalizationStore.ts',
22
26
  ];
23
27
 
24
- let allFilesExist = true;
25
28
  for (const file of mainFiles) {
26
29
  const filePath = path.join(PACKAGE_ROOT, file);
27
30
  if (!fs.existsSync(filePath)) {
28
- allFilesExist = false;
31
+ console.error(`❌ Missing mandatory file: ${file}`);
32
+ process.exit(1);
29
33
  }
30
34
  }
31
35
 
32
- if (!allFilesExist) {
33
- process.exit(1);
34
- }
36
+ console.log('✅ Pre-publish checks passed!');
@@ -6,35 +6,27 @@
6
6
  * Usage: node setup-languages.js [locales-dir]
7
7
  */
8
8
 
9
- const fs = require('fs');
10
- const path = require('path');
9
+ import fs from 'fs';
10
+ import path from 'path';
11
11
 
12
- function main() {
13
- const targetDir = process.argv[2] || 'src/domains/localization/translations';
12
+ export function setupLanguages(targetDir) {
14
13
  const localesDir = path.resolve(process.cwd(), targetDir);
15
14
 
16
- console.log('🚀 Setting up language files...\n');
17
- console.log(`📂 Locales directory: ${localesDir}\n`);
18
-
19
15
  if (!fs.existsSync(localesDir)) {
20
16
  console.error(`❌ Locales directory not found: ${localesDir}`);
21
- process.exit(1);
17
+ return false;
22
18
  }
23
19
 
24
20
  const files = fs.readdirSync(localesDir)
25
21
  .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/))
26
22
  .sort();
27
23
 
28
- console.log(`📊 Found ${files.length} language files:\n`);
29
-
30
24
  const imports = [];
31
25
  const exports = [];
32
26
 
33
27
  files.forEach(file => {
34
28
  const code = file.replace('.ts', '');
35
29
  const varName = code.replace(/-([a-z0-9])/g, (g) => g[1].toUpperCase()).replace('-', '');
36
-
37
- console.log(` 📄 ${code}`);
38
30
  imports.push(`import ${varName} from "./${code}";`);
39
31
  exports.push(` "${code}": ${varName},`);
40
32
  });
@@ -56,11 +48,13 @@ export type TranslationKey = keyof typeof translations;
56
48
  export default translations;
57
49
  `;
58
50
 
59
- const indexPath = path.join(localesDir, 'index.ts');
60
- fs.writeFileSync(indexPath, content);
61
-
62
- console.log(`\n✅ Generated index.ts with ${files.length} languages`);
63
- console.log(` Output: ${indexPath}`);
51
+ fs.writeFileSync(path.join(localesDir, 'index.ts'), content);
52
+ console.log(`✅ Generated index.ts with ${files.length} languages`);
53
+ return true;
64
54
  }
65
55
 
66
- main();
56
+ if (import.meta.url === `file://${process.argv[1]}`) {
57
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
58
+ console.log('🚀 Setting up language files...\n');
59
+ setupLanguages(targetDir);
60
+ }
@@ -3,15 +3,17 @@
3
3
  /**
4
4
  * Sync Translations Script
5
5
  * Synchronizes translation keys from en-US.ts to all other language files
6
- * Usage: node sync-translations.js [locales-dir]
6
+ * Usage: node sync-translations.js [locales-dir] [src-dir-optional]
7
7
  */
8
8
 
9
- const fs = require('fs');
10
- const path = require('path');
11
- const { parseTypeScriptFile, generateTypeScriptContent } = require('./utils/file-parser');
12
- const { addMissingKeys, removeExtraKeys } = require('./utils/sync-helper');
13
- const { detectNewKeys } = require('./utils/key-detector');
14
- const { getLangDisplayName } = require('./utils/translation-config');
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { parseTypeScriptFile, generateTypeScriptContent } from './utils/file-parser.js';
12
+ import { addMissingKeys, removeExtraKeys } from './utils/sync-helper.js';
13
+ import { detectNewKeys } from './utils/key-detector.js';
14
+ import { getLangDisplayName } from './utils/translation-config.js';
15
+ import { extractUsedKeys } from './utils/key-extractor.js';
16
+ import { setDeep } from './utils/object-helper.js';
15
17
 
16
18
  function syncLanguageFile(enUSPath, targetPath, langCode) {
17
19
  const enUS = parseTypeScriptFile(enUSPath);
@@ -37,113 +39,61 @@ function syncLanguageFile(enUSPath, targetPath, langCode) {
37
39
  fs.writeFileSync(targetPath, content);
38
40
  }
39
41
 
40
- return {
41
- added: addStats.added,
42
- removed: removeStats.removed,
43
- newKeys,
44
- removedKeys: removeStats.removedKeys,
45
- changed,
46
- };
42
+ return { ...addStats, ...removeStats, newKeys, changed };
47
43
  }
48
44
 
49
- function main() {
50
- const targetDir = process.argv[2] || 'src/domains/localization/translations';
51
- const targetLangCode = process.argv[3];
52
- const localesDir = path.resolve(process.cwd(), targetDir);
45
+ function processExtraction(srcDir, enUSPath) {
46
+ if (!srcDir) return;
53
47
 
54
- console.log('🚀 Starting translation synchronization...\n');
55
- console.log(`📂 Locales directory: ${localesDir}`);
56
- if (targetLangCode) {
57
- console.log(`🎯 Target language: ${targetLangCode}`);
48
+ console.log(`🔍 Scanning source code: ${srcDir}...`);
49
+ const usedKeys = extractUsedKeys(srcDir);
50
+ console.log(` Found ${usedKeys.size} unique keys in code.`);
51
+
52
+ const enUS = parseTypeScriptFile(enUSPath);
53
+ let addedCount = 0;
54
+ for (const key of usedKeys) {
55
+ if (setDeep(enUS, key, key)) addedCount++;
58
56
  }
59
- console.log('');
60
57
 
61
- if (!fs.existsSync(localesDir)) {
62
- console.error(`❌ Locales directory not found: ${localesDir}`);
63
- process.exit(1);
58
+ if (addedCount > 0) {
59
+ console.log(` ✨ Added ${addedCount} new keys to en-US.ts`);
60
+ const content = generateTypeScriptContent(enUS, 'en-US');
61
+ fs.writeFileSync(enUSPath, content);
64
62
  }
63
+ }
65
64
 
65
+ export function syncTranslations(targetDir, srcDir) {
66
+ const localesDir = path.resolve(process.cwd(), targetDir);
66
67
  const enUSPath = path.join(localesDir, 'en-US.ts');
67
- if (!fs.existsSync(enUSPath)) {
68
- console.error(`❌ Base file not found: ${enUSPath}`);
69
- process.exit(1);
68
+
69
+ if (!fs.existsSync(localesDir) || !fs.existsSync(enUSPath)) {
70
+ console.error(`❌ Localization files not found in: ${localesDir}`);
71
+ return false;
70
72
  }
71
73
 
74
+ processExtraction(srcDir, enUSPath);
75
+
72
76
  const files = fs.readdirSync(localesDir)
73
- .filter(f => {
74
- const isLangFile = f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts';
75
- if (!isLangFile) return false;
76
- if (targetLangCode) {
77
- return f === `${targetLangCode}.ts`;
78
- }
79
- return true;
80
- })
77
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
81
78
  .sort();
82
79
 
83
- if (targetLangCode && files.length === 0) {
84
- console.warn(`⚠️ Target language file ${targetLangCode}.ts not found in ${targetDir}`);
85
- }
86
-
87
80
  console.log(`📊 Languages to sync: ${files.length}\n`);
88
-
89
- let totalAdded = 0;
90
- let totalRemoved = 0;
91
- let totalChanged = 0;
92
- const allNewKeys = [];
93
-
94
- for (const file of files) {
81
+ files.forEach(file => {
95
82
  const langCode = file.replace('.ts', '');
96
83
  const targetPath = path.join(localesDir, file);
97
-
98
- console.log(`🌍 Syncing ${langCode} (${getLangDisplayName(langCode)})...`);
99
-
100
84
  const result = syncLanguageFile(enUSPath, targetPath, langCode);
101
-
102
85
  if (result.changed) {
103
- if (result.newKeys.length > 0) {
104
- console.log(` 🆕 ${result.newKeys.length} new keys added:`);
105
- result.newKeys.slice(0, 5).forEach(({ path }) => {
106
- console.log(` • ${path}`);
107
- });
108
- if (result.newKeys.length > 5) {
109
- console.log(` ... and ${result.newKeys.length - 5} more`);
110
- }
111
- allNewKeys.push(...result.newKeys.map(k => k.path));
112
- }
113
- if (result.removedKeys.length > 0) {
114
- console.log(` 🗑️ ${result.removedKeys.length} obsolete keys removed`);
115
- }
116
- totalAdded += result.added;
117
- totalRemoved += result.removed;
118
- totalChanged++;
119
- } else {
120
- console.log(` ✅ Already synchronized`);
86
+ console.log(` 🌍 ${langCode}: ✏️ +${result.added} keys, -${result.removed} keys`);
121
87
  }
122
- }
88
+ });
123
89
 
124
- console.log(`\n📊 Summary:`);
125
- console.log(` Languages processed: ${files.length}`);
126
- console.log(` Files changed: ${totalChanged}`);
127
- console.log(` Keys added: ${totalAdded}`);
128
- console.log(` Keys removed: ${totalRemoved}`);
129
-
130
- if (totalChanged > 0) {
131
- console.log(`\n✅ Synchronization completed!`);
132
- console.log(` Next: Run 'npm run i18n:translate' to translate new keys`);
133
-
134
- if (allNewKeys.length > 0) {
135
- const uniqueKeys = [...new Set(allNewKeys)];
136
- console.log(`\n📝 New keys that need translation (${uniqueKeys.length}):`);
137
- uniqueKeys.slice(0, 10).forEach(key => {
138
- console.log(` • ${key}`);
139
- });
140
- if (uniqueKeys.length > 10) {
141
- console.log(` ... and ${uniqueKeys.length - 10} more`);
142
- }
143
- }
144
- } else {
145
- console.log(`\n✅ All languages were already synchronized!`);
146
- }
90
+ console.log(`\n Synchronization completed!`);
91
+ return true;
147
92
  }
148
93
 
149
- main();
94
+ if (import.meta.url === `file://${process.argv[1]}`) {
95
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
96
+ const srcDir = process.argv[3];
97
+ console.log('🚀 Starting translation synchronization...\n');
98
+ syncTranslations(targetDir, srcDir);
99
+ }
@@ -3,14 +3,16 @@
3
3
  /**
4
4
  * Translate Missing Script
5
5
  * Translates missing strings from en-US.ts to all other language files
6
- * Usage: node translate-missing.js [locales-dir]
6
+ * Usage: node translate-missing.js [locales-dir] [src-dir-optional] [lang-code-optional]
7
7
  */
8
8
 
9
- const fs = require('fs');
10
- const path = require('path');
11
- const { getTargetLanguage, isEnglishVariant, getLangDisplayName } = require('./utils/translation-config');
12
- const { parseTypeScriptFile, generateTypeScriptContent } = require('./utils/file-parser');
13
- const { translateObject } = require('./utils/translator');
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { getTargetLanguage, isEnglishVariant, getLangDisplayName } from './utils/translation-config.js';
12
+ import { parseTypeScriptFile, generateTypeScriptContent } from './utils/file-parser.js';
13
+ import { translateObject } from './utils/translator.js';
14
+ import { setupLanguages } from './setup-languages.js';
15
+ import { syncTranslations } from './sync-translations.js';
14
16
 
15
17
  async function translateLanguageFile(enUSPath, targetPath, langCode) {
16
18
  const targetLang = getTargetLanguage(langCode);
@@ -46,78 +48,60 @@ async function translateLanguageFile(enUSPath, targetPath, langCode) {
46
48
  }
47
49
 
48
50
  async function main() {
49
- const targetDir = process.argv[2] || 'src/domains/localization/translations';
50
- const targetLangCode = process.argv[3];
51
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
52
+ const srcDir = process.argv[3];
53
+ const targetLangCode = process.argv[4];
54
+
51
55
  const localesDir = path.resolve(process.cwd(), targetDir);
56
+ const enUSPath = path.join(localesDir, 'en-US.ts');
57
+ const indexPath = path.join(localesDir, 'index.ts');
52
58
 
53
- console.log('🚀 Starting automatic translation...\n');
54
- console.log(`📂 Locales directory: ${localesDir}`);
55
- if (targetLangCode) {
56
- console.log(`🎯 Target language: ${targetLangCode}`);
57
- }
58
- console.log('');
59
+ console.log('🚀 Starting integrated translation workflow...\n');
59
60
 
60
- if (!fs.existsSync(localesDir)) {
61
- console.error(`❌ Locales directory not found: ${localesDir}`);
62
- process.exit(1);
61
+ // 1. Ensure setup exists (index.ts)
62
+ if (!fs.existsSync(indexPath)) {
63
+ console.log('📦 Setup (index.ts) missing. Generating...');
64
+ setupLanguages(targetDir);
65
+ console.log('');
63
66
  }
64
67
 
65
- const enUSPath = path.join(localesDir, 'en-US.ts');
66
- if (!fs.existsSync(enUSPath)) {
67
- console.error(`❌ Base file not found: ${enUSPath}`);
68
+ // 2. Synchronize keys (includes code scanning if srcDir is provided)
69
+ console.log('🔄 Checking synchronization...');
70
+ syncTranslations(targetDir, srcDir);
71
+ console.log('');
72
+
73
+ if (!fs.existsSync(localesDir) || !fs.existsSync(enUSPath)) {
74
+ console.error(`❌ Localization files not found in: ${localesDir}`);
68
75
  process.exit(1);
69
76
  }
70
77
 
71
78
  const files = fs.readdirSync(localesDir)
72
79
  .filter(f => {
73
80
  const isLangFile = f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts';
74
- if (!isLangFile) return false;
75
- if (targetLangCode) {
76
- return f === `${targetLangCode}.ts`;
77
- }
78
- return true;
81
+ return isLangFile && (!targetLangCode || f === `${targetLangCode}.ts`);
79
82
  })
80
83
  .sort();
81
84
 
82
- if (targetLangCode && files.length === 0) {
83
- console.warn(`⚠️ Target language file ${targetLangCode}.ts not found in ${targetDir}`);
84
- }
85
-
86
- console.log(`📊 Languages to translate: ${files.length}`);
87
- console.log('⚡ Running with 200ms delay between API calls\n');
85
+ console.log(`📊 Languages to translate: ${files.length}\n`);
88
86
 
89
87
  let totalTranslated = 0;
90
- let totalNewKeys = 0;
91
-
92
88
  for (const file of files) {
93
89
  const langCode = file.replace('.ts', '');
94
90
  const targetPath = path.join(localesDir, file);
95
-
96
- console.log(`\n🌍 Translating ${langCode} (${getLangDisplayName(langCode)})...`);
97
-
91
+ console.log(`🌍 Translating ${langCode} (${getLangDisplayName(langCode)})...`);
98
92
  const stats = await translateLanguageFile(enUSPath, targetPath, langCode);
99
93
  totalTranslated += stats.count;
100
- totalNewKeys += stats.newKeys.length;
101
-
102
94
  if (stats.count > 0) {
103
95
  console.log(` ✅ Translated ${stats.count} strings`);
104
- if (stats.newKeys.length > 0) {
105
- console.log(` 🆕 ${stats.newKeys.length} new keys translated`);
106
- }
107
96
  } else {
108
97
  console.log(` ✓ Already complete`);
109
98
  }
110
99
  }
111
100
 
112
- console.log(`\n✅ Translation completed!`);
113
- console.log(` Total strings translated: ${totalTranslated}`);
114
- if (totalNewKeys > 0) {
115
- console.log(` New keys translated: ${totalNewKeys}`);
116
- }
117
- console.log(`\n📝 Next: Run 'npm run i18n:setup' to update index.ts`);
101
+ console.log(`\n✅ Workflow completed! (Total translated: ${totalTranslated})`);
118
102
  }
119
103
 
120
- main().catch((error) => {
104
+ main().catch(error => {
121
105
  console.error('❌ Translation failed:', error.message);
122
106
  process.exit(1);
123
107
  });
@@ -1,14 +1,12 @@
1
- #!/usr/bin/env node
1
+ import fs from 'fs';
2
+ import { getLangDisplayName } from './translation-config.js';
2
3
 
3
4
  /**
4
5
  * File Parser
5
6
  * Parse and generate TypeScript translation files
6
7
  */
7
8
 
8
- const fs = require('fs');
9
- const { getLangDisplayName } = require('./translation-config');
10
-
11
- function parseTypeScriptFile(filePath) {
9
+ export function parseTypeScriptFile(filePath) {
12
10
  const content = fs.readFileSync(filePath, 'utf8');
13
11
  const match = content.match(/export\s+default\s+(\{[\s\S]*\});?\s*$/);
14
12
 
@@ -25,7 +23,7 @@ function parseTypeScriptFile(filePath) {
25
23
  }
26
24
  }
27
25
 
28
- function stringifyValue(value, indent = 2) {
26
+ export function stringifyValue(value, indent = 2) {
29
27
  if (typeof value === 'string') {
30
28
  const escaped = value
31
29
  .replace(/\\/g, '\\\\')
@@ -50,6 +48,7 @@ function stringifyValue(value, indent = 2) {
50
48
  const spaces = ' '.repeat(indent);
51
49
  const innerSpaces = ' '.repeat(indent + 2);
52
50
  const entriesStr = entries
51
+ .sort((a, b) => a[0].localeCompare(b[0]))
53
52
  .map(([k, v]) => {
54
53
  const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
55
54
  return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
@@ -61,20 +60,16 @@ function stringifyValue(value, indent = 2) {
61
60
  return String(value);
62
61
  }
63
62
 
64
- function generateTypeScriptContent(obj, langCode) {
63
+ export function generateTypeScriptContent(obj, langCode) {
65
64
  const langName = getLangDisplayName(langCode);
65
+ const isBase = langCode === 'en-US';
66
66
  const objString = stringifyValue(obj, 0);
67
67
 
68
68
  return `/**
69
69
  * ${langName} Translations
70
- * Auto-translated from en-US.ts
70
+ * ${isBase ? 'Base translations file' : 'Auto-synced from en-US.ts'}
71
71
  */
72
72
 
73
73
  export default ${objString};
74
74
  `;
75
75
  }
76
-
77
- module.exports = {
78
- parseTypeScriptFile,
79
- generateTypeScriptContent,
80
- };
@@ -1,52 +1,45 @@
1
- #!/usr/bin/env node
2
-
3
1
  /**
4
2
  * Key Detector
5
3
  * Detects new, missing, and removed keys between source and target objects
6
4
  */
7
5
 
8
- function detectNewKeys(sourceObj, targetObj, path = '', newKeys = []) {
9
- for (const key in sourceObj) {
10
- const currentPath = path ? `${path}.${key}` : key;
11
- const sourceValue = sourceObj[key];
12
- const targetValue = targetObj[key];
6
+ export function detectNewKeys(sourceObj, targetObj, path = '', newKeys = []) {
7
+ for (const key in sourceObj) {
8
+ const currentPath = path ? `${path}.${key}` : key;
9
+ const sourceValue = sourceObj[key];
10
+ const targetValue = targetObj[key];
13
11
 
14
- if (!Object.prototype.hasOwnProperty.call(targetObj, key)) {
15
- newKeys.push({ path: currentPath, value: sourceValue });
16
- } else if (
17
- typeof sourceValue === 'object' &&
18
- sourceValue !== null &&
19
- !Array.isArray(sourceValue)
20
- ) {
21
- if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)) {
22
- detectNewKeys(sourceValue, targetValue, currentPath, newKeys);
23
- }
24
- }
12
+ if (!Object.prototype.hasOwnProperty.call(targetObj, key)) {
13
+ newKeys.push({ path: currentPath, value: sourceValue });
14
+ } else if (
15
+ typeof sourceValue === 'object' &&
16
+ sourceValue !== null &&
17
+ !Array.isArray(sourceValue)
18
+ ) {
19
+ if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)) {
20
+ detectNewKeys(sourceValue, targetValue, currentPath, newKeys);
21
+ }
25
22
  }
26
- return newKeys;
23
+ }
24
+ return newKeys;
27
25
  }
28
26
 
29
- function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
30
- for (const key in targetObj) {
31
- const currentPath = path ? `${path}.${key}` : key;
27
+ export function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
28
+ for (const key in targetObj) {
29
+ const currentPath = path ? `${path}.${key}` : key;
32
30
 
33
- if (!Object.prototype.hasOwnProperty.call(sourceObj, key)) {
34
- missingKeys.push(currentPath);
35
- } else if (
36
- typeof sourceObj[key] === 'object' &&
37
- sourceObj[key] !== null &&
38
- !Array.isArray(sourceObj[key]) &&
39
- typeof targetObj[key] === 'object' &&
40
- targetObj[key] !== null &&
41
- !Array.isArray(targetObj[key])
42
- ) {
43
- detectMissingKeys(sourceObj[key], targetObj[key], currentPath, missingKeys);
44
- }
31
+ if (!Object.prototype.hasOwnProperty.call(sourceObj, key)) {
32
+ missingKeys.push(currentPath);
33
+ } else if (
34
+ typeof sourceObj[key] === 'object' &&
35
+ sourceObj[key] !== null &&
36
+ !Array.isArray(sourceObj[key]) &&
37
+ typeof targetObj[key] === 'object' &&
38
+ targetObj[key] !== null &&
39
+ !Array.isArray(targetObj[key])
40
+ ) {
41
+ detectMissingKeys(sourceObj[key], targetObj[key], currentPath, missingKeys);
45
42
  }
46
- return missingKeys;
43
+ }
44
+ return missingKeys;
47
45
  }
48
-
49
- module.exports = {
50
- detectNewKeys,
51
- detectMissingKeys,
52
- };
@@ -0,0 +1,43 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Key Extractor
6
+ * Scans source code for i18n keys
7
+ */
8
+
9
+ export function extractUsedKeys(srcDir) {
10
+ const keys = new Set();
11
+ if (!srcDir) return keys;
12
+
13
+ const absoluteSrcDir = path.resolve(process.cwd(), srcDir);
14
+ if (!fs.existsSync(absoluteSrcDir)) {
15
+ return keys;
16
+ }
17
+
18
+ function walk(dir) {
19
+ const files = fs.readdirSync(dir);
20
+ for (const file of files) {
21
+ const fullPath = path.join(dir, file);
22
+ const stat = fs.statSync(fullPath);
23
+
24
+ if (stat.isDirectory()) {
25
+ const skipDirs = ['node_modules', '.expo', '.git', 'build', 'ios', 'android', 'assets'];
26
+ if (!skipDirs.includes(file)) {
27
+ walk(fullPath);
28
+ }
29
+ } else if (/\.(ts|tsx|js|jsx)$/.test(file)) {
30
+ const content = fs.readFileSync(fullPath, 'utf8');
31
+ // Regex for t('key') or t("key") or i18n.t('key')
32
+ const regex = /(?:^|\W)t\(['"]([^'"]+)['"]\)/g;
33
+ let match;
34
+ while ((match = regex.exec(content)) !== null) {
35
+ keys.add(match[1]);
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ walk(absoluteSrcDir);
42
+ return keys;
43
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Object Helper
3
+ * Utilities for deep object manipulation
4
+ */
5
+
6
+ /**
7
+ * Set a value in a nested object, creating intermediate objects if necessary
8
+ * Returns true if the key was newly added, false if it already existed
9
+ */
10
+ export function setDeep(obj, path, value) {
11
+ const keys = path.split('.');
12
+ let current = obj;
13
+
14
+ for (let i = 0; i < keys.length - 1; i++) {
15
+ const key = keys[i];
16
+ if (!current[key] || typeof current[key] !== 'object' || Array.isArray(current[key])) {
17
+ current[key] = {};
18
+ }
19
+ current = current[key];
20
+ }
21
+
22
+ const lastKey = keys[keys.length - 1];
23
+ if (current[lastKey] === undefined) {
24
+ current[lastKey] = value;
25
+ return true;
26
+ }
27
+
28
+ return false;
29
+ }
@@ -1,11 +1,9 @@
1
- #!/usr/bin/env node
2
-
3
1
  /**
4
2
  * Sync Helper
5
3
  * Helper functions for synchronizing translation keys
6
4
  */
7
5
 
8
- function addMissingKeys(sourceObj, targetObj, stats = { added: 0, newKeys: [] }) {
6
+ export function addMissingKeys(sourceObj, targetObj, stats = { added: 0, newKeys: [] }) {
9
7
  for (const key in sourceObj) {
10
8
  const sourceValue = sourceObj[key];
11
9
  const isNewKey = !Object.prototype.hasOwnProperty.call(targetObj, key);
@@ -28,7 +26,7 @@ function addMissingKeys(sourceObj, targetObj, stats = { added: 0, newKeys: [] })
28
26
  return stats;
29
27
  }
30
28
 
31
- function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys: [] }) {
29
+ export function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys: [] }) {
32
30
  for (const key in targetObj) {
33
31
  const isExtraKey = !Object.prototype.hasOwnProperty.call(sourceObj, key);
34
32
 
@@ -49,8 +47,3 @@ function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys
49
47
  }
50
48
  return stats;
51
49
  }
52
-
53
- module.exports = {
54
- addMissingKeys,
55
- removeExtraKeys,
56
- };
@@ -1,11 +1,9 @@
1
- #!/usr/bin/env node
2
-
3
1
  /**
4
2
  * Translation Configuration
5
3
  * Language mappings and constants for translation system
6
4
  */
7
5
 
8
- const LANGUAGE_MAP = {
6
+ export const LANGUAGE_MAP = {
9
7
  'ar-SA': 'ar',
10
8
  'bg-BG': 'bg',
11
9
  'cs-CZ': 'cs',
@@ -47,7 +45,7 @@ const LANGUAGE_MAP = {
47
45
  'zh-TW': 'zh-TW',
48
46
  };
49
47
 
50
- const SKIP_WORDS = new Set([
48
+ export const SKIP_WORDS = new Set([
51
49
  'Google',
52
50
  'Apple',
53
51
  'Facebook',
@@ -57,7 +55,7 @@ const SKIP_WORDS = new Set([
57
55
  'WhatsApp',
58
56
  ]);
59
57
 
60
- const LANGUAGE_NAMES = {
58
+ export const LANGUAGE_NAMES = {
61
59
  'ar-SA': 'Arabic (Saudi Arabia)',
62
60
  'bg-BG': 'Bulgarian',
63
61
  'cs-CZ': 'Czech',
@@ -100,29 +98,19 @@ const LANGUAGE_NAMES = {
100
98
  'zh-TW': 'Chinese (Traditional)',
101
99
  };
102
100
 
103
- function getLangDisplayName(code) {
101
+ export function getLangDisplayName(code) {
104
102
  return LANGUAGE_NAMES[code] || code;
105
103
  }
106
104
 
107
- function getTargetLanguage(langCode) {
105
+ export function getTargetLanguage(langCode) {
108
106
  return LANGUAGE_MAP[langCode];
109
107
  }
110
108
 
111
- function shouldSkipWord(word) {
109
+ export function shouldSkipWord(word) {
112
110
  return SKIP_WORDS.has(word);
113
111
  }
114
112
 
115
- function isEnglishVariant(langCode) {
113
+ export function isEnglishVariant(langCode) {
116
114
  const targetLang = LANGUAGE_MAP[langCode];
117
115
  return targetLang === 'en';
118
116
  }
119
-
120
- module.exports = {
121
- LANGUAGE_MAP,
122
- SKIP_WORDS,
123
- LANGUAGE_NAMES,
124
- getLangDisplayName,
125
- getTargetLanguage,
126
- shouldSkipWord,
127
- isEnglishVariant,
128
- };
@@ -1,18 +1,16 @@
1
- #!/usr/bin/env node
1
+ import https from 'https';
2
+ import { shouldSkipWord } from './translation-config.js';
2
3
 
3
4
  /**
4
5
  * Translator
5
6
  * Google Translate API integration and translation logic
6
7
  */
7
8
 
8
- const https = require('https');
9
- const { shouldSkipWord } = require('./translation-config');
10
-
11
- function delay(ms) {
9
+ export function delay(ms) {
12
10
  return new Promise(resolve => setTimeout(resolve, ms));
13
11
  }
14
12
 
15
- async function translateText(text, targetLang) {
13
+ export async function translateText(text, targetLang) {
16
14
  return new Promise((resolve) => {
17
15
  if (shouldSkipWord(text)) {
18
16
  resolve(text);
@@ -49,82 +47,51 @@ async function translateText(text, targetLang) {
49
47
 
50
48
  function needsTranslation(value, enValue) {
51
49
  if (typeof enValue !== 'string') return false;
52
-
53
50
  if (shouldSkipWord(enValue)) return false;
54
-
55
51
  if (!value || typeof value !== 'string') return true;
56
52
 
57
53
  if (value === enValue) {
58
54
  const isSingleWord = !enValue.includes(' ') && enValue.length < 20;
59
- if (isSingleWord) {
60
- return false;
61
- }
62
- return true;
55
+ return !isSingleWord;
63
56
  }
64
57
 
65
58
  return false;
66
59
  }
67
60
 
68
- async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0, newKeys: [] }) {
61
+ export async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0, newKeys: [] }) {
69
62
  for (const key in enObj) {
70
63
  const currentPath = path ? `${path}.${key}` : key;
71
64
  const enValue = enObj[key];
72
65
  const targetValue = targetObj[key];
73
66
 
74
67
  if (Array.isArray(enValue)) {
75
- if (!Array.isArray(targetValue)) {
76
- targetObj[key] = [];
77
- }
68
+ if (!Array.isArray(targetValue)) targetObj[key] = [];
78
69
  for (let i = 0; i < enValue.length; i++) {
79
- if (typeof enValue[i] === 'string') {
80
- if (needsTranslation(targetObj[key][i], enValue[i])) {
81
- const translated = await translateText(enValue[i], targetLang);
82
-
83
- if (translated !== enValue[i]) {
84
- const preview = enValue[i].length > 40 ? enValue[i].substring(0, 40) + '...' : enValue[i];
85
- const isNewKey = targetObj[key][i] === enValue[i];
86
- const prefix = isNewKey ? '🆕 NEW' : '🔄';
87
- console.log(` ${prefix} ${currentPath}[${i}]: "${preview}"`);
88
-
89
- targetObj[key][i] = translated;
90
- stats.count++;
91
- if (isNewKey) stats.newKeys.push(`${currentPath}[${i}]`);
92
- }
93
-
94
- await delay(200);
70
+ if (typeof enValue[i] === 'string' && needsTranslation(targetObj[key][i], enValue[i])) {
71
+ const translated = await translateText(enValue[i], targetLang);
72
+ if (translated !== enValue[i]) {
73
+ const isNewKey = targetObj[key][i] === enValue[i];
74
+ console.log(` ${isNewKey ? '🆕 NEW' : '🔄'} ${currentPath}[${i}]: "${enValue[i].substring(0, 40)}"`);
75
+ targetObj[key][i] = translated;
76
+ stats.count++;
77
+ if (isNewKey) stats.newKeys.push(`${currentPath}[${i}]`);
95
78
  }
79
+ await delay(200);
96
80
  }
97
81
  }
98
82
  } else if (typeof enValue === 'object' && enValue !== null) {
99
- if (!targetObj[key] || typeof targetObj[key] !== 'object') {
100
- targetObj[key] = {};
101
- }
83
+ if (!targetObj[key] || typeof targetObj[key] !== 'object') targetObj[key] = {};
102
84
  await translateObject(enValue, targetObj[key], targetLang, currentPath, stats);
103
- } else if (typeof enValue === 'string') {
104
- if (needsTranslation(targetValue, enValue)) {
105
- const translated = await translateText(enValue, targetLang);
106
- const isNewKey = targetValue === undefined;
107
-
108
- if (translated !== enValue || isNewKey) {
109
- const preview = enValue.length > 40 ? enValue.substring(0, 40) + '...' : enValue;
110
- const prefix = isNewKey ? '🆕 NEW' : '🔄';
111
- console.log(` ${prefix} ${currentPath}: "${preview}"`);
112
-
113
- targetObj[key] = translated;
114
- stats.count++;
115
- if (isNewKey) stats.newKeys.push(currentPath);
116
- }
117
-
118
- await delay(200);
85
+ } else if (typeof enValue === 'string' && needsTranslation(targetValue, enValue)) {
86
+ const translated = await translateText(enValue, targetLang);
87
+ const isNewKey = targetValue === undefined;
88
+ if (translated !== enValue || isNewKey) {
89
+ console.log(` ${isNewKey ? '🆕 NEW' : '🔄'} ${currentPath}: "${enValue.substring(0, 40)}"`);
90
+ targetObj[key] = translated;
91
+ stats.count++;
92
+ if (isNewKey) stats.newKeys.push(currentPath);
119
93
  }
94
+ await delay(200);
120
95
  }
121
96
  }
122
-
123
- return stats.count;
124
97
  }
125
-
126
- module.exports = {
127
- translateText,
128
- translateObject,
129
- delay,
130
- };