@umituz/react-native-settings 5.4.22 → 5.4.23
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/domains/feedback/presentation/components/FeedbackForm.styles.ts +8 -2
- package/src/domains/gamification/components/AchievementItem.tsx +3 -2
- package/src/domains/gamification/components/GamificationScreen/GamificationScreen.tsx +2 -1
- package/src/domains/gamification/components/GamificationScreen/styles.ts +43 -37
- package/src/domains/gamification/components/styles/achievementItemStyles.ts +75 -68
- package/src/domains/localization/infrastructure/components/LanguageSwitcher.styles.ts +28 -25
- package/src/domains/localization/infrastructure/components/LanguageSwitcher.tsx +6 -6
- package/src/domains/localization/presentation/components/LanguageItem.styles.ts +47 -33
- package/src/domains/localization/presentation/components/LanguageItem.tsx +9 -29
- package/src/domains/localization/scripts/translate-missing.js +5 -95
- package/src/domains/localization/scripts/utils/translation-config.js +13 -109
- package/src/domains/localization/scripts/utils/translator.js +0 -91
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.23",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification - expo-store-review and expo-device now lazy loaded",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { StyleSheet } from "react-native";
|
|
2
2
|
import type { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
3
|
+
import { withAlpha } from "@umituz/react-native-design-system/theme";
|
|
3
4
|
|
|
4
|
-
export const getFeedbackFormStyles = (
|
|
5
|
+
export const getFeedbackFormStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
5
6
|
StyleSheet.create({
|
|
6
7
|
container: {
|
|
7
8
|
width: "100%",
|
|
@@ -25,7 +26,7 @@ export const getFeedbackFormStyles = (_tokens: ReturnType<typeof useAppDesignTok
|
|
|
25
26
|
alignItems: "center",
|
|
26
27
|
marginVertical: 32,
|
|
27
28
|
paddingVertical: 16,
|
|
28
|
-
backgroundColor:
|
|
29
|
+
backgroundColor: withAlpha(tokens.colors.primary, 0.05),
|
|
29
30
|
borderRadius: 16,
|
|
30
31
|
},
|
|
31
32
|
ratingLabel: {
|
|
@@ -34,6 +35,7 @@ export const getFeedbackFormStyles = (_tokens: ReturnType<typeof useAppDesignTok
|
|
|
34
35
|
letterSpacing: 1,
|
|
35
36
|
marginBottom: 16,
|
|
36
37
|
textTransform: "uppercase",
|
|
38
|
+
color: tokens.colors.textSecondary,
|
|
37
39
|
},
|
|
38
40
|
stars: {
|
|
39
41
|
flexDirection: "row",
|
|
@@ -52,10 +54,14 @@ export const getFeedbackFormStyles = (_tokens: ReturnType<typeof useAppDesignTok
|
|
|
52
54
|
borderRadius: 16,
|
|
53
55
|
padding: 16,
|
|
54
56
|
fontSize: 15,
|
|
57
|
+
color: tokens.colors.textPrimary,
|
|
58
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
59
|
+
borderColor: tokens.colors.border,
|
|
55
60
|
},
|
|
56
61
|
errorText: {
|
|
57
62
|
marginTop: 8,
|
|
58
63
|
fontWeight: "600",
|
|
64
|
+
color: tokens.colors.error,
|
|
59
65
|
},
|
|
60
66
|
submitButton: {
|
|
61
67
|
width: "100%",
|
|
@@ -8,7 +8,7 @@ import { View } from "react-native";
|
|
|
8
8
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
9
9
|
import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system/theme";
|
|
10
10
|
import type { AchievementItemProps } from "./types/AchievementItemProps";
|
|
11
|
-
import {
|
|
11
|
+
import { createAchievementItemStyles } from "./styles/achievementItemStyles";
|
|
12
12
|
|
|
13
13
|
export const AchievementItem: React.FC<AchievementItemProps> = ({
|
|
14
14
|
title,
|
|
@@ -28,6 +28,7 @@ export const AchievementItem: React.FC<AchievementItemProps> = ({
|
|
|
28
28
|
lockedOpacity,
|
|
29
29
|
}) => {
|
|
30
30
|
const tokens = useAppDesignTokens();
|
|
31
|
+
const styles = createAchievementItemStyles(tokens);
|
|
31
32
|
const finalAccentColor = accentColor || tokens.colors.primary;
|
|
32
33
|
const finalBackgroundColor = backgroundColor || tokens.colors.surface;
|
|
33
34
|
const finalTextColor = textColor || tokens.colors.textPrimary;
|
|
@@ -61,7 +62,7 @@ export const AchievementItem: React.FC<AchievementItemProps> = ({
|
|
|
61
62
|
</AtomicText>
|
|
62
63
|
{isUnlocked && (
|
|
63
64
|
<View style={[styles.checkmark, { backgroundColor: finalAccentColor }]}>
|
|
64
|
-
<AtomicText style={[styles.checkmarkText, { color: tokens.colors.
|
|
65
|
+
<AtomicText style={[styles.checkmarkText, { color: tokens.colors.onPrimary || '#FFFFFF' }]}>
|
|
65
66
|
✓
|
|
66
67
|
</AtomicText>
|
|
67
68
|
</View>
|
|
@@ -14,7 +14,7 @@ import { LevelProgress } from "../LevelProgress";
|
|
|
14
14
|
import { StreakDisplay } from "../StreakDisplay";
|
|
15
15
|
import { StatsGrid } from "./StatsGrid";
|
|
16
16
|
import { AchievementsList } from "./AchievementsList";
|
|
17
|
-
import {
|
|
17
|
+
import { createStyles } from "./styles";
|
|
18
18
|
import type { GamificationScreenProps } from "./types";
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -39,6 +39,7 @@ export const GamificationScreenInner: React.FC<GamificationScreenProps> = ({
|
|
|
39
39
|
}) => {
|
|
40
40
|
const _navigation = useAppNavigation();
|
|
41
41
|
const tokens = useAppDesignTokens();
|
|
42
|
+
const styles = createStyles(tokens);
|
|
42
43
|
|
|
43
44
|
// Use tokens for fallbacks
|
|
44
45
|
const finalAccentColor = accentColor || tokens.colors.primary;
|
|
@@ -3,41 +3,47 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { StyleSheet } from "react-native";
|
|
6
|
+
import type { DesignTokens } from "@umituz/react-native-design-system/theme";
|
|
6
7
|
|
|
7
|
-
export const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
8
|
+
export const createStyles = (tokens: DesignTokens) =>
|
|
9
|
+
StyleSheet.create({
|
|
10
|
+
container: {
|
|
11
|
+
flex: 1,
|
|
12
|
+
backgroundColor: tokens.colors.backgroundPrimary,
|
|
13
|
+
},
|
|
14
|
+
scrollView: {
|
|
15
|
+
flex: 1,
|
|
16
|
+
},
|
|
17
|
+
scrollContent: {
|
|
18
|
+
padding: tokens.spacing.md,
|
|
19
|
+
paddingBottom: tokens.spacing.xl,
|
|
20
|
+
},
|
|
21
|
+
header: {
|
|
22
|
+
marginBottom: tokens.spacing.lg,
|
|
23
|
+
},
|
|
24
|
+
title: {
|
|
25
|
+
fontSize: 28,
|
|
26
|
+
fontWeight: "bold",
|
|
27
|
+
color: tokens.colors.textPrimary,
|
|
28
|
+
},
|
|
29
|
+
section: {
|
|
30
|
+
marginBottom: tokens.spacing.xl,
|
|
31
|
+
},
|
|
32
|
+
sectionTitle: {
|
|
33
|
+
fontSize: 18,
|
|
34
|
+
fontWeight: "600",
|
|
35
|
+
marginBottom: tokens.spacing.md,
|
|
36
|
+
color: tokens.colors.textPrimary,
|
|
37
|
+
},
|
|
38
|
+
statsGrid: {
|
|
39
|
+
flexDirection: "row",
|
|
40
|
+
flexWrap: "wrap",
|
|
41
|
+
gap: tokens.spacing.md,
|
|
42
|
+
},
|
|
43
|
+
emptyText: {
|
|
44
|
+
fontSize: 14,
|
|
45
|
+
textAlign: "center",
|
|
46
|
+
paddingVertical: tokens.spacing.lg,
|
|
47
|
+
color: tokens.colors.textSecondary,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -3,72 +3,79 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { StyleSheet } from 'react-native';
|
|
6
|
+
import type { DesignTokens } from "@umituz/react-native-design-system/theme";
|
|
6
7
|
|
|
7
|
-
export const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
8
|
+
export const createAchievementItemStyles = (tokens: DesignTokens) =>
|
|
9
|
+
StyleSheet.create({
|
|
10
|
+
container: {
|
|
11
|
+
flexDirection: "row",
|
|
12
|
+
alignItems: "center",
|
|
13
|
+
padding: tokens.spacing.md,
|
|
14
|
+
borderRadius: tokens.borders.radius.md,
|
|
15
|
+
marginBottom: tokens.spacing.sm,
|
|
16
|
+
borderWidth: 1,
|
|
17
|
+
borderColor: tokens.colors.borderLight,
|
|
18
|
+
},
|
|
19
|
+
iconContainer: {
|
|
20
|
+
width: 48,
|
|
21
|
+
height: 48,
|
|
22
|
+
borderRadius: 24,
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
justifyContent: "center",
|
|
25
|
+
marginRight: tokens.spacing.md,
|
|
26
|
+
},
|
|
27
|
+
content: {
|
|
28
|
+
flex: 1,
|
|
29
|
+
},
|
|
30
|
+
header: {
|
|
31
|
+
flexDirection: "row",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
justifyContent: "space-between",
|
|
34
|
+
marginBottom: 4,
|
|
35
|
+
},
|
|
36
|
+
title: {
|
|
37
|
+
fontSize: 16,
|
|
38
|
+
fontWeight: "600",
|
|
39
|
+
flex: 1,
|
|
40
|
+
color: "inherit",
|
|
41
|
+
},
|
|
42
|
+
checkmark: {
|
|
43
|
+
width: 20,
|
|
44
|
+
height: 20,
|
|
45
|
+
borderRadius: 10,
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
justifyContent: "center",
|
|
48
|
+
marginLeft: 8,
|
|
49
|
+
},
|
|
50
|
+
checkmarkText: {
|
|
51
|
+
fontSize: 12,
|
|
52
|
+
fontWeight: "bold",
|
|
53
|
+
},
|
|
54
|
+
description: {
|
|
55
|
+
fontSize: 13,
|
|
56
|
+
lineHeight: 18,
|
|
57
|
+
color: "inherit",
|
|
58
|
+
},
|
|
59
|
+
progressContainer: {
|
|
60
|
+
marginTop: 8,
|
|
61
|
+
flexDirection: "row",
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
gap: 8,
|
|
64
|
+
},
|
|
65
|
+
progressBar: {
|
|
66
|
+
flex: 1,
|
|
67
|
+
height: 4,
|
|
68
|
+
borderRadius: 2,
|
|
69
|
+
overflow: "hidden",
|
|
70
|
+
},
|
|
71
|
+
progressFill: {
|
|
72
|
+
height: "100%",
|
|
73
|
+
borderRadius: 2,
|
|
74
|
+
},
|
|
75
|
+
progressText: {
|
|
76
|
+
fontSize: 11,
|
|
77
|
+
minWidth: 40,
|
|
78
|
+
textAlign: "right",
|
|
79
|
+
color: "inherit",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -3,36 +3,39 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { StyleSheet } from 'react-native';
|
|
6
|
+
import type { DesignTokens } from "@umituz/react-native-design-system/theme";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_CONFIG = {
|
|
8
9
|
defaultIconSize: 20,
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
export const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
12
|
+
export const createLanguageSwitcherStyles = (tokens: DesignTokens) =>
|
|
13
|
+
StyleSheet.create({
|
|
14
|
+
container: {
|
|
15
|
+
flexDirection: 'row',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
gap: tokens.spacing.sm,
|
|
18
|
+
paddingHorizontal: tokens.spacing.xs,
|
|
19
|
+
paddingVertical: tokens.spacing.xs,
|
|
20
|
+
},
|
|
21
|
+
disabled: {
|
|
22
|
+
opacity: 0.5,
|
|
23
|
+
},
|
|
24
|
+
flag: {
|
|
25
|
+
fontSize: DEFAULT_CONFIG.defaultIconSize,
|
|
26
|
+
textAlign: 'center',
|
|
27
|
+
},
|
|
28
|
+
languageName: {
|
|
29
|
+
fontSize: 14,
|
|
30
|
+
fontWeight: '600',
|
|
31
|
+
textAlign: 'center',
|
|
32
|
+
color: tokens.colors.textPrimary,
|
|
33
|
+
},
|
|
34
|
+
icon: {
|
|
35
|
+
fontSize: DEFAULT_CONFIG.defaultIconSize,
|
|
36
|
+
textAlign: 'center',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
36
39
|
|
|
37
40
|
export const DEFAULT_CONFIG_VALUES = {
|
|
38
41
|
hitSlop: { top: 10, bottom: 10, left: 10, right: 10 } as const,
|
|
@@ -8,7 +8,7 @@ import { TouchableOpacity, type StyleProp, type ViewStyle, type TextStyle } from
|
|
|
8
8
|
import { AtomicText } from '@umituz/react-native-design-system/atoms';
|
|
9
9
|
import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
|
|
10
10
|
import { useLanguageSwitcher } from './useLanguageSwitcher';
|
|
11
|
-
import {
|
|
11
|
+
import { createLanguageSwitcherStyles, DEFAULT_CONFIG_VALUES } from './LanguageSwitcher.styles';
|
|
12
12
|
|
|
13
13
|
export interface LanguageSwitcherProps {
|
|
14
14
|
showName?: boolean;
|
|
@@ -36,6 +36,7 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
36
36
|
accessibilityLabel,
|
|
37
37
|
}) => {
|
|
38
38
|
const tokens = useAppDesignTokens();
|
|
39
|
+
const styles = createLanguageSwitcherStyles(tokens);
|
|
39
40
|
const { currentLang, handlePress } = useLanguageSwitcher({ onPress, disabled });
|
|
40
41
|
|
|
41
42
|
const accessibilityProps = useMemo(() => ({
|
|
@@ -50,9 +51,8 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
50
51
|
return (
|
|
51
52
|
<TouchableOpacity
|
|
52
53
|
style={[
|
|
53
|
-
styles.container,
|
|
54
|
-
|
|
55
|
-
style,
|
|
54
|
+
styles.container,
|
|
55
|
+
style,
|
|
56
56
|
disabled && styles.disabled
|
|
57
57
|
]}
|
|
58
58
|
onPress={handlePress}
|
|
@@ -68,8 +68,8 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
68
68
|
</AtomicText>
|
|
69
69
|
)}
|
|
70
70
|
{showName && (
|
|
71
|
-
<AtomicText
|
|
72
|
-
type="bodySmall"
|
|
71
|
+
<AtomicText
|
|
72
|
+
type="bodySmall"
|
|
73
73
|
style={[styles.languageName, { color: textColor, fontWeight: '600' }, textStyle]}
|
|
74
74
|
>
|
|
75
75
|
{currentLang.nativeName}
|
|
@@ -3,38 +3,52 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { StyleSheet } from 'react-native';
|
|
6
|
+
import type { DesignTokens } from "@umituz/react-native-design-system/theme";
|
|
6
7
|
|
|
7
|
-
export const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
8
|
+
export const createLanguageItemStyles = (tokens: DesignTokens) =>
|
|
9
|
+
StyleSheet.create({
|
|
10
|
+
languageItem: {
|
|
11
|
+
flexDirection: 'row',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
justifyContent: 'space-between',
|
|
14
|
+
borderWidth: 1,
|
|
15
|
+
borderColor: tokens.colors.borderLight,
|
|
16
|
+
backgroundColor: tokens.colors.surface,
|
|
17
|
+
borderRadius: tokens.borders.radius.md,
|
|
18
|
+
marginBottom: tokens.spacing.sm,
|
|
19
|
+
overflow: 'hidden',
|
|
20
|
+
},
|
|
21
|
+
selectedLanguageItem: {
|
|
22
|
+
borderWidth: 2,
|
|
23
|
+
borderColor: tokens.colors.primary,
|
|
24
|
+
},
|
|
25
|
+
languageContent: {
|
|
26
|
+
flexDirection: 'row',
|
|
27
|
+
alignItems: 'center',
|
|
28
|
+
flex: 1,
|
|
29
|
+
padding: tokens.spacing.md,
|
|
30
|
+
},
|
|
31
|
+
flag: {
|
|
32
|
+
fontSize: 24,
|
|
33
|
+
marginRight: tokens.spacing.md,
|
|
34
|
+
},
|
|
35
|
+
languageText: {
|
|
36
|
+
flex: 1,
|
|
37
|
+
flexShrink: 1,
|
|
38
|
+
},
|
|
39
|
+
nativeName: {
|
|
40
|
+
fontSize: 16,
|
|
41
|
+
fontWeight: '500',
|
|
42
|
+
color: tokens.colors.textPrimary,
|
|
43
|
+
marginBottom: 2,
|
|
44
|
+
},
|
|
45
|
+
languageName: {
|
|
46
|
+
fontSize: 14,
|
|
47
|
+
color: tokens.colors.textSecondary,
|
|
48
|
+
},
|
|
49
|
+
checkIcon: {
|
|
50
|
+
padding: tokens.spacing.md,
|
|
51
|
+
// Replaced by AtomicIcon
|
|
52
|
+
},
|
|
53
|
+
});
|
|
40
54
|
|
|
@@ -17,7 +17,7 @@ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system/atoms
|
|
|
17
17
|
import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
|
|
18
18
|
import type { Language } from '../../infrastructure/storage/types/Language';
|
|
19
19
|
import { ICON_PATHS } from '../../../../utils/iconPaths';
|
|
20
|
-
import {
|
|
20
|
+
import { createLanguageItemStyles } from './LanguageItem.styles';
|
|
21
21
|
|
|
22
22
|
interface LanguageItemProps {
|
|
23
23
|
item: Language;
|
|
@@ -42,29 +42,9 @@ export const LanguageItem: React.FC<LanguageItemProps> = React.memo(({
|
|
|
42
42
|
customStyles,
|
|
43
43
|
}) => {
|
|
44
44
|
const tokens = useAppDesignTokens();
|
|
45
|
+
const styles = createLanguageItemStyles(tokens);
|
|
45
46
|
|
|
46
47
|
const themedStyles = useMemo(() => ({
|
|
47
|
-
languageItem: {
|
|
48
|
-
paddingHorizontal: tokens.spacing.md,
|
|
49
|
-
paddingVertical: tokens.spacing.md,
|
|
50
|
-
borderRadius: tokens.borders.radius.lg,
|
|
51
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
52
|
-
marginBottom: tokens.spacing.md,
|
|
53
|
-
flexDirection: 'row' as const,
|
|
54
|
-
alignItems: 'center' as const,
|
|
55
|
-
justifyContent: 'space-between' as const,
|
|
56
|
-
} as ViewStyle,
|
|
57
|
-
selectedLanguageItem: {
|
|
58
|
-
backgroundColor: tokens.colors.surface,
|
|
59
|
-
borderColor: tokens.colors.primary,
|
|
60
|
-
borderWidth: 1.5,
|
|
61
|
-
} as ViewStyle,
|
|
62
|
-
nativeName: {
|
|
63
|
-
color: tokens.colors.textPrimary,
|
|
64
|
-
} as TextStyle,
|
|
65
|
-
languageName: {
|
|
66
|
-
color: tokens.colors.textSecondary,
|
|
67
|
-
} as TextStyle,
|
|
68
48
|
flagContainer: {
|
|
69
49
|
width: 44,
|
|
70
50
|
height: 44,
|
|
@@ -80,8 +60,8 @@ export const LanguageItem: React.FC<LanguageItemProps> = React.memo(({
|
|
|
80
60
|
<TouchableOpacity
|
|
81
61
|
testID="language-item-test"
|
|
82
62
|
style={[
|
|
83
|
-
|
|
84
|
-
isSelected &&
|
|
63
|
+
styles.languageItem,
|
|
64
|
+
isSelected && styles.selectedLanguageItem,
|
|
85
65
|
customStyles?.languageItem,
|
|
86
66
|
]}
|
|
87
67
|
onPress={() => {
|
|
@@ -110,15 +90,15 @@ export const LanguageItem: React.FC<LanguageItemProps> = React.memo(({
|
|
|
110
90
|
)}
|
|
111
91
|
</View>
|
|
112
92
|
<View style={[styles.languageText, customStyles?.languageText]}>
|
|
113
|
-
<AtomicText
|
|
93
|
+
<AtomicText
|
|
114
94
|
type="bodyLarge"
|
|
115
|
-
style={[
|
|
95
|
+
style={[styles.nativeName, { fontWeight: '600' }, customStyles?.nativeName]}
|
|
116
96
|
>
|
|
117
97
|
{item.nativeName}
|
|
118
98
|
</AtomicText>
|
|
119
|
-
<AtomicText
|
|
120
|
-
type="labelMedium"
|
|
121
|
-
style={[
|
|
99
|
+
<AtomicText
|
|
100
|
+
type="labelMedium"
|
|
101
|
+
style={[styles.languageName, customStyles?.nativeName]}
|
|
122
102
|
>
|
|
123
103
|
{item.name}
|
|
124
104
|
</AtomicText>
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import { parseTypeScriptFile, generateTypeScriptContent } from './utils/file-parser.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
googleTranslateService,
|
|
14
|
+
getTargetLanguage,
|
|
15
|
+
getLanguageDisplayName,
|
|
16
|
+
} from '@umituz/react-native-google-translate/services';
|
|
13
17
|
import { setupLanguages } from './setup-languages.js';
|
|
14
18
|
import { syncTranslations } from './sync-translations.js';
|
|
15
19
|
|
|
@@ -104,100 +108,6 @@ async function translateMissing(targetDir, srcDir) {
|
|
|
104
108
|
console.log('\n✅ All translations completed!');
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
// Helper functions (can be replaced with package imports later)
|
|
108
|
-
const LANGUAGE_MAP = {
|
|
109
|
-
'ar-SA': 'ar',
|
|
110
|
-
'bg-BG': 'bg',
|
|
111
|
-
'cs-CZ': 'cs',
|
|
112
|
-
'da-DK': 'da',
|
|
113
|
-
'de-DE': 'de',
|
|
114
|
-
'el-GR': 'el',
|
|
115
|
-
'en-AU': 'en',
|
|
116
|
-
'en-CA': 'en',
|
|
117
|
-
'en-GB': 'en',
|
|
118
|
-
'es-ES': 'es',
|
|
119
|
-
'es-MX': 'es',
|
|
120
|
-
'fi-FI': 'fi',
|
|
121
|
-
'fr-CA': 'fr',
|
|
122
|
-
'fr-FR': 'fr',
|
|
123
|
-
'hi-IN': 'hi',
|
|
124
|
-
'hr-HR': 'hr',
|
|
125
|
-
'hu-HU': 'hu',
|
|
126
|
-
'id-ID': 'id',
|
|
127
|
-
'it-IT': 'it',
|
|
128
|
-
'ja-JP': 'ja',
|
|
129
|
-
'ko-KR': 'ko',
|
|
130
|
-
'ms-MY': 'ms',
|
|
131
|
-
'nl-NL': 'nl',
|
|
132
|
-
'no-NO': 'no',
|
|
133
|
-
'pl-PL': 'pl',
|
|
134
|
-
'pt-BR': 'pt',
|
|
135
|
-
'pt-PT': 'pt',
|
|
136
|
-
'ro-RO': 'ro',
|
|
137
|
-
'ru-RU': 'ru',
|
|
138
|
-
'sk-SK': 'sk',
|
|
139
|
-
'sl-SI': 'sl',
|
|
140
|
-
'sv-SE': 'sv',
|
|
141
|
-
'th-TH': 'th',
|
|
142
|
-
'tl-PH': 'tl',
|
|
143
|
-
'tr-TR': 'tr',
|
|
144
|
-
'uk-UA': 'uk',
|
|
145
|
-
'vi-VN': 'vi',
|
|
146
|
-
'zh-CN': 'zh-CN',
|
|
147
|
-
'zh-TW': 'zh-TW',
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const LANGUAGE_NAMES = {
|
|
151
|
-
'ar-SA': 'Arabic (Saudi Arabia)',
|
|
152
|
-
'bg-BG': 'Bulgarian',
|
|
153
|
-
'cs-CZ': 'Czech',
|
|
154
|
-
'da-DK': 'Danish',
|
|
155
|
-
'de-DE': 'German',
|
|
156
|
-
'el-GR': 'Greek',
|
|
157
|
-
'en-AU': 'English (Australia)',
|
|
158
|
-
'en-CA': 'English (Canada)',
|
|
159
|
-
'en-GB': 'English (UK)',
|
|
160
|
-
'en-US': 'English (US)',
|
|
161
|
-
'es-ES': 'Spanish (Spain)',
|
|
162
|
-
'es-MX': 'Spanish (Mexico)',
|
|
163
|
-
'fi-FI': 'Finnish',
|
|
164
|
-
'fr-CA': 'French (Canada)',
|
|
165
|
-
'fr-FR': 'French (France)',
|
|
166
|
-
'hi-IN': 'Hindi',
|
|
167
|
-
'hr-HR': 'Croatian',
|
|
168
|
-
'hu-HU': 'Hungarian',
|
|
169
|
-
'id-ID': 'Indonesian',
|
|
170
|
-
'it-IT': 'Italian',
|
|
171
|
-
'ja-JP': 'Japanese',
|
|
172
|
-
'ko-KR': 'Korean',
|
|
173
|
-
'ms-MY': 'Malay',
|
|
174
|
-
'nl-NL': 'Dutch',
|
|
175
|
-
'no-NO': 'Norwegian',
|
|
176
|
-
'pl-PL': 'Polish',
|
|
177
|
-
'pt-BR': 'Portuguese (Brazil)',
|
|
178
|
-
'pt-PT': 'Portuguese (Portugal)',
|
|
179
|
-
'ro-RO': 'Romanian',
|
|
180
|
-
'ru-RU': 'Russian',
|
|
181
|
-
'sk-SK': 'Slovak',
|
|
182
|
-
'sl-SI': 'Slovenian',
|
|
183
|
-
'sv-SE': 'Swedish',
|
|
184
|
-
'th-TH': 'Thai',
|
|
185
|
-
'tl-PH': 'Tagalog',
|
|
186
|
-
'tr-TR': 'Turkish',
|
|
187
|
-
'uk-UA': 'Ukrainian',
|
|
188
|
-
'vi-VN': 'Vietnamese',
|
|
189
|
-
'zh-CN': 'Chinese (Simplified)',
|
|
190
|
-
'zh-TW': 'Chinese (Traditional)',
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
function getTargetLanguage(langCode) {
|
|
194
|
-
return LANGUAGE_MAP[langCode];
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function getLanguageDisplayName(code) {
|
|
198
|
-
return LANGUAGE_NAMES[code] || code;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
111
|
const isMainModule = import.meta.url.endsWith('translate-missing.js');
|
|
202
112
|
if (isMainModule) {
|
|
203
113
|
const args = process.argv.slice(2).filter(arg => !arg.startsWith('--'));
|
|
@@ -1,116 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Translation Configuration
|
|
3
|
-
*
|
|
3
|
+
* Re-exports from @umituz/react-native-google-translate package
|
|
4
|
+
* This file provides backward compatibility for existing imports
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'en-GB': 'en',
|
|
16
|
-
'es-ES': 'es',
|
|
17
|
-
'es-MX': 'es',
|
|
18
|
-
'fi-FI': 'fi',
|
|
19
|
-
'fr-CA': 'fr',
|
|
20
|
-
'fr-FR': 'fr',
|
|
21
|
-
'hi-IN': 'hi',
|
|
22
|
-
'hr-HR': 'hr',
|
|
23
|
-
'hu-HU': 'hu',
|
|
24
|
-
'id-ID': 'id',
|
|
25
|
-
'it-IT': 'it',
|
|
26
|
-
'ja-JP': 'ja',
|
|
27
|
-
'ko-KR': 'ko',
|
|
28
|
-
'ms-MY': 'ms',
|
|
29
|
-
'nl-NL': 'nl',
|
|
30
|
-
'no-NO': 'no',
|
|
31
|
-
'pl-PL': 'pl',
|
|
32
|
-
'pt-BR': 'pt',
|
|
33
|
-
'pt-PT': 'pt',
|
|
34
|
-
'ro-RO': 'ro',
|
|
35
|
-
'ru-RU': 'ru',
|
|
36
|
-
'sk-SK': 'sk',
|
|
37
|
-
'sl-SI': 'sl',
|
|
38
|
-
'sv-SE': 'sv',
|
|
39
|
-
'th-TH': 'th',
|
|
40
|
-
'tl-PH': 'tl',
|
|
41
|
-
'tr-TR': 'tr',
|
|
42
|
-
'uk-UA': 'uk',
|
|
43
|
-
'vi-VN': 'vi',
|
|
44
|
-
'zh-CN': 'zh-CN',
|
|
45
|
-
'zh-TW': 'zh-TW',
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const SKIP_WORDS = new Set([
|
|
49
|
-
'Google',
|
|
50
|
-
'Apple',
|
|
51
|
-
'Facebook',
|
|
52
|
-
'Instagram',
|
|
53
|
-
'Twitter',
|
|
54
|
-
'YouTube',
|
|
55
|
-
'WhatsApp',
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
|
-
export const LANGUAGE_NAMES = {
|
|
59
|
-
'ar-SA': 'Arabic (Saudi Arabia)',
|
|
60
|
-
'bg-BG': 'Bulgarian',
|
|
61
|
-
'cs-CZ': 'Czech',
|
|
62
|
-
'da-DK': 'Danish',
|
|
63
|
-
'de-DE': 'German',
|
|
64
|
-
'el-GR': 'Greek',
|
|
65
|
-
'en-AU': 'English (Australia)',
|
|
66
|
-
'en-CA': 'English (Canada)',
|
|
67
|
-
'en-GB': 'English (UK)',
|
|
68
|
-
'en-US': 'English (US)',
|
|
69
|
-
'es-ES': 'Spanish (Spain)',
|
|
70
|
-
'es-MX': 'Spanish (Mexico)',
|
|
71
|
-
'fi-FI': 'Finnish',
|
|
72
|
-
'fr-CA': 'French (Canada)',
|
|
73
|
-
'fr-FR': 'French (France)',
|
|
74
|
-
'hi-IN': 'Hindi',
|
|
75
|
-
'hr-HR': 'Croatian',
|
|
76
|
-
'hu-HU': 'Hungarian',
|
|
77
|
-
'id-ID': 'Indonesian',
|
|
78
|
-
'it-IT': 'Italian',
|
|
79
|
-
'ja-JP': 'Japanese',
|
|
80
|
-
'ko-KR': 'Korean',
|
|
81
|
-
'ms-MY': 'Malay',
|
|
82
|
-
'nl-NL': 'Dutch',
|
|
83
|
-
'no-NO': 'Norwegian',
|
|
84
|
-
'pl-PL': 'Polish',
|
|
85
|
-
'pt-BR': 'Portuguese (Brazil)',
|
|
86
|
-
'pt-PT': 'Portuguese (Portugal)',
|
|
87
|
-
'ro-RO': 'Romanian',
|
|
88
|
-
'ru-RU': 'Russian',
|
|
89
|
-
'sk-SK': 'Slovak',
|
|
90
|
-
'sl-SI': 'Slovenian',
|
|
91
|
-
'sv-SE': 'Swedish',
|
|
92
|
-
'th-TH': 'Thai',
|
|
93
|
-
'tl-PH': 'Tagalog',
|
|
94
|
-
'tr-TR': 'Turkish',
|
|
95
|
-
'uk-UA': 'Ukrainian',
|
|
96
|
-
'vi-VN': 'Vietnamese',
|
|
97
|
-
'zh-CN': 'Chinese (Simplified)',
|
|
98
|
-
'zh-TW': 'Chinese (Traditional)',
|
|
99
|
-
};
|
|
7
|
+
export {
|
|
8
|
+
LANGUAGE_MAP,
|
|
9
|
+
SKIP_WORDS,
|
|
10
|
+
LANGUAGE_NAMES,
|
|
11
|
+
getTargetLanguage,
|
|
12
|
+
isEnglishVariant,
|
|
13
|
+
getLanguageDisplayName,
|
|
14
|
+
shouldSkipWord,
|
|
15
|
+
} from '@umituz/react-native-google-translate/services';
|
|
100
16
|
|
|
17
|
+
// Backward compatibility alias
|
|
101
18
|
export function getLangDisplayName(code) {
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function getTargetLanguage(langCode) {
|
|
106
|
-
return LANGUAGE_MAP[langCode];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function shouldSkipWord(word) {
|
|
110
|
-
return SKIP_WORDS.has(word);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function isEnglishVariant(langCode) {
|
|
114
|
-
const targetLang = LANGUAGE_MAP[langCode];
|
|
115
|
-
return targetLang === 'en';
|
|
19
|
+
return getLanguageDisplayName(code);
|
|
116
20
|
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Translation Utilities
|
|
3
|
-
* Handles call to translation APIs
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { getTargetLanguage, shouldSkipWord } from './translation-config.js';
|
|
7
|
-
|
|
8
|
-
let lastCallTime = 0;
|
|
9
|
-
const MIN_DELAY = 100; // ms
|
|
10
|
-
|
|
11
|
-
async function translateText(text, targetLang) {
|
|
12
|
-
if (!text || typeof text !== 'string') return text;
|
|
13
|
-
if (shouldSkipWord(text)) return text;
|
|
14
|
-
|
|
15
|
-
// Rate limiting
|
|
16
|
-
const now = Date.now();
|
|
17
|
-
const waitTime = Math.max(0, MIN_DELAY - (now - lastCallTime));
|
|
18
|
-
if (waitTime > 0) await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
19
|
-
lastCallTime = Date.now();
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const encodedText = encodeURIComponent(text);
|
|
23
|
-
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodedText}`;
|
|
24
|
-
|
|
25
|
-
const response = await fetch(url);
|
|
26
|
-
if (!response.ok) return text;
|
|
27
|
-
|
|
28
|
-
const data = await response.json();
|
|
29
|
-
return data && data[0] && data[0][0] && data[0][0][0] ? data[0][0][0] : text;
|
|
30
|
-
} catch (error) {
|
|
31
|
-
return text;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function needsTranslation(value, enValue) {
|
|
36
|
-
if (typeof enValue !== 'string' || !enValue.trim()) return false;
|
|
37
|
-
if (shouldSkipWord(enValue)) return false;
|
|
38
|
-
|
|
39
|
-
// CRITICAL OPTIMIZATION: If enValue is a technical key (e.g. "scenario.xxx.title"),
|
|
40
|
-
// skip translating it to other languages. We only translate REAL English content.
|
|
41
|
-
const isTechnicalKey = enValue.includes('.') && !enValue.includes(' ');
|
|
42
|
-
if (isTechnicalKey) return false;
|
|
43
|
-
|
|
44
|
-
// If value is missing or same as English, it needs translation
|
|
45
|
-
if (!value || typeof value !== 'string') return true;
|
|
46
|
-
|
|
47
|
-
if (value === enValue) {
|
|
48
|
-
const isSingleWord = !enValue.includes(' ') && enValue.length < 20;
|
|
49
|
-
return !isSingleWord;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Detect outdated template patterns (e.g., {{appName}}, {{variable}})
|
|
53
|
-
if (value && typeof value === 'string') {
|
|
54
|
-
const hasTemplatePattern = value.includes('{{') && value.includes('}}');
|
|
55
|
-
if (hasTemplatePattern && !enValue.includes('{{')) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0, checked: 0, translatedKeys: [] }) {
|
|
64
|
-
const keys = Object.keys(enObj);
|
|
65
|
-
|
|
66
|
-
if (!stats.translatedKeys) stats.translatedKeys = [];
|
|
67
|
-
|
|
68
|
-
for (const key of keys) {
|
|
69
|
-
const enValue = enObj[key];
|
|
70
|
-
const targetValue = targetObj[key];
|
|
71
|
-
const currentPath = path ? `${path}.${key}` : key;
|
|
72
|
-
|
|
73
|
-
if (typeof enValue === 'object' && enValue !== null) {
|
|
74
|
-
if (!targetObj[key] || typeof targetObj[key] !== 'object') targetObj[key] = {};
|
|
75
|
-
await translateObject(enValue, targetObj[key], targetLang, currentPath, stats);
|
|
76
|
-
} else if (typeof enValue === 'string') {
|
|
77
|
-
stats.checked++;
|
|
78
|
-
if (needsTranslation(targetValue, enValue)) {
|
|
79
|
-
// Show progress for translations
|
|
80
|
-
process.stdout.write(` \r Progress: ${stats.checked} keys checked, ${stats.count} translated...`);
|
|
81
|
-
|
|
82
|
-
const translated = await translateText(enValue, targetLang);
|
|
83
|
-
if (translated !== enValue) {
|
|
84
|
-
targetObj[key] = translated;
|
|
85
|
-
stats.count++;
|
|
86
|
-
stats.translatedKeys.push({ key: currentPath, from: enValue, to: translated });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|