@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 +1 -1
- package/src/presentation/components/LanguageSection.tsx +3 -5
- package/src/scripts/sync-translations.js +32 -4
- package/src/scripts/translate-missing.js +17 -9
- package/src/scripts/utils/key-detector.js +52 -0
- package/src/scripts/utils/sync-helper.js +21 -14
- package/src/scripts/utils/translator.js +9 -4
package/package.json
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
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
|
|
26
|
-
const
|
|
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];
|
|
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
|
-
|
|
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
|
|
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];
|
|
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
|
|
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(
|
|
9
|
-
for (const key in
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
!Array.isArray(
|
|
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(
|
|
25
|
+
addMissingKeys(sourceValue, targetObj[key], stats);
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
return stats;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
function removeExtraKeys(
|
|
31
|
+
function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys: [] }) {
|
|
28
32
|
for (const key in targetObj) {
|
|
29
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
!Array.isArray(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|