@umituz/react-native-localization 3.5.24 → 3.5.26

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": "3.5.24",
3
+ "version": "3.5.26",
4
4
  "description": "Generic localization system for React Native apps with i18n support",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,11 +1,9 @@
1
1
  import React from 'react';
2
2
  import { View, Text, Pressable, StyleSheet, type ViewStyle } from 'react-native';
3
3
  // @ts-ignore - Optional peer dependency
4
- import { Feather } from '@expo/vector-icons';
5
- // @ts-ignore - Optional peer dependency
6
4
  import { useNavigation } from '@react-navigation/native';
7
5
  // @ts-ignore - Optional peer dependency
8
- import { useAppDesignTokens } from '@umituz/react-native-design-system';
6
+ import { useAppDesignTokens, AtomicIcon } from '@umituz/react-native-design-system';
9
7
  import { useLocalization } from '../../infrastructure/hooks/useLocalization';
10
8
  import { getLanguageByCode } from '../../infrastructure/config/languages';
11
9
 
@@ -67,7 +65,7 @@ export const LanguageSection: React.FC<LanguageSectionProps> = ({
67
65
  { backgroundColor: `${colors.primary}15` },
68
66
  ]}
69
67
  >
70
- <Feather name="globe" size={24} color={colors.primary} />
68
+ <AtomicIcon name="globe-outline" customSize={24} customColor={colors.primary} />
71
69
  </View>
72
70
  <View style={styles.textContainer}>
73
71
  <Text style={[styles.title, { color: colors.textPrimary }]}>{title}</Text>
