@umituz/react-native-settings 4.23.68 β 4.23.70
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 +2 -1
- package/src/domains/about/presentation/hooks/useAboutInfo.ts +0 -1
- package/src/domains/appearance/data/colorPalettes.ts +1 -6
- package/src/domains/appearance/hooks/useAppearance.ts +5 -3
- package/src/domains/appearance/hooks/useAppearanceActions.ts +2 -1
- package/src/domains/appearance/index.ts +2 -1
- package/src/domains/appearance/presentation/components/ColorPicker.tsx +1 -1
- package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +2 -1
- package/src/domains/appearance/types/index.ts +5 -2
- package/src/domains/disclaimer/presentation/components/DisclaimerModal.tsx +1 -1
- package/src/domains/feedback/presentation/components/FeedbackForm.tsx +48 -12
- package/src/domains/feedback/presentation/components/FeedbackModal.tsx +1 -1
- package/src/domains/gamification/components/GamificationScreen/index.tsx +83 -4
- package/src/domains/gamification/components/GamificationScreen/types.ts +5 -0
- package/src/domains/gamification/components/index.ts +0 -1
- package/src/domains/gamification/index.ts +0 -1
- package/src/domains/gamification/utils/calculations.ts +1 -1
- package/src/domains/legal/presentation/components/LegalItem.tsx +1 -1
- package/src/domains/localization/index.ts +0 -2
- package/src/domains/localization/infrastructure/components/useLanguageSwitcher.ts +18 -1
- package/src/domains/localization/infrastructure/config/languages.ts +0 -9
- package/src/domains/localization/infrastructure/hooks/TranslationHook.ts +22 -24
- package/src/domains/localization/infrastructure/storage/LocalizationStore.ts +163 -111
- package/src/domains/notifications/infrastructure/services/NotificationManager.ts +1 -3
- package/src/domains/notifications/infrastructure/services/NotificationScheduler.ts +2 -2
- package/src/domains/notifications/quietHours/presentation/components/QuietHoursCard.tsx +0 -1
- package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +60 -25
- package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +2 -2
- package/src/domains/rating/presentation/components/RatingPromptModal.tsx +1 -1
- package/src/domains/rating/presentation/components/StarRating.tsx +1 -1
- package/src/presentation/components/SettingsErrorBoundary.tsx +14 -18
- package/src/presentation/components/SettingsFooter.tsx +1 -3
- package/src/presentation/components/SettingsItemCard.tsx +27 -6
- package/src/presentation/hooks/queries/useSettingsQuery.ts +0 -1
- package/src/presentation/hooks/useSettings.ts +8 -0
- package/src/presentation/hooks/useSettingsScreenConfig.ts +3 -3
- package/src/presentation/navigation/SettingsStackNavigator.tsx +6 -8
- package/src/presentation/screens/SettingsScreen.tsx +6 -3
- package/src/presentation/screens/components/SettingsContent.tsx +16 -1
- package/src/domains/appearance/hooks/index.ts +0 -6
- package/src/domains/appearance/presentation/screens/index.ts +0 -2
- package/src/domains/gamification/components/GamificationScreenWrapper.tsx +0 -65
- package/src/presentation/navigation/components/wrappers/AboutScreenWrapper.tsx +0 -14
- package/src/presentation/navigation/components/wrappers/LegalScreenWrapper.tsx +0 -50
- package/src/presentation/navigation/components/wrappers/SettingsScreenWrapper.tsx +0 -44
- package/src/presentation/navigation/components/wrappers/index.ts +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.70",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"@umituz/react-native-sentry": "*",
|
|
82
82
|
"eslint": "^8.57.0",
|
|
83
83
|
"eslint-plugin-react": "^7.37.5",
|
|
84
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
84
85
|
"eslint-plugin-react-native": "^5.0.0",
|
|
85
86
|
"expo-apple-authentication": "^8.0.8",
|
|
86
87
|
"expo-auth-session": "^5.0.0",
|
|
@@ -9,7 +9,6 @@ import { AboutRepository } from '../../infrastructure/repositories/AboutReposito
|
|
|
9
9
|
import type { UseAboutInfoOptions, UseAboutInfoReturn } from './useAboutInfo.types';
|
|
10
10
|
import {
|
|
11
11
|
setErrorIfMounted,
|
|
12
|
-
setLoadingIfMounted,
|
|
13
12
|
initializeAppInfo,
|
|
14
13
|
updateAppInfoConfig,
|
|
15
14
|
updateAppInfoPartial,
|
|
@@ -86,9 +86,4 @@ export const generateColorPalette = (
|
|
|
86
86
|
name: "custom",
|
|
87
87
|
colors,
|
|
88
88
|
};
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Legacy exports for backward compatibility
|
|
92
|
-
export const PRIMARY_COLORS = DEFAULT_PRIMARY_COLORS.colors;
|
|
93
|
-
export const SECONDARY_COLORS = DEFAULT_SECONDARY_COLORS.colors;
|
|
94
|
-
export const ACCENT_COLORS = DEFAULT_ACCENT_COLORS.colors;
|
|
89
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { useTheme, type ThemeMode, type CustomThemeColors } from "@umituz/react-native-design-system";
|
|
1
|
+
import { useTheme, type ThemeMode as BaseThemeMode, type CustomThemeColors } from "@umituz/react-native-design-system";
|
|
2
|
+
import type { ThemeMode } from "../types";
|
|
2
3
|
|
|
3
4
|
export const useAppearance = () => {
|
|
4
5
|
const { themeMode, customColors, defaultColors, isInitialized, setThemeMode, setCustomColors, resetToDefaults } = useTheme();
|
|
5
6
|
|
|
6
7
|
return {
|
|
7
|
-
themeMode,
|
|
8
|
+
themeMode: themeMode as ThemeMode,
|
|
8
9
|
customColors,
|
|
9
10
|
defaultColors,
|
|
10
11
|
isLoading: !isInitialized,
|
|
11
12
|
setThemeMode: (mode: ThemeMode) => {
|
|
12
|
-
|
|
13
|
+
// Cast to base ThemeMode since design system doesn't support 'auto'
|
|
14
|
+
void setThemeMode(mode as BaseThemeMode);
|
|
13
15
|
},
|
|
14
16
|
setCustomColors: (colors: CustomThemeColors) => {
|
|
15
17
|
void setCustomColors(colors);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { useAppearance } from "./useAppearance";
|
|
3
|
-
import type { CustomThemeColors
|
|
3
|
+
import type { CustomThemeColors } from "@umituz/react-native-design-system";
|
|
4
|
+
import type { ThemeMode } from "../types";
|
|
4
5
|
|
|
5
6
|
export const useAppearanceActions = () => {
|
|
6
7
|
const { themeMode, customColors, setThemeMode, setCustomColors, reset } = useAppearance();
|
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
export * from './presentation/screens/AppearanceScreen';
|
|
7
7
|
export * from './presentation/components';
|
|
8
|
-
export
|
|
8
|
+
export { useAppearance } from './hooks/useAppearance';
|
|
9
|
+
export { useAppearanceActions } from './hooks/useAppearanceActions';
|
|
9
10
|
export * from './types';
|
|
@@ -49,7 +49,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
|
|
49
49
|
|
|
50
50
|
// Memoize color options to prevent unnecessary re-renders
|
|
51
51
|
const colorOptions = useMemo(() => {
|
|
52
|
-
return colorsMemo.map((color
|
|
52
|
+
return colorsMemo.map((color) => {
|
|
53
53
|
const isSelected = value === color;
|
|
54
54
|
|
|
55
55
|
return (
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
useAppNavigation
|
|
14
14
|
} from "@umituz/react-native-design-system";
|
|
15
15
|
import { useLocalization } from "../../../localization";
|
|
16
|
-
import { useAppearance
|
|
16
|
+
import { useAppearance } from "../../hooks/useAppearance";
|
|
17
|
+
import { useAppearanceActions } from "../../hooks/useAppearanceActions";
|
|
17
18
|
import {
|
|
18
19
|
AppearanceHeader,
|
|
19
20
|
ThemeModeSection,
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type {
|
|
8
|
-
ThemeMode,
|
|
8
|
+
ThemeMode as BaseThemeMode,
|
|
9
9
|
CustomThemeColors,
|
|
10
10
|
} from "@umituz/react-native-design-system";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// Extended theme mode to support 'auto' option
|
|
13
|
+
export type ThemeMode = BaseThemeMode | 'auto';
|
|
14
|
+
|
|
15
|
+
export type { CustomThemeColors };
|
|
13
16
|
|
|
14
17
|
export interface AppearanceSettings {
|
|
15
18
|
themeMode: ThemeMode;
|
|
@@ -69,7 +69,7 @@ export const DisclaimerModal: React.FC<DisclaimerModalProps> = ({
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
const getStyles = (
|
|
72
|
+
const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
73
73
|
StyleSheet.create({
|
|
74
74
|
modalContainer: {
|
|
75
75
|
flex: 1,
|
|
@@ -7,7 +7,6 @@ import React, { useState } from "react";
|
|
|
7
7
|
import { View, StyleSheet, TouchableOpacity, ScrollView, TextInput } from "react-native";
|
|
8
8
|
import { useAppDesignTokens, AtomicText, AtomicButton, AtomicIcon } from "@umituz/react-native-design-system";
|
|
9
9
|
import type { FeedbackType, FeedbackRating } from "../../domain/entities/FeedbackEntity";
|
|
10
|
-
import { useFeedbackForm } from "../hooks/useFeedbackForm";
|
|
11
10
|
|
|
12
11
|
export interface FeedbackFormProps {
|
|
13
12
|
onSubmit: (data: { type: FeedbackType; rating: FeedbackRating; description: string; title: string }) => Promise<void>;
|
|
@@ -35,16 +34,38 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
|
|
|
35
34
|
const [rating, setRating] = useState<FeedbackRating>(5);
|
|
36
35
|
const [description, setDescription] = useState("");
|
|
37
36
|
const [title, setTitle] = useState("");
|
|
37
|
+
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
const [isSubmittingLocal, setIsSubmittingLocal] = useState(false);
|
|
38
39
|
|
|
39
40
|
const handleSubmit = async () => {
|
|
40
|
-
|
|
41
|
+
// Validate input
|
|
42
|
+
if (!description.trim()) {
|
|
43
|
+
setError("Please provide a description");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
setIsSubmittingLocal(true);
|
|
48
|
+
setError(null);
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await onSubmit({
|
|
52
|
+
type: selectedType,
|
|
53
|
+
rating,
|
|
54
|
+
description,
|
|
55
|
+
title: title || texts.defaultTitle(selectedType),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Clear form on success
|
|
59
|
+
setDescription("");
|
|
60
|
+
setTitle("");
|
|
61
|
+
setRating(5);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to submit feedback";
|
|
64
|
+
setError(errorMessage);
|
|
65
|
+
console.error("[FeedbackForm] Submission error:", err);
|
|
66
|
+
} finally {
|
|
67
|
+
setIsSubmittingLocal(false);
|
|
68
|
+
}
|
|
48
69
|
};
|
|
49
70
|
|
|
50
71
|
const renderRating = () => (
|
|
@@ -116,7 +137,10 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
|
|
|
116
137
|
<View style={styles.inputContainer}>
|
|
117
138
|
<TextInput
|
|
118
139
|
value={description}
|
|
119
|
-
onChangeText={
|
|
140
|
+
onChangeText={(text) => {
|
|
141
|
+
setDescription(text);
|
|
142
|
+
setError(null); // Clear error on input
|
|
143
|
+
}}
|
|
120
144
|
placeholder={texts.descriptionPlaceholder}
|
|
121
145
|
placeholderTextColor={tokens.colors.textTertiary}
|
|
122
146
|
multiline
|
|
@@ -126,18 +150,27 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
|
|
|
126
150
|
{
|
|
127
151
|
color: tokens.colors.textPrimary,
|
|
128
152
|
backgroundColor: tokens.colors.surface,
|
|
129
|
-
borderColor: tokens.colors.border,
|
|
153
|
+
borderColor: error ? tokens.colors.error : tokens.colors.border,
|
|
130
154
|
}
|
|
131
155
|
]}
|
|
132
156
|
/>
|
|
157
|
+
{error && (
|
|
158
|
+
<AtomicText
|
|
159
|
+
type="bodySmall"
|
|
160
|
+
color="error"
|
|
161
|
+
style={styles.errorText}
|
|
162
|
+
>
|
|
163
|
+
{error}
|
|
164
|
+
</AtomicText>
|
|
165
|
+
)}
|
|
133
166
|
</View>
|
|
134
167
|
|
|
135
168
|
<AtomicButton
|
|
136
169
|
onPress={handleSubmit}
|
|
137
|
-
disabled={isSubmitting || !description.trim()}
|
|
170
|
+
disabled={isSubmitting || isSubmittingLocal || !description.trim()}
|
|
138
171
|
style={styles.submitButton}
|
|
139
172
|
>
|
|
140
|
-
{isSubmitting ? texts.submittingButton : texts.submitButton}
|
|
173
|
+
{(isSubmitting || isSubmittingLocal) ? texts.submittingButton : texts.submitButton}
|
|
141
174
|
</AtomicButton>
|
|
142
175
|
</View>
|
|
143
176
|
);
|
|
@@ -185,6 +218,9 @@ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
|
185
218
|
padding: 12,
|
|
186
219
|
fontSize: tokens.typography.bodyMedium.fontSize,
|
|
187
220
|
},
|
|
221
|
+
errorText: {
|
|
222
|
+
marginTop: 8,
|
|
223
|
+
},
|
|
188
224
|
submitButton: {
|
|
189
225
|
width: "100%",
|
|
190
226
|
},
|
|
@@ -79,7 +79,7 @@ export const FeedbackModal: React.FC<FeedbackModalProps> = ({
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
const getStyles = (
|
|
82
|
+
const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
83
83
|
StyleSheet.create({
|
|
84
84
|
safeArea: {
|
|
85
85
|
flex: 1,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
/**
|
|
3
3
|
* GamificationScreen Component
|
|
4
|
-
* Full gamification screen
|
|
4
|
+
* Full gamification screen with integrated hook
|
|
5
5
|
* Generic for 100+ apps - NO hardcoded strings
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { View, ScrollView
|
|
8
|
+
import { View, ScrollView } from "react-native";
|
|
9
9
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
10
10
|
import { LevelProgress } from "../LevelProgress";
|
|
11
11
|
import { StreakDisplay } from "../StreakDisplay";
|
|
@@ -13,11 +13,90 @@ import { Header } from "./Header";
|
|
|
13
13
|
import { StatsGrid } from "./StatsGrid";
|
|
14
14
|
import { AchievementsList } from "./AchievementsList";
|
|
15
15
|
import { styles } from "./styles";
|
|
16
|
-
import type { GamificationScreenProps } from "./types";
|
|
16
|
+
import type { GamificationScreenProps, GamificationConfigProps } from "./types";
|
|
17
|
+
import type { Achievement } from "../../types";
|
|
17
18
|
|
|
18
19
|
export type { GamificationScreenProps };
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
/**
|
|
22
|
+
* GamificationScreen that accepts either detailed props OR a config object
|
|
23
|
+
* When config is provided, a hook consumer component is used
|
|
24
|
+
*/
|
|
25
|
+
export const GamificationScreen: React.FC<GamificationScreenProps | GamificationConfigProps> = (props) => {
|
|
26
|
+
// If config is provided, use the hook-based component
|
|
27
|
+
if ('config' in props && props.config) {
|
|
28
|
+
return <GamificationScreenWithConfig config={props.config} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Otherwise, render directly with provided props
|
|
32
|
+
return <GamificationScreenInner {...(props as GamificationScreenProps)} />;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Component that uses the hook - separated to satisfy React Hooks rules
|
|
37
|
+
*/
|
|
38
|
+
const GamificationScreenWithConfig: React.FC<GamificationConfigProps> = ({ config }) => {
|
|
39
|
+
// Import hook here to avoid conditional hook usage
|
|
40
|
+
const { useGamification } = require("../../hooks/useGamification");
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
points,
|
|
44
|
+
level,
|
|
45
|
+
streak,
|
|
46
|
+
achievements,
|
|
47
|
+
} = useGamification(config);
|
|
48
|
+
|
|
49
|
+
// Transform store achievements to UI props
|
|
50
|
+
const achievementItems = achievements.map((a: Achievement) => ({
|
|
51
|
+
...a,
|
|
52
|
+
title: a.title,
|
|
53
|
+
description: a.description,
|
|
54
|
+
icon: a.icon,
|
|
55
|
+
isUnlocked: a.isUnlocked,
|
|
56
|
+
progress: a.progress,
|
|
57
|
+
threshold: a.threshold,
|
|
58
|
+
id: a.id,
|
|
59
|
+
type: a.type
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
const screenProps: GamificationScreenProps = {
|
|
63
|
+
title: config.translations.title,
|
|
64
|
+
statsTitle: config.translations.statsTitle,
|
|
65
|
+
achievementsTitle: config.translations.achievementsTitle,
|
|
66
|
+
streakTitle: config.translations.streakTitle,
|
|
67
|
+
levelProps: {
|
|
68
|
+
level: level.currentLevel,
|
|
69
|
+
points: level.currentPoints,
|
|
70
|
+
pointsToNext: level.pointsToNext,
|
|
71
|
+
progress: level.progress,
|
|
72
|
+
levelTitle: config.translations.levelTitle,
|
|
73
|
+
showPoints: true,
|
|
74
|
+
},
|
|
75
|
+
streakProps: {
|
|
76
|
+
current: streak.current,
|
|
77
|
+
longest: streak.longest,
|
|
78
|
+
currentLabel: config.translations.currentStreak,
|
|
79
|
+
bestLabel: config.translations.bestStreak,
|
|
80
|
+
daysLabel: config.translations.days,
|
|
81
|
+
},
|
|
82
|
+
stats: [
|
|
83
|
+
{
|
|
84
|
+
label: config.translations.statsTitle,
|
|
85
|
+
value: points,
|
|
86
|
+
icon: "star",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
achievements: achievementItems,
|
|
90
|
+
emptyAchievementsText: config.translations.emptyAchievements,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return <GamificationScreenInner {...screenProps} />;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Internal component that renders the screen with all props
|
|
98
|
+
*/
|
|
99
|
+
const GamificationScreenInner: React.FC<GamificationScreenProps> = ({
|
|
21
100
|
title,
|
|
22
101
|
statsTitle,
|
|
23
102
|
achievementsTitle,
|
|
@@ -9,6 +9,11 @@ import type { StatsCardProps } from "../StatsCard";
|
|
|
9
9
|
import type { AchievementItemProps } from "../AchievementItem";
|
|
10
10
|
import type { StreakDisplayProps } from "../StreakDisplay";
|
|
11
11
|
import type { ViewStyle, TextStyle } from "react-native";
|
|
12
|
+
import type { GamificationConfig } from "../../types";
|
|
13
|
+
|
|
14
|
+
export interface GamificationConfigProps extends Partial<Omit<GamificationScreenProps, 'config'>> {
|
|
15
|
+
config: GamificationConfig;
|
|
16
|
+
}
|
|
12
17
|
|
|
13
18
|
export interface GamificationScreenProps {
|
|
14
19
|
// Section titles (all via props)
|
|
@@ -11,4 +11,3 @@ export { StreakDisplay, type StreakDisplayProps } from "./StreakDisplay";
|
|
|
11
11
|
export { StatsCard, type StatsCardProps } from "./StatsCard";
|
|
12
12
|
export { AchievementItem, type AchievementItemProps } from "./AchievementItem";
|
|
13
13
|
export { GamificationScreen, type GamificationScreenProps } from "./GamificationScreen/index";
|
|
14
|
-
export { GamificationScreenWrapper } from "./GamificationScreenWrapper";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Pure utility functions - NO side effects
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { LevelDefinition, LevelState,
|
|
6
|
+
import type { LevelDefinition, LevelState, AchievementDefinition } from "../types";
|
|
7
7
|
|
|
8
8
|
export const calculateLevel = (
|
|
9
9
|
points: number,
|
|
@@ -42,7 +42,7 @@ export const LegalItem: React.FC<LegalItemProps> = React.memo(({
|
|
|
42
42
|
title,
|
|
43
43
|
description,
|
|
44
44
|
onPress,
|
|
45
|
-
testID,
|
|
45
|
+
testID: _testID,
|
|
46
46
|
}) => {
|
|
47
47
|
// Use iconName if provided, otherwise fallback to default
|
|
48
48
|
const finalIcon = iconName || icon || "shield-checkmark";
|
|
@@ -22,8 +22,6 @@ export type { LanguageSectionProps, LanguageSectionConfig } from './presentation
|
|
|
22
22
|
export { default as i18n } from './infrastructure/config/i18n';
|
|
23
23
|
export { I18nInitializer } from './infrastructure/config/I18nInitializer';
|
|
24
24
|
export {
|
|
25
|
-
SUPPORTED_LANGUAGES,
|
|
26
|
-
LANGUAGES,
|
|
27
25
|
DEFAULT_LANGUAGE,
|
|
28
26
|
getLanguageByCode,
|
|
29
27
|
isLanguageSupported,
|
|
@@ -17,7 +17,24 @@ export const useLanguageSwitcher = ({ onPress, disabled }: UseLanguageSwitcherPr
|
|
|
17
17
|
const { currentLanguage } = useLocalization();
|
|
18
18
|
|
|
19
19
|
const currentLang = useMemo((): Language => {
|
|
20
|
-
|
|
20
|
+
// Double fallback to ensure we always have a valid language
|
|
21
|
+
const lang = languageRepository.getLanguageByCode(currentLanguage)
|
|
22
|
+
|| languageRepository.getDefaultLanguage()
|
|
23
|
+
|| languageRepository.getLanguages()[0]; // Final fallback
|
|
24
|
+
|
|
25
|
+
if (!lang) {
|
|
26
|
+
// This should never happen if repository is set up correctly
|
|
27
|
+
console.error('[useLanguageSwitcher] No valid language found. Check language repository configuration.');
|
|
28
|
+
// Return a minimal fallback language object
|
|
29
|
+
return {
|
|
30
|
+
code: 'en',
|
|
31
|
+
name: 'English',
|
|
32
|
+
nativeName: 'English',
|
|
33
|
+
flag: 'πΊπΈ',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return lang;
|
|
21
38
|
}, [currentLanguage]);
|
|
22
39
|
|
|
23
40
|
const handlePress = useCallback(() => {
|
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
* Central export point for all language-related functionality
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { languageRepository } from '../repository/LanguageRepository';
|
|
7
|
-
import type { Language } from '../storage/types/Language';
|
|
8
|
-
|
|
9
6
|
// Re-export from DeviceLocale
|
|
10
7
|
export { DEFAULT_LANGUAGE, getDeviceLocale } from './DeviceLocale';
|
|
11
8
|
|
|
@@ -20,9 +17,3 @@ export {
|
|
|
20
17
|
|
|
21
18
|
// Re-export from LocaleMapping
|
|
22
19
|
export { LOCALE_MAPPING } from './LocaleMapping';
|
|
23
|
-
|
|
24
|
-
// Backward compatibility
|
|
25
|
-
export const getSUPPORTED_LANGUAGES = () => languageRepository.getLanguages();
|
|
26
|
-
export const getLANGUAGES = () => languageRepository.getLanguages();
|
|
27
|
-
export const SUPPORTED_LANGUAGES: Language[] = languageRepository.getLanguages();
|
|
28
|
-
export const LANGUAGES = SUPPORTED_LANGUAGES;
|
|
@@ -10,30 +10,28 @@
|
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import i18n from '../config/i18n';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const translationResult = useTranslation(undefined, { i18n });
|
|
13
|
+
/**
|
|
14
|
+
* Custom hook that provides translation function with proper fallbacks
|
|
15
|
+
*/
|
|
16
|
+
export const useTranslationFunction = (): ((key: string, options?: Record<string, unknown>) => string) => {
|
|
17
|
+
// Always call useTranslation hook (React hooks rules)
|
|
18
|
+
const translationResult = useTranslation(undefined, { i18n });
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
// Use react-i18next if available, otherwise fallback to direct i18n
|
|
21
|
+
if (translationResult?.t && typeof translationResult.t === 'function' && i18n.isInitialized) {
|
|
22
|
+
return (key: string, options?: Record<string, unknown>): string => {
|
|
23
|
+
const result = translationResult.t(key, options);
|
|
24
|
+
return typeof result === 'string' ? result : String(result);
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
return (key: string, options?: Record<string, unknown>): string => {
|
|
28
|
+
// Fallback to direct i18n.t
|
|
29
|
+
if (i18n.isInitialized && typeof i18n.t === 'function') {
|
|
30
|
+
const result = i18n.t(key, options);
|
|
25
31
|
return typeof result === 'string' ? result : String(result);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
if (i18n.isInitialized && typeof i18n.t === 'function') {
|
|
31
|
-
const result = i18n.t(key, options);
|
|
32
|
-
return typeof result === 'string' ? result : String(result);
|
|
33
|
-
}
|
|
34
|
-
// Final fallback: return key
|
|
35
|
-
return key;
|
|
36
|
-
};
|
|
37
|
-
}
|
|
32
|
+
}
|
|
33
|
+
// Final fallback: return key
|
|
34
|
+
return key;
|
|
35
|
+
};
|
|
38
36
|
}
|
|
39
|
-
}
|
|
37
|
+
};
|