@umituz/react-native-settings 5.4.21 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "5.4.21",
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",
@@ -124,6 +124,7 @@
124
124
  "@react-navigation/stack": ">=6.0.0",
125
125
  "@tanstack/react-query": ">=5.0.0",
126
126
  "@umituz/react-native-design-system": "*",
127
+ "@umituz/react-native-google-translate": "*",
127
128
  "expo": ">=54.0.0",
128
129
  "expo-constants": ">=55.0.0",
129
130
  "expo-device": ">=6.0.0",
@@ -162,6 +163,7 @@
162
163
  "@umituz/react-native-auth": "^4.3.39",
163
164
  "@umituz/react-native-design-system": "^4.27.0",
164
165
  "@umituz/react-native-firebase": "^2.4.55",
166
+ "@umituz/react-native-google-translate": "^1.0.0",
165
167
  "@umituz/react-native-sentry": "latest",
166
168
  "eslint": "^8.57.0",
167
169
  "eslint-plugin-react": "^7.37.5",
@@ -16,8 +16,10 @@
16
16
  */
17
17
 
18
18
  import React, { memo } from 'react';
19
- import { Modal, View, StyleSheet } from 'react-native';
19
+ import { Modal, View } from 'react-native';
20
20
  import { AIConsentScreen, type AIProvider } from '../screens/AIConsentScreen';
21
+ import { useThemedStyleSheet } from '@umituz/react-native-design-system/theme';
22
+ import type { Theme } from '@umituz/react-native-design-system/theme';
21
23
 
