@umituz/react-native-localization 3.7.7 → 3.7.9

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.9",
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,16 +6,14 @@
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
12
  function main() {
13
- const targetDir = process.argv[2] || 'src/domains/localization/translations';
13
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
14
14
  const localesDir = path.resolve(process.cwd(), targetDir);
15
15
 
16
16
  console.log('🚀 Setting up language files...\n');
17
- console.log(`📂 Locales directory: ${localesDir}\n`);
18
-
19
17
  if (!fs.existsSync(localesDir)) {
20
18
  console.error(`❌ Locales directory not found: ${localesDir}`);
21
19
  process.exit(1);
@@ -25,16 +23,12 @@ function main() {
25
23
  .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/))
26
24
  .sort();
27
25
 
28
- console.log(`📊 Found ${files.length} language files:\n`);
29
-
30
26
  const imports = [];
31
27
  const exports = [];
32
28
 
33
29
  files.forEach(file => {
34
30
  const code = file.replace('.ts', '');
35
31
  const varName = code.replace(/-([a-z0-9])/g, (g) => g[1].toUpperCase()).replace('-', '');
36
-
37
- console.log(` 📄 ${code}`);
38
32
  imports.push(`import ${varName} from "./${code}";`);
39
33
  exports.push(` "${code}": ${varName},`);
40
34
  });
@@ -56,11 +50,8 @@ export type TranslationKey = keyof typeof translations;
56
50
  export default translations;
57
51
  `;
58
52
 
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}`);
53
+ fs.writeFileSync(path.join(localesDir, 'index.ts'), content);
54
+ console.log(`✅ Generated index.ts with ${files.length} languages`);
64
55
  }
65
56
 
66
57
  main();
@@ -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
+ function main() {
66
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
67
+ const srcDir = process.argv[3];
68
+ const localesDir = path.resolve(process.cwd(), targetDir);
66
69
  const enUSPath = path.join(localesDir, 'en-US.ts');
67
- if (!fs.existsSync(enUSPath)) {
68
- console.error(`❌ Base file not found: ${enUSPath}`);
70
+
71
+ console.log('🚀 Starting translation synchronization...\n');
72
+ if (!fs.existsSync(localesDir) || !fs.existsSync(enUSPath)) {
73
+ console.error(`❌ Localization files not found in: ${localesDir}`);
69
74
  process.exit(1);
70
75
  }
71
76
 
77
+ processExtraction(srcDir, enUSPath);
78
+
72
79
  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
- })
80
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
81
81
  .sort();
82
82
 
83
- if (targetLangCode && files.length === 0) {
84
- console.warn(`⚠️ Target language file ${targetLangCode}.ts not found in ${targetDir}`);
85
- }
86
-
87
83
  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) {
84
+ files.forEach(file => {
95
85
  const langCode = file.replace('.ts', '');
96
86
  const targetPath = path.join(localesDir, file);
97
-
98
87
  console.log(`🌍 Syncing ${langCode} (${getLangDisplayName(langCode)})...`);
99
-
100
88
  const result = syncLanguageFile(enUSPath, targetPath, langCode);
101
-
102
89
  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++;
90
+ console.log(` ✏️ +${result.added} keys, -${result.removed} keys`);
119
91
  } else {
120
92
  console.log(` ✅ Already synchronized`);
121
93
  }
122
- }
94
+ });
123
95
 
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
- }
96
+ console.log(`\n Synchronization completed!`);
147
97
  }
148
98
 
149
99
  main();
@@ -3,14 +3,14 @@
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] [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
14
 
15
15
  async function translateLanguageFile(enUSPath, targetPath, langCode) {
16
16
  const targetLang = getTargetLanguage(langCode);
@@ -46,78 +46,44 @@ async function translateLanguageFile(enUSPath, targetPath, langCode) {
46
46
  }
47
47
 
48
48
  async function main() {
49
- const targetDir = process.argv[2] || 'src/domains/localization/translations';
49
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
50
50
  const targetLangCode = process.argv[3];
51
51
  const localesDir = path.resolve(process.cwd(), targetDir);
52
+ const enUSPath = path.join(localesDir, 'en-US.ts');
52
53
 
53
54
  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
-
60
- if (!fs.existsSync(localesDir)) {
61
- console.error(`❌ Locales directory not found: ${localesDir}`);
62
- process.exit(1);
63
- }
64
-
65
- const enUSPath = path.join(localesDir, 'en-US.ts');
66
- if (!fs.existsSync(enUSPath)) {
67
- console.error(`❌ Base file not found: ${enUSPath}`);
55
+ if (!fs.existsSync(localesDir) || !fs.existsSync(enUSPath)) {
56
+ console.error(`❌ Localization files not found in: ${localesDir}`);
68
57
  process.exit(1);
69
58
  }
70
59
 
71
60
  const files = fs.readdirSync(localesDir)
72
61
  .filter(f => {
73
62
  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;
63
+ return isLangFile && (!targetLangCode || f === `${targetLangCode}.ts`);
79
64
  })
80
65
  .sort();
81
66
 
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');
67
+ console.log(`📊 Languages to translate: ${files.length}\n`);
88
68
 
89
69
  let totalTranslated = 0;
90
- let totalNewKeys = 0;
91
-
92
70
  for (const file of files) {
93
71
  const langCode = file.replace('.ts', '');
94
72
  const targetPath = path.join(localesDir, file);
95
-
96
- console.log(`\n🌍 Translating ${langCode} (${getLangDisplayName(langCode)})...`);
97
-
73
+ console.log(`🌍 Translating ${langCode} (${getLangDisplayName(langCode)})...`);
98
74
  const stats = await translateLanguageFile(enUSPath, targetPath, langCode);
99
75
  totalTranslated += stats.count;
100
- totalNewKeys += stats.newKeys.length;
101
-
102
76
  if (stats.count > 0) {
103
77
  console.log(` ✅ Translated ${stats.count} strings`);
104
- if (stats.newKeys.length > 0) {
105
- console.log(` 🆕 ${stats.newKeys.length} new keys translated`);
106
- }
107
78
  } else {
108
79
  console.log(` ✓ Already complete`);
109
80
  }
110
81
  }
111
82
 
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`);
83
+ console.log(`\n✅ Translation completed! (Total: ${totalTranslated})`);
118
84
  }
119
85
 
120
- main().catch((error) => {
86
+ main().catch(error => {
121
87
  console.error('❌ Translation failed:', error.message);
122
88
  process.exit(1);
123
89
  });
@@ -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
- };