@@ -75,7 +73,7 @@ export const LanguageSection: React.FC<LanguageSectionProps> = ({
75
73
  {languageDisplay}
76
74
  </Text>
77
75
  </View>
78
- <Feather name="chevron-right" size={20} color={colors.textSecondary} />
76
+ <AtomicIcon name="chevron-forward-outline" customSize={20} customColor={colors.textSecondary} />
79
77
  </View>
80
78
  </Pressable>
81
79
  </View>
@@ -10,6 +10,7 @@ const fs = require('fs');
10
10
  const path = require('path');
11
11
  const { parseTypeScriptFile, generateTypeScriptContent } = require('./utils/file-parser');
12
12
  const { addMissingKeys, removeExtraKeys } = require('./utils/sync-helper');
13
+ const { detectNewKeys } = require('./utils/key-detector');
13
14
  const { getLangDisplayName } = require('./utils/translation-config');
14
15
 
15
16
  function syncLanguageFile(enUSPath, targetPath, langCode) {
@@ -22,8 +23,9 @@ function syncLanguageFile(enUSPath, targetPath, langCode) {
22
23
  target = {};
23
24
  }
24
25
 
25
- const addStats = { added: 0 };
26
- const removeStats = { removed: 0 };
26
+ const newKeys = detectNewKeys(enUS, target);
27
+ const addStats = { added: 0, newKeys: [] };
28
+ const removeStats = { removed: 0, removedKeys: [] };
27
29
 
28
30
  addMissingKeys(enUS, target, addStats);
29
31
  removeExtraKeys(enUS, target, removeStats);
@@ -38,13 +40,15 @@ function syncLanguageFile(enUSPath, targetPath, langCode) {
38
40
  return {
39
41
  added: addStats.added,
40
42
  removed: removeStats.removed,
43
+ newKeys,
44
+ removedKeys: removeStats.removedKeys,
41
45
  changed,
42
46
  };
43
47
  }
44
48
 
45
49
  function main() {
46
50
  const targetDir = process.argv[2] || 'src/domains/localization/translations';
47
- const targetLangCode = process.argv[3]; // Optional specific language to sync
51
+ const targetLangCode = process.argv[3];
48
52
  const localesDir = path.resolve(process.cwd(), targetDir);
49
53
 
50
54
  console.log('šŸš€ Starting translation synchronization...\n');
@@ -85,6 +89,7 @@ function main() {
85
89
  let totalAdded = 0;
86
90
  let totalRemoved = 0;
87
91
  let totalChanged = 0;
92
+ const allNewKeys = [];
88
93
 
89
94
  for (const file of files) {
90
95
  const langCode = file.replace('.ts', '');
@@ -95,7 +100,19 @@ function main() {
95
100
  const result = syncLanguageFile(enUSPath, targetPath, langCode);
96
101
 
97
102
  if (result.changed) {
98
- console.log(` āœļø +${result.added} keys, -${result.removed} keys`);
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
+ }
99
116
  totalAdded += result.added;
100
117
  totalRemoved += result.removed;
101
118
  totalChanged++;
@@ -113,6 +130,17 @@ function main() {
113
130
  if (totalChanged > 0) {
114
131
  console.log(`\nāœ… Synchronization completed!`);
115
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
+ }
116
144
  } else {
117
145
  console.log(`\nāœ… All languages were already synchronized!`);
118
146
  }
@@ -17,12 +17,12 @@ async function translateLanguageFile(enUSPath, targetPath, langCode) {
17
17
 
18
18
  if (!targetLang) {
19
19
  console.log(` āš ļø No language mapping for ${langCode}, skipping`);
20
- return 0;
20
+ return { count: 0, newKeys: [] };
21
21
  }
22
22
 
23
23
  if (isEnglishVariant(langCode)) {
24
24
  console.log(` ā­ļø Skipping English variant: ${langCode}`);
25
- return 0;
25
+ return { count: 0, newKeys: [] };
26
26
  }
27
27
 
28
28
  const enUS = parseTypeScriptFile(enUSPath);
@@ -34,7 +34,7 @@ async function translateLanguageFile(enUSPath, targetPath, langCode) {
34
34
  target = {};
35
35
  }
36
36
 
37
- const stats = { count: 0 };
37
+ const stats = { count: 0, newKeys: [] };
38
38
  await translateObject(enUS, target, targetLang, '', stats);
39
39
 
40
40
  if (stats.count > 0) {
@@ -42,12 +42,12 @@ async function translateLanguageFile(enUSPath, targetPath, langCode) {
42
42
  fs.writeFileSync(targetPath, content);
43
43
  }
44
44
 
45
- return stats.count;
45
+ return stats;
46
46
  }
47
47
 
48
48
  async function main() {
49
49
  const targetDir = process.argv[2] || 'src/domains/localization/translations';
50
- const targetLangCode = process.argv[3]; // Optional specific language to translate
50
+ const targetLangCode = process.argv[3];
51
51
  const localesDir = path.resolve(process.cwd(), targetDir);
52
52
 
53
53
  console.log('šŸš€ Starting automatic translation...\n');
@@ -87,6 +87,7 @@ async function main() {
87
87
  console.log('⚔ Running with 200ms delay between API calls\n');
88
88
 
89
89
  let totalTranslated = 0;
90
+ let totalNewKeys = 0;
90
91
 
91
92
  for (const file of files) {
92
93
  const langCode = file.replace('.ts', '');
@@ -94,11 +95,15 @@ async function main() {
94
95
 
95
96
  console.log(`\nšŸŒ Translating ${langCode} (${getLangDisplayName(langCode)})...`);
96
97
 
97
- const count = await translateLanguageFile(enUSPath, targetPath, langCode);
98
- totalTranslated += count;
98
+ const stats = await translateLanguageFile(enUSPath, targetPath, langCode);
99
+ totalTranslated += stats.count;
100
+ totalNewKeys += stats.newKeys.length;
99
101
 
100
- if (count > 0) {
101
- console.log(` āœ… Translated ${count} strings`);
102
+ if (stats.count > 0) {
103
+ console.log(` āœ… Translated ${stats.count} strings`);
104
+ if (stats.newKeys.length > 0) {
105
+ console.log(` šŸ†• ${stats.newKeys.length} new keys translated`);
106
+ }
102
107
  } else {
103
108
  console.log(` āœ“ Already complete`);
104
109
  }
@@ -106,6 +111,9 @@ async function main() {
106
111
 
107
112
  console.log(`\nāœ… Translation completed!`);
108
113
  console.log(` Total strings translated: ${totalTranslated}`);
114
+ if (totalNewKeys > 0) {
115
+ console.log(` New keys translated: ${totalNewKeys}`);
116
+ }
109
117
  console.log(`\nšŸ“ Next: Run 'npm run i18n:setup' to update index.ts`);
110
118
  }
111
119
 
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Key Detector
5
+ * Detects new, missing, and removed keys between source and target objects
6
+ */
7
+
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];
13
+
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
+ }
25
+ }
26
+ return newKeys;
27
+ }
28
+
29
+ function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
30
+ for (const key in targetObj) {
31
+ const currentPath = path ? `${path}.${key}` : key;
32
+
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
+ }
45
+ }
46
+ return missingKeys;
47
+ }
48
+
49
+ module.exports = {
50
+ detectNewKeys,
51
+ detectMissingKeys,
52
+ };
@@ -5,39 +5,46 @@
5
5
  * Helper functions for synchronizing translation keys
6
6
  */
7
7
 
8
- function addMissingKeys(enObj, targetObj, stats = { added: 0 }) {
9
- for (const key in enObj) {
10
- if (!Object.prototype.hasOwnProperty.call(targetObj, key)) {
11
- targetObj[key] = enObj[key];
8
+ function addMissingKeys(sourceObj, targetObj, stats = { added: 0, newKeys: [] }) {
9
+ for (const key in sourceObj) {
10
+ const sourceValue = sourceObj[key];
11
+ const isNewKey = !Object.prototype.hasOwnProperty.call(targetObj, key);
12
+
13
+ if (isNewKey) {
14
+ targetObj[key] = sourceValue;
12
15
  stats.added++;
16
+ stats.newKeys.push(key);
13
17
  } else if (
14
- typeof enObj[key] === 'object' &&
15
- enObj[key] !== null &&
16
- !Array.isArray(enObj[key])
18
+ typeof sourceValue === 'object' &&
19
+ sourceValue !== null &&
20
+ !Array.isArray(sourceValue)
17
21
  ) {
18
22
  if (!targetObj[key] || typeof targetObj[key] !== 'object') {
19
23
  targetObj[key] = {};
20
24
  }
21
- addMissingKeys(enObj[key], targetObj[key], stats);
25
+ addMissingKeys(sourceValue, targetObj[key], stats);
22
26
  }
23
27
  }
24
28
  return stats;
25
29
  }
26
30
 
27
- function removeExtraKeys(enObj, targetObj, stats = { removed: 0 }) {
31
+ function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys: [] }) {
28
32
  for (const key in targetObj) {
29
- if (!Object.prototype.hasOwnProperty.call(enObj, key)) {
33
+ const isExtraKey = !Object.prototype.hasOwnProperty.call(sourceObj, key);
34
+
35
+ if (isExtraKey) {
30
36
  delete targetObj[key];
31
37
  stats.removed++;
38
+ stats.removedKeys.push(key);
32
39
  } else if (
33
- typeof enObj[key] === 'object' &&
34
- enObj[key] !== null &&
35
- !Array.isArray(enObj[key]) &&
40
+ typeof sourceObj[key] === 'object' &&
41
+ sourceObj[key] !== null &&
42
+ !Array.isArray(sourceObj[key]) &&
36
43
  typeof targetObj[key] === 'object' &&
37
44
  targetObj[key] !== null &&
38
45
  !Array.isArray(targetObj[key])
39
46
  ) {
40
- removeExtraKeys(enObj[key], targetObj[key], stats);
47
+ removeExtraKeys(sourceObj[key], targetObj[key], stats);
41
48
  }
42
49
  }
43
50
  return stats;
@@ -48,17 +48,18 @@ async function translateText(text, targetLang) {
48
48
  }
49
49
 
50
50
  function needsTranslation(value, enValue) {
51
- if (typeof value !== 'string') return false;
51
+ if (typeof value !== 'string' || typeof enValue !== 'string') return false;
52
52
  if (value === enValue) return true;
53
53
  if (shouldSkipWord(value)) return false;
54
54
  return false;
55
55
  }
56
56
 
57
- async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0 }) {
57
+ async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0, newKeys: [] }) {
58
58
  for (const key in enObj) {
59
59
  const currentPath = path ? `${path}.${key}` : key;
60
60
  const enValue = enObj[key];
61
61
  const targetValue = targetObj[key];
62
+ const isNewKey = !Object.prototype.hasOwnProperty.call(targetObj, key);
62
63
 
63
64
  if (Array.isArray(enValue)) {
64
65
  if (!Array.isArray(targetValue)) {
@@ -68,9 +69,11 @@ async function translateObject(enObj, targetObj, targetLang, path = '', stats =
68
69
  if (typeof enValue[i] === 'string') {
69
70
  if (needsTranslation(targetObj[key][i], enValue[i])) {
70
71
  const preview = enValue[i].length > 40 ? enValue[i].substring(0, 40) + '...' : enValue[i];
71
- console.log(` šŸ”„ ${currentPath}[${i}]: "${preview}"`);
72
+ const prefix = isNewKey ? 'šŸ†• NEW' : 'šŸ”„';
73
+ console.log(` ${prefix} ${currentPath}[${i}]: "${preview}"`);
72
74
  targetObj[key][i] = await translateText(enValue[i], targetLang);
73
75
  stats.count++;
76
+ if (isNewKey) stats.newKeys.push(`${currentPath}[${i}]`);
74
77
  await delay(200);
75
78
  }
76
79
  }
@@ -83,9 +86,11 @@ async function translateObject(enObj, targetObj, targetLang, path = '', stats =
83
86
  } else if (typeof enValue === 'string') {
84
87
  if (needsTranslation(targetValue, enValue)) {
85
88
  const preview = enValue.length > 40 ? enValue.substring(0, 40) + '...' : enValue;
86
- console.log(` šŸ”„ ${currentPath}: "${preview}"`);
89
+ const prefix = isNewKey ? 'šŸ†• NEW' : 'šŸ”„';
90
+ console.log(` ${prefix} ${currentPath}: "${preview}"`);
87
91
  targetObj[key] = await translateText(enValue, targetLang);
88
92
  stats.count++;
93
+ if (isNewKey) stats.newKeys.push(currentPath);
89
94
  await delay(200);
90
95
  }
91
96
  }