22
24
  export interface AIConsentModalProps {
23
25
  visible: boolean;
@@ -34,6 +36,8 @@ export const AIConsentModal: React.FC<AIConsentModalProps> = memo(({
34
36
  providers,
35
37
  customMessage,
36
38
  }) => {
39
+ const styles = useThemedStyleSheet(createStyles);
40
+
37
41
  return (
38
42
  <Modal
39
43
  visible={visible}
@@ -56,9 +60,9 @@ export const AIConsentModal: React.FC<AIConsentModalProps> = memo(({
56
60
 
57
61
  AIConsentModal.displayName = 'AIConsentModal';
58
62
 
59
- const styles = StyleSheet.create({
63
+ const createStyles = (theme: Theme) => ({
60
64
  container: {
61
65
  flex: 1,
62
- backgroundColor: '#FFFFFF',
66
+ backgroundColor: theme.colors.backgroundPrimary,
63
67
  },
64
68
  });
@@ -15,7 +15,6 @@
15
15
  import React, { memo } from 'react';
16
16
  import {
17
17
  View,
18
- StyleSheet,
19
18
  ScrollView,
20
19
  TouchableOpacity,
21
20
  } from 'react-native';
@@ -31,7 +30,8 @@ import {
31
30
  NavigationHeader,
32
31
  useAppNavigation,
33
32
  } from '@umituz/react-native-design-system/molecules';
34
- import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
33
+ import { useThemedStyleSheet } from '@umituz/react-native-design-system/theme';
34
+ import type { Theme } from '@umituz/react-native-design-system/theme';
35
35
 
36
36
  export interface AIProvider {
37
37
  name: string;
@@ -85,7 +85,7 @@ export const AIConsentScreen: React.FC<AIConsentScreenProps> = memo(({
85
85
  standalone = false,
86
86
  }) => {
87
87
  const navigation = useAppNavigation();
88
- const tokens = useAppDesignTokens();
88
+ const styles = useThemedStyleSheet(createStyles);
89
89
  const [loading, setLoading] = React.useState(false);
90
90
 
91
91
  // Get params from route or use props
@@ -289,7 +289,7 @@ export const AIConsentScreen: React.FC<AIConsentScreenProps> = memo(({
289
289
 
290
290
  AIConsentScreen.displayName = 'AIConsentScreen';
291
291
 
292
- const styles = StyleSheet.create({
292
+ const createStyles = (theme: Theme) => ({
293
293
  scrollView: {
294
294
  flex: 1,
295
295
  },
@@ -300,32 +300,32 @@ const styles = StyleSheet.create({
300
300
  fontSize: 15,
301
301
  lineHeight: 22,
302
302
  marginBottom: 24,
303
- color: '#374151',
303
+ color: theme.colors.textSecondary,
304
304
  },
305
305
  section: {
306
306
  marginBottom: 24,
307
307
  padding: 16,
308
- backgroundColor: '#F9FAFB',
308
+ backgroundColor: theme.colors.surfaceVariant,
309
309
  borderRadius: 12,
310
310
  },
311
311
  sectionTitle: {
312
312
  fontSize: 17,
313
313
  fontWeight: '600',
314
- color: '#111827',
314
+ color: theme.colors.textPrimary,
315
315
  marginBottom: 12,
316
316
  },
317
317
  providerList: {
318
318
  gap: 12,
319
319
  },
320
320
  providerItem: {
321
- backgroundColor: '#FFFFFF',
321
+ backgroundColor: theme.colors.surface,
322
322
  padding: 12,
323
323
  borderRadius: 8,
324
324
  borderWidth: 1,
325
- borderColor: '#E5E7EB',
325
+ borderColor: theme.colors.borderLight,
326
326
  },
327
327
  providerHeader: {
328
- flexDirection: 'row',
328
+ flexDirection: 'row' as const,
329
329
  justifyContent: 'space-between',
330
330
  alignItems: 'center',
331
331
  marginBottom: 4,
@@ -333,26 +333,26 @@ const styles = StyleSheet.create({
333
333
  providerName: {
334
334
  fontSize: 15,
335
335
  fontWeight: '600',
336
- color: '#111827',
336
+ color: theme.colors.textPrimary,
337
337
  },
338
338
  privacyLink: {
339
339
  fontSize: 13,
340
- color: '#3B82F6',
340
+ color: theme.colors.primary,
341
341
  },
342
342
  providerPurpose: {
343
343
  fontSize: 14,
344
- color: '#6B7280',
344
+ color: theme.colors.textTertiary,
345
345
  },
346
346
  bullet: {
347
347
  fontSize: 15,
348
- color: '#374151',
348
+ color: theme.colors.textSecondary,
349
349
  lineHeight: 22,
350
350
  marginLeft: 8,
351
351
  },
352
352
  note: {
353
353
  fontSize: 13,
354
- color: '#6B7280',
355
- fontStyle: 'italic',
354
+ color: theme.colors.textTertiary,
355
+ fontStyle: 'italic' as const,
356
356
  marginLeft: 8,
357
357
  marginTop: 4,
358
358
  },
@@ -362,15 +362,15 @@ const styles = StyleSheet.create({
362
362
  },
363
363
  linkText: {
364
364
  fontSize: 15,
365
- color: '#3B82F6',
365
+ color: theme.colors.primary,
366
366
  fontWeight: '500',
367
367
  },
368
368
  declarationSection: {
369
- flexDirection: 'row',
369
+ flexDirection: 'row' as const,
370
370
  alignItems: 'flex-start',
371
371
  marginTop: 8,
372
372
  padding: 12,
373
- backgroundColor: '#EFF6FF',
373
+ backgroundColor: theme.colors.primaryBackground || 'rgba(90, 175, 127, 0.15)',
374
374
  borderRadius: 8,
375
375
  },
376
376
  checkbox: {
@@ -383,26 +383,26 @@ const styles = StyleSheet.create({
383
383
  width: 24,
384
384
  height: 24,
385
385
  borderRadius: 4,
386
- backgroundColor: '#3B82F6',
387
- alignItems: 'center',
388
- justifyContent: 'center',
386
+ backgroundColor: theme.colors.primary,
387
+ alignItems: 'center' as const,
388
+ justifyContent: 'center' as const,
389
389
  },
390
390
  checkmark: {
391
- color: '#FFFFFF',
391
+ color: theme.colors.onPrimary || '#FFFFFF',
392
392
  fontSize: 16,
393
393
  fontWeight: '700',
394
394
  },
395
395
  declarationText: {
396
396
  flex: 1,
397
397
  fontSize: 14,
398
- color: '#1E3A8A',
398
+ color: theme.colors.textPrimary,
399
399
  lineHeight: 20,
400
400
  },
401
401
  footer: {
402
- flexDirection: 'row',
402
+ flexDirection: 'row' as const,
403
403
  padding: 16,
404
404
  borderTopWidth: 1,
405
- borderTopColor: '#E5E7EB',
405
+ borderTopColor: theme.colors.borderLight,
406
406
  gap: 12,
407
407
  },
408
408
  declineButton: {
@@ -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 = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
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: "rgba(255,255,255,0.02)",
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 { achievementItemStyles as styles } from "./styles/achievementItemStyles";
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.background }]}>
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 { styles } from "./styles";
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 styles = StyleSheet.create({
8
- container: {
9
- flex: 1,
10
- },
11
- scrollView: {
12
- flex: 1,
13
- },
14
- scrollContent: {
15
- padding: 16,
16
- paddingBottom: 32,
17
- },
18
- header: {
19
- marginBottom: 20,
20
- },
21
- title: {
22
- fontSize: 28,
23
- fontWeight: "bold",
24
- },
25
- section: {
26
- marginBottom: 24,
27
- },
28
- sectionTitle: {
29
- fontSize: 18,
30
- fontWeight: "600",
31
- marginBottom: 12,
32
- },
33
- statsGrid: {
34
- flexDirection: "row",
35
- flexWrap: "wrap",
36
- gap: 12,
37
- },
38
- emptyText: {
39
- fontSize: 14,
40
- textAlign: "center",
41
- paddingVertical: 20,
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 achievementItemStyles = StyleSheet.create({
8
- container: {
9
- flexDirection: "row",
10
- alignItems: "center",
11
- padding: 12,
12
- borderRadius: 12,
13
- marginBottom: 8,
14
- },
15
- iconContainer: {
16
- width: 48,
17
- height: 48,
18
- borderRadius: 24,
19
- alignItems: "center",
20
- justifyContent: "center",
21
- marginRight: 12,
22
- },
23
- content: {
24
- flex: 1,
25
- },
26
- header: {
27
- flexDirection: "row",
28
- alignItems: "center",
29
- justifyContent: "space-between",
30
- marginBottom: 4,
31
- },
32
- title: {
33
- fontSize: 16,
34
- fontWeight: "600",
35
- flex: 1,
36
- },
37
- checkmark: {
38
- width: 20,
39
- height: 20,
40
- borderRadius: 10,
41
- alignItems: "center",
42
- justifyContent: "center",
43
- marginLeft: 8,
44
- },
45
- checkmarkText: {
46
- fontSize: 12,
47
- fontWeight: "bold",
48
- },
49
- description: {
50
- fontSize: 13,
51
- lineHeight: 18,
52
- },
53
- progressContainer: {
54
- marginTop: 8,
55
- flexDirection: "row",
56
- alignItems: "center",
57
- gap: 8,
58
- },
59
- progressBar: {
60
- flex: 1,
61
- height: 4,
62
- borderRadius: 2,
63
- overflow: "hidden",
64
- },
65
- progressFill: {
66
- height: "100%",
67
- borderRadius: 2,
68
- },
69
- progressText: {
70
- fontSize: 11,
71
- minWidth: 40,
72
- textAlign: "right",
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 styles = StyleSheet.create({
12
- container: {
13
- flexDirection: 'row',
14
- alignItems: 'center',
15
- gap: 8,
16
- paddingHorizontal: 4,
17
- paddingVertical: 4,
18
- },
19
- disabled: {
20
- opacity: 0.5,
21
- },
22
- flag: {
23
- fontSize: DEFAULT_CONFIG.defaultIconSize,
24
- textAlign: 'center',
25
- },
26
- languageName: {
27
- fontSize: 14,
28
- fontWeight: '600',
29
- textAlign: 'center',
30
- },
31
- icon: {
32
- fontSize: DEFAULT_CONFIG.defaultIconSize,
33
- textAlign: 'center',
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 { styles, DEFAULT_CONFIG_VALUES } from './LanguageSwitcher.styles';
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
- { gap: tokens.spacing.xs },
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 styles = StyleSheet.create({
8
- languageItem: {
9
- flexDirection: 'row',
10
- alignItems: 'center',
11
- justifyContent: 'space-between',
12
- borderWidth: 1,
13
- },
14
- selectedLanguageItem: {
15
- borderWidth: 2,
16
- },
17
- languageContent: {
18
- flexDirection: 'row',
19
- alignItems: 'center',
20
- flex: 1,
21
- },
22
- flag: {
23
- fontSize: 24,
24
- marginRight: 16,
25
- },
26
- languageText: {
27
- flex: 1,
28
- flexShrink: 1,
29
- },
30
- nativeName: {
31
- // Styling moved to themedStyles in component for token support
32
- },
33
- languageName: {
34
- // Styling moved to themedStyles in component for token support
35
- },
36
- checkIcon: {
37
- // Replaced by AtomicIcon
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 { styles } from './LanguageItem.styles';
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
- themedStyles.languageItem,
84
- isSelected && themedStyles.selectedLanguageItem,
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={[themedStyles.nativeName, { fontWeight: '600' }, customStyles?.nativeName]}
95
+ style={[styles.nativeName, { fontWeight: '600' }, customStyles?.nativeName]}
116
96
  >
117
97
  {item.nativeName}
118
98
  </AtomicText>
119
- <AtomicText
120
- type="labelMedium"
121
- style={[themedStyles.languageName, customStyles?.nativeName]}
99
+ <AtomicText
100
+ type="labelMedium"
101
+ style={[styles.languageName, customStyles?.nativeName]}
122
102
  >
123
103
  {item.name}
124
104
  </AtomicText>
@@ -3,17 +3,28 @@
3
3
  /**
4
4
  * Translate Missing Script
5
5
  * Automatically translates missing strings using Google Translate
6
+ * Refactored to use @umituz/react-native-google-translate package
6
7
  */
7
8
 
8
9
  import fs from 'fs';
9
10
  import path from 'path';
10
11
  import { parseTypeScriptFile, generateTypeScriptContent } from './utils/file-parser.js';
11
- import { getTargetLanguage, getLangDisplayName } from './utils/translation-config.js';
12
- import { translateObject } from './utils/translator.js';
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
 
16
20
  async function translateMissing(targetDir, srcDir) {
21
+ // Initialize the translation service
22
+ googleTranslateService.initialize({
23
+ minDelay: 100,
24
+ maxRetries: 3,
25
+ timeout: 10000,
26
+ });
27
+
17
28
  const localesDir = path.resolve(process.cwd(), targetDir);
18
29
  const enUSPath = path.join(localesDir, 'en-US.ts');
19
30
 
@@ -41,27 +52,44 @@ async function translateMissing(targetDir, srcDir) {
41
52
 
42
53
  for (const file of files) {
43
54
  const langCode = file.replace('.ts', '');
44
- const targetLang = getTargetLanguage(langCode);
45
- const langName = getLangDisplayName(langCode);
46
55
 
47
- if (!targetLang || targetLang === 'en') continue;
56
+ // Skip English variants
57
+ const targetLang = getTargetLanguage(langCode);
58
+ if (!targetLang || targetLang === 'en') {
59
+ console.log(`⏭️ Skipping ${langCode} (English variant)`);
60
+ continue;
61
+ }
48
62
 
63
+ const langName = getLanguageDisplayName(langCode);
49
64
  console.log(`🌍 Translating ${langCode} (${langName})...`);
50
65
 
51
66
  const targetPath = path.join(localesDir, file);
52
67
  const target = parseTypeScriptFile(targetPath);
53
68
 
54
- const stats = { count: 0, checked: 0, translatedKeys: [] };
55
- await translateObject(enUS, target, targetLang, '', stats);
69
+ const stats = {
70
+ totalCount: 0,
71
+ successCount: 0,
72
+ failureCount: 0,
73
+ skippedCount: 0,
74
+ translatedKeys: [],
75
+ };
76
+
77
+ await googleTranslateService.translateObject(
78
+ enUS,
79
+ target,
80
+ targetLang,
81
+ '',
82
+ stats
83
+ );
56
84
 
57
85
  // Clear progress line
58
86
  process.stdout.write('\r' + ' '.repeat(80) + '\r');
59
87
 
60
- if (stats.count > 0) {
88
+ if (stats.successCount > 0) {
61
89
  const content = generateTypeScriptContent(target, langCode);
62
90
  fs.writeFileSync(targetPath, content);
63
91
 
64
- console.log(` ✅ Successfully translated ${stats.count} keys:`);
92
+ console.log(` ✅ Successfully translated ${stats.successCount} keys:`);
65
93
 
66
94
  // Detailed logging of translated keys
67
95
  const displayCount = Math.min(stats.translatedKeys.length, 15);
@@ -1,116 +1,20 @@
1
1
  /**
2
2
  * Translation Configuration
3
- * Language mappings and constants for translation system
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 const LANGUAGE_MAP = {
7
- 'ar-SA': 'ar',
8
- 'bg-BG': 'bg',
9
- 'cs-CZ': 'cs',
10
- 'da-DK': 'da',
11
- 'de-DE': 'de',
12
- 'el-GR': 'el',
13
- 'en-AU': 'en',
14
- 'en-CA': 'en',
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 LANGUAGE_NAMES[code] || code;
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
- }