Merge pull request #49 from djwesty/vutukuri15/35
Implemented Language Selector # 35
This commit is contained in:
commit
b7238d11d4
3
app.json
3
app.json
@ -33,7 +33,8 @@
|
|||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"expo-localization"
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
import i18n from "@/i18n/i18n";
|
||||||
import AppContext, { IAppContext } from "@/util/context";
|
import AppContext, { IAppContext } from "@/util/context";
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
import { MaterialIcons } from "@expo/vector-icons";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
|
import { I18nextProvider, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const RootLayout: React.FC = () => {
|
const RootLayout: React.FC = () => {
|
||||||
const [showSettings, setShowSettings] = useState<boolean>(false);
|
const [showSettings, setShowSettings] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const ctx = useMemo<IAppContext>(
|
const ctx = useMemo<IAppContext>(
|
||||||
() => ({
|
() => ({
|
||||||
showSettings,
|
showSettings,
|
||||||
@ -15,19 +19,21 @@ const RootLayout: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider value={ctx}>
|
<AppContext.Provider value={ctx}>
|
||||||
<Stack
|
<I18nextProvider i18n={i18n}>
|
||||||
screenOptions={{
|
<Stack
|
||||||
headerShown: true,
|
screenOptions={{
|
||||||
title: "Poker Chips Helper",
|
headerShown: true,
|
||||||
headerRight: () => (
|
title: t("poker_chips_helper"),
|
||||||
<MaterialIcons
|
headerRight: () => (
|
||||||
name="settings"
|
<MaterialIcons
|
||||||
onPress={() => setShowSettings(!showSettings)}
|
name="settings"
|
||||||
size={30}
|
onPress={() => setShowSettings(!showSettings)}
|
||||||
/>
|
size={30}
|
||||||
),
|
/>
|
||||||
}}
|
),
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</I18nextProvider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,8 @@ import {
|
|||||||
import styles from "@/styles/styles";
|
import styles from "@/styles/styles";
|
||||||
import Section from "@/containers/Section";
|
import Section from "@/containers/Section";
|
||||||
import AppContext from "@/util/context";
|
import AppContext from "@/util/context";
|
||||||
|
import { Picker } from "@react-native-picker/picker";
|
||||||
|
import i18n from "@/i18n/i18n";
|
||||||
|
|
||||||
const IndexScreen: React.FC = () => {
|
const IndexScreen: React.FC = () => {
|
||||||
const [playerCount, setPlayerCount] = useState(2);
|
const [playerCount, setPlayerCount] = useState(2);
|
||||||
@ -22,6 +24,7 @@ const IndexScreen: React.FC = () => {
|
|||||||
const [numberOfChips, setNumberOfChips] = useState<number>(5);
|
const [numberOfChips, setNumberOfChips] = useState<number>(5);
|
||||||
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
||||||
const [selectedCurrency, setSelectedCurrency] = useState<string>("$");
|
const [selectedCurrency, setSelectedCurrency] = useState<string>("$");
|
||||||
|
const [selectedLanguage, setSelectedLanguage] = useState<string>("en");
|
||||||
const context = useContext(AppContext);
|
const context = useContext(AppContext);
|
||||||
const isSettingsVisible = useMemo(() => context.showSettings, [context]);
|
const isSettingsVisible = useMemo(() => context.showSettings, [context]);
|
||||||
|
|
||||||
@ -49,7 +52,7 @@ const IndexScreen: React.FC = () => {
|
|||||||
|
|
||||||
const handleSave = async (slot: "SLOT1" | "SLOT2") => {
|
const handleSave = async (slot: "SLOT1" | "SLOT2") => {
|
||||||
if (buyInAmount === null) {
|
if (buyInAmount === null) {
|
||||||
Alert.alert("Error", "Please select a valid buy-in amount");
|
Alert.alert(i18n.t("error"), i18n.t("please_select_valid_buyin"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const state = {
|
const state = {
|
||||||
@ -63,10 +66,10 @@ const IndexScreen: React.FC = () => {
|
|||||||
await saveState(slot, state);
|
await saveState(slot, state);
|
||||||
await savePersistentState(state);
|
await savePersistentState(state);
|
||||||
console.log(`Game state saved to ${slot}:`, state);
|
console.log(`Game state saved to ${slot}:`, state);
|
||||||
Alert.alert("Success", `State saved to ${slot}`);
|
Alert.alert(i18n.t("success"), i18n.t("state_saved", { slot })); // Fixed interpolation
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving state:", error);
|
console.error("Error saving state:", error);
|
||||||
Alert.alert("Error", "Failed to save state.");
|
Alert.alert(i18n.t("error"), i18n.t("failed_to_save_state"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,23 +84,49 @@ const IndexScreen: React.FC = () => {
|
|||||||
setSelectedCurrency(loadedState.selectedCurrency || "$");
|
setSelectedCurrency(loadedState.selectedCurrency || "$");
|
||||||
await savePersistentState(loadedState);
|
await savePersistentState(loadedState);
|
||||||
console.log(`Game state loaded from ${slot}:`, loadedState);
|
console.log(`Game state loaded from ${slot}:`, loadedState);
|
||||||
Alert.alert("Success", `State loaded from ${slot}`);
|
Alert.alert(i18n.t("success"), i18n.t("state_loaded_from", { slot })); // Fixed interpolation
|
||||||
} else {
|
} else {
|
||||||
Alert.alert("Info", "No saved state found.");
|
Alert.alert(i18n.t("info"), i18n.t("no_saved_state_found"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading state:", error);
|
console.error("Error loading state:", error);
|
||||||
Alert.alert("Error", "Failed to load state.");
|
Alert.alert(i18n.t("error"), i18n.t("failed_to_load_state"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLanguageChange = (language: string) => {
|
||||||
|
setSelectedLanguage(language);
|
||||||
|
i18n.changeLanguage(language);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollViewContent}
|
contentContainerStyle={styles.scrollViewContent}
|
||||||
>
|
>
|
||||||
{isSettingsVisible && (
|
{isSettingsVisible && (
|
||||||
<Section title={"Select Currency"} iconName={"attach-money"}>
|
<Section
|
||||||
|
title={i18n.t("select_language")}
|
||||||
|
iconName={"language"}
|
||||||
|
orientation="row"
|
||||||
|
>
|
||||||
|
<Picker
|
||||||
|
selectedValue={selectedLanguage}
|
||||||
|
onValueChange={handleLanguageChange}
|
||||||
|
style={styles.picker}
|
||||||
|
>
|
||||||
|
<Picker.Item label={i18n.t("english")} value="en" />
|
||||||
|
<Picker.Item label={i18n.t("spanish")} value="es" />
|
||||||
|
</Picker>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isSettingsVisible && (
|
||||||
|
<Section
|
||||||
|
title={i18n.t("select_currency")}
|
||||||
|
iconName={"attach-money"}
|
||||||
|
orientation="row"
|
||||||
|
>
|
||||||
<CurrencySelector
|
<CurrencySelector
|
||||||
selectedCurrency={selectedCurrency}
|
selectedCurrency={selectedCurrency}
|
||||||
setSelectedCurrency={setSelectedCurrency}
|
setSelectedCurrency={setSelectedCurrency}
|
||||||
@ -106,7 +135,7 @@ const IndexScreen: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Section
|
<Section
|
||||||
title={"Select the number of players"}
|
title={i18n.t("select_number_of_players")}
|
||||||
iconName={"people"}
|
iconName={"people"}
|
||||||
orientation="row"
|
orientation="row"
|
||||||
>
|
>
|
||||||
@ -116,14 +145,20 @@ const IndexScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title={"Select buy-in amount"} iconName={"monetization-on"}>
|
<Section
|
||||||
|
title={i18n.t("select_buyin_amount")}
|
||||||
|
iconName={"monetization-on"}
|
||||||
|
>
|
||||||
<BuyInSelector
|
<BuyInSelector
|
||||||
selectedCurrency={selectedCurrency}
|
selectedCurrency={selectedCurrency}
|
||||||
setBuyInAmount={setBuyInAmount}
|
setBuyInAmount={setBuyInAmount}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title={"Automatic Chip Detection"} iconName={"camera-alt"}>
|
<Section
|
||||||
|
title={i18n.t("automatic_chip_detection")}
|
||||||
|
iconName={"camera-alt"}
|
||||||
|
>
|
||||||
<ChipDetection
|
<ChipDetection
|
||||||
updateChipCount={(chipData) => {
|
updateChipCount={(chipData) => {
|
||||||
const chipCountArray = Object.values(chipData);
|
const chipCountArray = Object.values(chipData);
|
||||||
@ -132,7 +167,10 @@ const IndexScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title={"Manual Chip Adjustment"} iconName={"account-balance"}>
|
<Section
|
||||||
|
title={i18n.t("manual_chip_adjustment")}
|
||||||
|
iconName={"account-balance"}
|
||||||
|
>
|
||||||
<ChipsSelector
|
<ChipsSelector
|
||||||
totalChipsCount={totalChipsCount}
|
totalChipsCount={totalChipsCount}
|
||||||
setTotalChipsCount={setTotalChipsCount}
|
setTotalChipsCount={setTotalChipsCount}
|
||||||
@ -142,7 +180,7 @@ const IndexScreen: React.FC = () => {
|
|||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section
|
<Section
|
||||||
title={"Distribution & Denomination"}
|
title={i18n.t("distribution_and_denomination")}
|
||||||
iconName={"currency-exchange"}
|
iconName={"currency-exchange"}
|
||||||
>
|
>
|
||||||
<ChipDistributionSummary
|
<ChipDistributionSummary
|
||||||
@ -153,20 +191,30 @@ const IndexScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title={"Save + Load"} iconName={"save"} orientation="row">
|
<Section
|
||||||
|
title={i18n.t("save_and_load")}
|
||||||
|
iconName={"save"}
|
||||||
|
orientation="row"
|
||||||
|
>
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
title={"Save\nSlot 1"}
|
title={i18n.t("save_slot_1")}
|
||||||
onPress={() => handleSave("SLOT1")}
|
onPress={() => handleSave("SLOT1")}
|
||||||
disabled={buyInAmount === null}
|
disabled={buyInAmount === null}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title={"Save\nSlot 2"}
|
title={i18n.t("save_slot_2")}
|
||||||
onPress={() => handleSave("SLOT2")}
|
onPress={() => handleSave("SLOT2")}
|
||||||
disabled={buyInAmount === null}
|
disabled={buyInAmount === null}
|
||||||
/>
|
/>
|
||||||
<Button title={"Load\nSlot 1"} onPress={() => handleLoad("SLOT1")} />
|
<Button
|
||||||
<Button title={"Load\nSlot 2"} onPress={() => handleLoad("SLOT2")} />
|
title={i18n.t("load_slot_1")}
|
||||||
|
onPress={() => handleLoad("SLOT1")}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title={i18n.t("load_slot_2")}
|
||||||
|
onPress={() => handleLoad("SLOT2")}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
</Section>
|
</Section>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -2,10 +2,11 @@ import React, { useState } from "react";
|
|||||||
import { View, Text, TextInput } from "react-native";
|
import { View, Text, TextInput } from "react-native";
|
||||||
import styles, { COLORS } from "@/styles/styles";
|
import styles, { COLORS } from "@/styles/styles";
|
||||||
import Button from "@/containers/Button";
|
import Button from "@/containers/Button";
|
||||||
|
import i18n from "@/i18n/i18n";
|
||||||
|
|
||||||
interface BuyInSelectorProps {
|
interface BuyInSelectorProps {
|
||||||
setBuyInAmount: React.Dispatch<React.SetStateAction<number>>;
|
setBuyInAmount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
selectedCurrency: string; // Accept selectedCurrency as a prop
|
selectedCurrency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBuyInOptions = [10, 25, 50];
|
const defaultBuyInOptions = [10, 25, 50];
|
||||||
@ -45,22 +46,25 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
|
|||||||
color={buyInAmount === amount ? COLORS.PRIMARY : COLORS.SECONDARY}
|
color={buyInAmount === amount ? COLORS.PRIMARY : COLORS.SECONDARY}
|
||||||
onPress={() => handleBuyInSelection(amount)}
|
onPress={() => handleBuyInSelection(amount)}
|
||||||
title={`${selectedCurrency} ${amount}`}
|
title={`${selectedCurrency} ${amount}`}
|
||||||
></Button>
|
/>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.p}>Or enter a custom amount:</Text>
|
<Text style={styles.p}>{i18n.t("custom_buy_in")}</Text>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
value={customAmount}
|
value={customAmount}
|
||||||
onChangeText={handleCustomAmountChange}
|
onChangeText={handleCustomAmountChange}
|
||||||
placeholder="Enter custom buy-in"
|
placeholder={i18n.t("enter_custom_buy_in")}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={styles.h2}>
|
<Text style={styles.h2}>
|
||||||
Selected Buy-in:{" "}
|
{`${i18n.t("selected_buy_in")}: `}
|
||||||
{buyInAmount !== null ? `${selectedCurrency} ${buyInAmount}` : "None"}
|
{buyInAmount !== null
|
||||||
|
? `${selectedCurrency} ${buyInAmount}`
|
||||||
|
: i18n.t("none")}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Image, ActivityIndicator, Text, View } from "react-native";
|
import { Image, ActivityIndicator, Text, View } from "react-native";
|
||||||
import Button from "@/containers/Button";
|
import Button from "@/containers/Button";
|
||||||
|
|
||||||
import * as ImagePicker from "expo-image-picker";
|
import * as ImagePicker from "expo-image-picker";
|
||||||
|
import i18n from "@/i18n/i18n";
|
||||||
|
|
||||||
const ChipDetection = ({
|
const ChipDetection = ({
|
||||||
updateChipCount,
|
updateChipCount,
|
||||||
}: {
|
}: {
|
||||||
updateChipCount: () => void;
|
updateChipCount: (chipData: Record<string, number>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [imageUri, setImageUri] = useState(null);
|
const [imageUri, setImageUri] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [lastDetectedChips, setLastDetectedChips] = useState({});
|
const [lastDetectedChips, setLastDetectedChips] = useState<
|
||||||
|
Record<string, number>
|
||||||
|
>({});
|
||||||
|
|
||||||
const requestCameraPermissions = async () => {
|
const requestCameraPermissions = async () => {
|
||||||
const cameraPermission = await ImagePicker.requestCameraPermissionsAsync();
|
const cameraPermission = await ImagePicker.requestCameraPermissionsAsync();
|
||||||
@ -26,16 +28,16 @@ const ChipDetection = ({
|
|||||||
quality: 1,
|
quality: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.canceled) {
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
||||||
setImageUri(result.assets[0].uri);
|
setImageUri(result.assets[0].uri);
|
||||||
await processImage(result.assets[0].base64);
|
await processImage(result.assets[0].base64 as string);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const takePhoto = async () => {
|
const takePhoto = async () => {
|
||||||
const hasPermission = await requestCameraPermissions();
|
const hasPermission = await requestCameraPermissions();
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
setError("Camera permission is required to take a photo.");
|
setError(i18n.t("camera_permission_required"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,25 +46,25 @@ const ChipDetection = ({
|
|||||||
quality: 1,
|
quality: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.canceled) {
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
||||||
setImageUri(result.assets[0].uri);
|
setImageUri(result.assets[0].uri);
|
||||||
await processImage(result.assets[0].base64);
|
await processImage(result.assets[0].base64 as string);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const processImage = async (base64Image) => {
|
const processImage = async (base64Image: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(process.env.EXPO_PUBLIC_API_URL, {
|
const response = await fetch(process.env.EXPO_PUBLIC_API_URL as string, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${process.env.EXPO_PUBLIC_API_KEY}`, // Use environment variable for API key
|
Authorization: `Bearer ${process.env.EXPO_PUBLIC_API_KEY}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: process.env.EXPO_PUBLIC_MODEL_NAME, // Use environment variable for model name
|
model: process.env.EXPO_PUBLIC_MODEL_NAME,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
@ -90,13 +92,12 @@ const ChipDetection = ({
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (!response.ok || !result.choices || !result.choices[0].message) {
|
if (!response.ok || !result.choices || !result.choices[0].message) {
|
||||||
throw new Error("Invalid response from API.");
|
throw new Error(i18n.t("invalid_response")); // Translate invalid response error
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawContent = result.choices[0].message.content.trim();
|
const rawContent = result.choices[0].message.content.trim();
|
||||||
const cleanJSON = rawContent.replace(/```json|```/g, "").trim();
|
const cleanJSON = rawContent.replace(/```json|```/g, "").trim();
|
||||||
|
const parsedData: Record<string, number> = JSON.parse(cleanJSON);
|
||||||
const parsedData = JSON.parse(cleanJSON);
|
|
||||||
|
|
||||||
const filteredData = Object.fromEntries(
|
const filteredData = Object.fromEntries(
|
||||||
Object.entries(parsedData).filter(([_, count]) => count > 0)
|
Object.entries(parsedData).filter(([_, count]) => count > 0)
|
||||||
@ -105,27 +106,36 @@ const ChipDetection = ({
|
|||||||
setLastDetectedChips(filteredData);
|
setLastDetectedChips(filteredData);
|
||||||
updateChipCount(filteredData);
|
updateChipCount(filteredData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError("Failed to analyze the image.");
|
setError(i18n.t("failed_to_analyze_image"));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View>
|
||||||
<View style={{ flexDirection: "row", justifyContent: "space-evenly" }}>
|
<View
|
||||||
<Button title="Pick an Image" onPress={pickImage} />
|
style={{
|
||||||
<Button title="Take a Photo" onPress={takePhoto} />
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button title={i18n.t("pick_an_image")} onPress={pickImage} />
|
||||||
|
<Button title={i18n.t("take_a_photo")} onPress={takePhoto} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{imageUri && (
|
{imageUri && (
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: imageUri }}
|
source={{ uri: imageUri }}
|
||||||
style={{ width: 300, height: 300, marginTop: 10 }}
|
style={{ width: 300, height: 300, marginTop: 10 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{loading && <ActivityIndicator size="large" color="blue" />}
|
{loading && <ActivityIndicator size="large" color="blue" />}
|
||||||
|
|
||||||
{error && <Text style={{ color: "red", marginTop: 10 }}>{error}</Text>}
|
{error && <Text style={{ color: "red", marginTop: 10 }}>{error}</Text>}
|
||||||
</>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { View, Text, StyleSheet } from "react-native";
|
import { View, Text, StyleSheet } from "react-native";
|
||||||
import { ColorValue } from "react-native";
|
import { ColorValue } from "react-native";
|
||||||
|
import i18n from "@/i18n/i18n";
|
||||||
import styles from "@/styles/styles";
|
import styles from "@/styles/styles";
|
||||||
|
|
||||||
interface ChipDistributionSummaryProps {
|
interface ChipDistributionSummaryProps {
|
||||||
@ -8,7 +9,7 @@ interface ChipDistributionSummaryProps {
|
|||||||
buyInAmount: number;
|
buyInAmount: number;
|
||||||
totalChipsCount: number[];
|
totalChipsCount: number[];
|
||||||
colors?: ColorValue[];
|
colors?: ColorValue[];
|
||||||
selectedCurrency: string; // Add the selectedCurrency as a prop here
|
selectedCurrency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChipDistributionSummary = ({
|
const ChipDistributionSummary = ({
|
||||||
@ -165,17 +166,17 @@ const ChipDistributionSummary = ({
|
|||||||
...(colors[index] === "white" && styles.shadow),
|
...(colors[index] === "white" && styles.shadow),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`${distributions[index]} chips: ${selectedCurrency}${denominations[index]} each`}
|
{`${distributions[index]} ${i18n.t("chips")}: ${selectedCurrency}${denominations[index]} ${i18n.t("each")}`}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
||||||
<Text style={styles.p}>
|
<Text style={styles.p}>
|
||||||
Total Value: {selectedCurrency} {totalValue}
|
{i18n.t("total_value")}: {selectedCurrency} {totalValue}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.p}>
|
<Text style={styles.p}>
|
||||||
{selectedCurrency} {potValue} Pot
|
{selectedCurrency} {potValue} {i18n.t("pot")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import Button from "@/containers/Button";
|
import Button from "@/containers/Button";
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
import styles from "@/styles/styles";
|
import styles from "@/styles/styles";
|
||||||
|
import i18n from "@/i18n/i18n";
|
||||||
|
|
||||||
const colors: ColorValue[] = ["white", "red", "green", "blue", "black"];
|
const colors: ColorValue[] = ["white", "red", "green", "blue", "black"];
|
||||||
|
|
||||||
@ -48,7 +49,9 @@ const ChipInputModal = ({
|
|||||||
{value !== undefined && (
|
{value !== undefined && (
|
||||||
<>
|
<>
|
||||||
<Text style={styles.h2}>
|
<Text style={styles.h2}>
|
||||||
Number of {showModal[1]?.toString()} chips
|
{i18n.t("number_of_chips", {
|
||||||
|
color: showModal[1]?.toString(),
|
||||||
|
})}{" "}
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={{
|
style={{
|
||||||
@ -67,7 +70,7 @@ const ChipInputModal = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
title="Accept"
|
title={i18n.t("accept")}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
update(showModal[1], Number.isNaN(value) ? 0 : value);
|
update(showModal[1], Number.isNaN(value) ? 0 : value);
|
||||||
setShowModal([false, color]);
|
setShowModal([false, color]);
|
||||||
@ -128,7 +131,7 @@ const ChipsSelector = ({
|
|||||||
[numberOfChips]
|
[numberOfChips]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Callback for ChipInputModal to update the chips in the parents state.
|
// Callback for ChipInputModal to update the chips in the parent's state.
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(color: ColorValue, count: number) => {
|
(color: ColorValue, count: number) => {
|
||||||
const newTotalChipsCount = totalChipsCount.slice();
|
const newTotalChipsCount = totalChipsCount.slice();
|
||||||
@ -227,4 +230,5 @@ const styles1 = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
button: {},
|
button: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ChipsSelector;
|
export default ChipsSelector;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Picker } from "@react-native-picker/picker";
|
import { Picker } from "@react-native-picker/picker";
|
||||||
import styles from "@/styles/styles";
|
import styles from "@/styles/styles";
|
||||||
|
import i18n from "@/i18n/i18n";
|
||||||
|
|
||||||
interface CurrencySelectorProps {
|
interface CurrencySelectorProps {
|
||||||
selectedCurrency: string;
|
selectedCurrency: string;
|
||||||
@ -19,10 +20,10 @@ const CurrencySelector: React.FC<CurrencySelectorProps> = ({
|
|||||||
style={styles.picker}
|
style={styles.picker}
|
||||||
testID="currency-picker" // ✅ Add testID here
|
testID="currency-picker" // ✅ Add testID here
|
||||||
>
|
>
|
||||||
<Picker.Item label="USD ($)" value="$" />
|
<Picker.Item label={i18n.t("usd")} value="$" />
|
||||||
<Picker.Item label="Euro (€)" value="€" />
|
<Picker.Item label={i18n.t("euro")} value="€" />
|
||||||
<Picker.Item label="Pound (£)" value="£" />
|
<Picker.Item label={i18n.t("pound")} value="£" />
|
||||||
<Picker.Item label="INR (₹)" value="₹" />
|
<Picker.Item label={i18n.t("inr")} value="₹" />
|
||||||
</Picker>
|
</Picker>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -13,6 +13,7 @@ describe("BuyInSelector Component", () => {
|
|||||||
let setBuyInAmount;
|
let setBuyInAmount;
|
||||||
let getByText;
|
let getByText;
|
||||||
let getByPlaceholderText;
|
let getByPlaceholderText;
|
||||||
|
let queryByText;
|
||||||
|
|
||||||
// Render the component with the necessary props
|
// Render the component with the necessary props
|
||||||
const renderComponent = (selectedCurrency = "$") => {
|
const renderComponent = (selectedCurrency = "$") => {
|
||||||
@ -24,11 +25,12 @@ describe("BuyInSelector Component", () => {
|
|||||||
);
|
);
|
||||||
getByText = result.getByText;
|
getByText = result.getByText;
|
||||||
getByPlaceholderText = result.getByPlaceholderText;
|
getByPlaceholderText = result.getByPlaceholderText;
|
||||||
|
queryByText = result.queryByText;
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setBuyInAmount = jest.fn();
|
setBuyInAmount = jest.fn();
|
||||||
renderComponent(); // Render with default currency
|
renderComponent();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders the buy-in options and input correctly", () => {
|
it("renders the buy-in options and input correctly", () => {
|
||||||
@ -36,7 +38,9 @@ describe("BuyInSelector Component", () => {
|
|||||||
expect(getByText("$ 25")).toBeTruthy();
|
expect(getByText("$ 25")).toBeTruthy();
|
||||||
expect(getByText("$ 50")).toBeTruthy();
|
expect(getByText("$ 50")).toBeTruthy();
|
||||||
expect(getByPlaceholderText("Enter custom buy-in")).toBeTruthy();
|
expect(getByPlaceholderText("Enter custom buy-in")).toBeTruthy();
|
||||||
expect(getByText("Selected Buy-in: None")).toBeTruthy(); // Check default selection
|
|
||||||
|
// Check default selection with a more flexible approach
|
||||||
|
expect(queryByText(/Selected Buy-in.*None/)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets a predefined buy-in amount correctly", () => {
|
it("sets a predefined buy-in amount correctly", () => {
|
||||||
@ -51,9 +55,9 @@ describe("BuyInSelector Component", () => {
|
|||||||
|
|
||||||
it("resets custom amount if invalid input is entered", () => {
|
it("resets custom amount if invalid input is entered", () => {
|
||||||
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "-10");
|
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "-10");
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(25); // Assuming 25 is the default
|
expect(setBuyInAmount).toHaveBeenCalledWith(25);
|
||||||
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "abc");
|
fireEvent.changeText(getByPlaceholderText("Enter custom buy-in"), "abc");
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(25); // Reset to default
|
expect(setBuyInAmount).toHaveBeenCalledWith(25);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clears the custom amount when selecting a predefined option", () => {
|
it("clears the custom amount when selecting a predefined option", () => {
|
||||||
@ -74,9 +78,9 @@ describe("BuyInSelector Component", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("triggers state update every time a buy-in option is clicked, even if it's the same", () => {
|
it("triggers state update every time a buy-in option is clicked, even if it's the same", () => {
|
||||||
fireEvent.press(getByText("$ 25")); // First click
|
fireEvent.press(getByText("$ 25"));
|
||||||
fireEvent.press(getByText("$ 25")); // Clicking the same option again
|
fireEvent.press(getByText("$ 25"));
|
||||||
expect(setBuyInAmount).toHaveBeenCalledTimes(2); // Expect it to be called twice
|
expect(setBuyInAmount).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resets to default buy-in when custom input is cleared", () => {
|
it("resets to default buy-in when custom input is cleared", () => {
|
||||||
@ -84,7 +88,7 @@ describe("BuyInSelector Component", () => {
|
|||||||
fireEvent.changeText(input, "75");
|
fireEvent.changeText(input, "75");
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(75);
|
expect(setBuyInAmount).toHaveBeenCalledWith(75);
|
||||||
fireEvent.changeText(input, "");
|
fireEvent.changeText(input, "");
|
||||||
expect(setBuyInAmount).toHaveBeenCalledWith(25); // Assuming 25 is the default
|
expect(setBuyInAmount).toHaveBeenCalledWith(25);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates state correctly when selecting predefined buy-in after entering a custom amount", () => {
|
it("updates state correctly when selecting predefined buy-in after entering a custom amount", () => {
|
||||||
@ -95,10 +99,12 @@ describe("BuyInSelector Component", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("displays selected currency correctly", () => {
|
it("displays selected currency correctly", () => {
|
||||||
renderComponent("€"); // Test with a different currency
|
renderComponent("€");
|
||||||
expect(getByText("€ 10")).toBeTruthy();
|
expect(getByText("€ 10")).toBeTruthy();
|
||||||
expect(getByText("€ 25")).toBeTruthy();
|
expect(getByText("€ 25")).toBeTruthy();
|
||||||
expect(getByText("€ 50")).toBeTruthy();
|
expect(getByText("€ 50")).toBeTruthy();
|
||||||
expect(getByText("Selected Buy-in: None")).toBeTruthy();
|
|
||||||
|
// Check default selection text with a flexible regex
|
||||||
|
expect(queryByText(/Selected Buy-in.*None/)).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,8 +42,9 @@ describe("ChipDetection", () => {
|
|||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||||
);
|
);
|
||||||
expect(getByText("Pick an Image")).toBeTruthy();
|
|
||||||
expect(getByText("Take a Photo")).toBeTruthy();
|
expect(getByText(/pick an image/i)).toBeTruthy();
|
||||||
|
expect(getByText(/take a photo/i)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("picks an image from the library", async () => {
|
it("picks an image from the library", async () => {
|
||||||
@ -55,7 +56,7 @@ describe("ChipDetection", () => {
|
|||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||||
);
|
);
|
||||||
fireEvent.press(getByText("Pick an Image"));
|
fireEvent.press(getByText(/pick an image/i));
|
||||||
|
|
||||||
await waitFor(() => expect(mockUpdateChipCount).toHaveBeenCalled());
|
await waitFor(() => expect(mockUpdateChipCount).toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
@ -72,7 +73,7 @@ describe("ChipDetection", () => {
|
|||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||||
);
|
);
|
||||||
fireEvent.press(getByText("Take a Photo"));
|
fireEvent.press(getByText(/take a photo/i));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(mockUpdateChipCount).toHaveBeenCalledWith({ red: 5, green: 3 })
|
expect(mockUpdateChipCount).toHaveBeenCalledWith({ red: 5, green: 3 })
|
||||||
@ -87,11 +88,11 @@ describe("ChipDetection", () => {
|
|||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||||
);
|
);
|
||||||
fireEvent.press(getByText("Take a Photo"));
|
fireEvent.press(getByText(/take a photo/i));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(
|
expect(
|
||||||
getByText("Camera permission is required to take a photo.")
|
getByText(/camera permission is required to take a photo/i)
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -112,10 +113,10 @@ describe("ChipDetection", () => {
|
|||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||||
);
|
);
|
||||||
fireEvent.press(getByText("Pick an Image"));
|
fireEvent.press(getByText(/pick an image/i));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(getByText("Failed to analyze the image.")).toBeTruthy()
|
expect(getByText(/failed to analyze the image/i)).toBeTruthy()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ describe("ChipDetection", () => {
|
|||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
<ChipDetection updateChipCount={mockUpdateChipCount} />
|
||||||
);
|
);
|
||||||
fireEvent.press(getByText("Pick an Image"));
|
fireEvent.press(getByText(/pick an image/i));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(mockUpdateChipCount).toHaveBeenCalledWith({
|
expect(mockUpdateChipCount).toHaveBeenCalledWith({
|
||||||
|
@ -6,9 +6,7 @@ describe("ChipDistributionSummary Component", () => {
|
|||||||
test("renders correctly with valid data", () => {
|
test("renders correctly with valid data", () => {
|
||||||
const playerCount = 4;
|
const playerCount = 4;
|
||||||
const totalChipsCount = [100, 80, 60, 40, 20];
|
const totalChipsCount = [100, 80, 60, 40, 20];
|
||||||
const colors = ["WHITE", "RED", "GREEN", "BLUE", "BLACK"];
|
|
||||||
const buyInAmount = 20;
|
const buyInAmount = 20;
|
||||||
|
|
||||||
const expectedDistribution = [2, 2, 1, 2, 2];
|
const expectedDistribution = [2, 2, 1, 2, 2];
|
||||||
const expectedDenominations = [0.5, 1, 2, 2.5, 5];
|
const expectedDenominations = [0.5, 1, 2, 2.5, 5];
|
||||||
|
|
||||||
@ -23,7 +21,8 @@ describe("ChipDistributionSummary Component", () => {
|
|||||||
|
|
||||||
expectedDistribution.forEach((count, index) => {
|
expectedDistribution.forEach((count, index) => {
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(
|
||||||
`^${count}\\s+chips:\\s+\\$${expectedDenominations[index]}\\s+each$`
|
`^${count}\\s+chips:\\s+\\$${expectedDenominations[index]}\\s+Each$`,
|
||||||
|
"i"
|
||||||
);
|
);
|
||||||
expect(getByText(regex)).toBeTruthy();
|
expect(getByText(regex)).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -67,7 +66,7 @@ describe("ChipDistributionSummary Component", () => {
|
|||||||
|
|
||||||
expect(getByText("Distribution & Denomination")).toBeTruthy();
|
expect(getByText("Distribution & Denomination")).toBeTruthy();
|
||||||
|
|
||||||
expectedDistribution.forEach((count, index) => {
|
expectedDistribution.forEach((count) => {
|
||||||
expect(getByText(new RegExp(`^${count}\\s+chips:`, "i"))).toBeTruthy();
|
expect(getByText(new RegExp(`^${count}\\s+chips:`, "i"))).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
screen,
|
screen,
|
||||||
waitForElementToBeRemoved,
|
waitForElementToBeRemoved,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
|
act,
|
||||||
} from "@testing-library/react-native";
|
} from "@testing-library/react-native";
|
||||||
import ChipsSelector from "@/components/ChipsSelector";
|
import ChipsSelector from "@/components/ChipsSelector";
|
||||||
|
|
||||||
@ -55,7 +56,8 @@ describe("tests for ChipsSelector", () => {
|
|||||||
const green = screen.getByText("60");
|
const green = screen.getByText("60");
|
||||||
expect(green).toHaveStyle({ color: "green" });
|
expect(green).toHaveStyle({ color: "green" });
|
||||||
|
|
||||||
userEvent.press(green);
|
fireEvent.press(green);
|
||||||
|
|
||||||
const modalLabel = await screen.findByText(/number of green chips/i);
|
const modalLabel = await screen.findByText(/number of green chips/i);
|
||||||
expect(modalLabel).toBeDefined();
|
expect(modalLabel).toBeDefined();
|
||||||
|
|
||||||
|
50
i18n/en.json
Normal file
50
i18n/en.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"translation": {
|
||||||
|
"poker_chips_helper": "Poker Chips Helper",
|
||||||
|
"select_currency": "Select Currency",
|
||||||
|
"usd": "USD ($)",
|
||||||
|
"euro": "Euro (€)",
|
||||||
|
"pound": "Pound (£)",
|
||||||
|
"inr": "INR (₹)",
|
||||||
|
"select_number_of_players": "Select the Number of Players:",
|
||||||
|
"select_buyin_amount": "Select Buy-in Amount:",
|
||||||
|
"custom_buy_in": "Or enter a custom amount:",
|
||||||
|
"enter_custom_buy_in": "Enter custom buy-in",
|
||||||
|
"selected_buy_in": "Selected Buy-in:",
|
||||||
|
"none": "None",
|
||||||
|
"pick_an_image": "Pick an image",
|
||||||
|
"take_a_photo": "Take a photo",
|
||||||
|
"chips": "chips",
|
||||||
|
"number_of_chips": "Number of {{color}} chips",
|
||||||
|
"accept": "Accept",
|
||||||
|
"distribution_and_denomination": "Distribution & Denomination",
|
||||||
|
"pot": "Pot",
|
||||||
|
"total_value": "Total Value",
|
||||||
|
"each": "Each",
|
||||||
|
"select_language": "Select Language",
|
||||||
|
"english": "English",
|
||||||
|
"spanish": "Spanish",
|
||||||
|
"settings": "Settings",
|
||||||
|
"camera_permission_required": "Camera permission is required to take a photo.",
|
||||||
|
"how_many_chips_by_color": "How many poker chips are there for each color? Return structured JSON.",
|
||||||
|
"invalid_response": "Invalid response from API.",
|
||||||
|
"failed_to_analyze_image": "Failed to analyze the image.",
|
||||||
|
"state_saved_successfully": "State saved successfully",
|
||||||
|
"state_saved": "State saved to {{slot}}",
|
||||||
|
"state_save_failed": "Failed to save state",
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"failed_to_save_state": "Failed to save state.",
|
||||||
|
"state_loaded_from": "State loaded from",
|
||||||
|
"info": "Info",
|
||||||
|
"no_saved_state_found": "No saved state found.",
|
||||||
|
"automatic_chip_detection": "Automatic Chip Detection",
|
||||||
|
"manual_chip_adjustment": "Manual Chip Adjustment",
|
||||||
|
"save_and_load": "Save & Load",
|
||||||
|
"save_slot_1": "Save\nSlot 1",
|
||||||
|
"save_slot_2": "Save\nSlot 2",
|
||||||
|
"load_slot_1": "Load\nSlot 1",
|
||||||
|
"load_slot_2": "Load\nSlot 2",
|
||||||
|
"please_select_valid_buyin": "Please select a valid buy-in amount"
|
||||||
|
}
|
||||||
|
}
|
51
i18n/es.json
Normal file
51
i18n/es.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"translation": {
|
||||||
|
"poker_chips_helper": "Ayudante de Fichas de Póker",
|
||||||
|
"select_currency": "Seleccionar moneda",
|
||||||
|
"usd": "USD ($)",
|
||||||
|
"euro": "Euro (€)",
|
||||||
|
"pound": "Libra (£)",
|
||||||
|
"inr": "INR (₹)",
|
||||||
|
"select_number_of_players": "Seleccionar número de jugadores:",
|
||||||
|
"select_buyin_amount": "Seleccionar cantidad de buy-in:",
|
||||||
|
"custom_buy_in": "O ingresa una cantidad personalizada:",
|
||||||
|
"enter_custom_buy_in": "Ingresar buy-in personalizado",
|
||||||
|
"selected_buy_in": "Buy-in seleccionado:",
|
||||||
|
"none": "Ninguno",
|
||||||
|
"pick_an_image": "Elige una imagen",
|
||||||
|
"take_a_photo": "Tomar una foto",
|
||||||
|
"chips": "fichas",
|
||||||
|
"number_of_chips": "Número de {{color}} fichas",
|
||||||
|
"accept": "Aceptar",
|
||||||
|
"distribution_and_denomination": "Distribución y Denominación",
|
||||||
|
"pot": "Olla",
|
||||||
|
"total_value": "Valor total",
|
||||||
|
"each": "Cada",
|
||||||
|
"select_language": "Seleccionar idioma",
|
||||||
|
"english": "Inglés",
|
||||||
|
"spanish": "Español",
|
||||||
|
"settings": "Configuraciones",
|
||||||
|
"camera_permission_required": "Se requiere permiso de cámara para tomar una foto.",
|
||||||
|
"how_many_chips_by_color": "¿Cuántas fichas de póker hay de cada color? Devuelve JSON estructurado.",
|
||||||
|
"invalid_response": "Respuesta no válida de la API.",
|
||||||
|
"failed_to_analyze_image": "No se pudo analizar la imagen",
|
||||||
|
"state_saved_successfully": "Estado guardado con éxito",
|
||||||
|
"state_saved": "Estado guardado en {{slot}}",
|
||||||
|
"state_save_failed": "Error al guardar el estado",
|
||||||
|
"success": "Éxito",
|
||||||
|
"state_saved_to": "Estado guardado en",
|
||||||
|
"error": "Error",
|
||||||
|
"failed_to_save_state": "No se pudo guardar el estado.",
|
||||||
|
"state_loaded_from": "Estado cargado desde",
|
||||||
|
"info": "Información",
|
||||||
|
"no_saved_state_found": "No se encontró estado guardado.",
|
||||||
|
"automatic_chip_detection": "Detección automática de fichas",
|
||||||
|
"manual_chip_adjustment": "Ajuste manual de fichas",
|
||||||
|
"save_and_load": "Guardar & Cargar",
|
||||||
|
"save_slot_1": "Guardar\nSlot 1",
|
||||||
|
"save_slot_2": "Guardar\nSlot 2",
|
||||||
|
"load_slot_1": "Cargar\nSlot 1",
|
||||||
|
"load_slot_2": "Cargar\nSlot 2",
|
||||||
|
"please_select_valid_buyin": "Por favor seleccione una cantidad de buy-in válida"
|
||||||
|
}
|
||||||
|
}
|
22
i18n/i18n.ts
Normal file
22
i18n/i18n.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import i18n from "i18next";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import * as Localization from "expo-localization";
|
||||||
|
import en from "./en.json";
|
||||||
|
import es from "./es.json";
|
||||||
|
const resources = {
|
||||||
|
en,
|
||||||
|
es,
|
||||||
|
};
|
||||||
|
|
||||||
|
const detectedLanguage = Localization.locale.split("-")[0] || "en";
|
||||||
|
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
resources,
|
||||||
|
lng: detectedLanguage,
|
||||||
|
fallbackLng: "en",
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
180
package-lock.json
generated
180
package-lock.json
generated
@ -21,20 +21,22 @@
|
|||||||
"expo-haptics": "14.0.1",
|
"expo-haptics": "14.0.1",
|
||||||
"expo-image-picker": "16.0.6",
|
"expo-image-picker": "16.0.6",
|
||||||
"expo-linking": "7.0.5",
|
"expo-linking": "7.0.5",
|
||||||
|
"expo-localization": "~16.0.1",
|
||||||
"expo-router": "4.0.17",
|
"expo-router": "4.0.17",
|
||||||
"expo-splash-screen": "0.29.22",
|
"expo-splash-screen": "0.29.22",
|
||||||
"expo-status-bar": "2.0.1",
|
"expo-status-bar": "2.0.1",
|
||||||
"expo-symbols": "0.2.2",
|
"expo-symbols": "0.2.2",
|
||||||
"expo-system-ui": "4.0.8",
|
"expo-system-ui": "4.0.8",
|
||||||
"expo-web-browser": "14.0.2",
|
"expo-web-browser": "14.0.2",
|
||||||
|
"i18next": "^24.2.2",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
|
"react-i18next": "^15.4.1",
|
||||||
"react-native": "0.76.7",
|
"react-native": "0.76.7",
|
||||||
"react-native-gesture-handler": "2.20.2",
|
"react-native-gesture-handler": "2.20.2",
|
||||||
"react-native-reanimated": "3.16.7",
|
"react-native-reanimated": "3.16.7",
|
||||||
"react-native-safe-area-context": "4.12.0",
|
"react-native-safe-area-context": "4.12.0",
|
||||||
"react-native-screens": "4.4.0",
|
"react-native-screens": "4.4.0",
|
||||||
"react-native-vector-icons": "^10.2.0",
|
|
||||||
"react-native-web": "0.19.13",
|
"react-native-web": "0.19.13",
|
||||||
"react-native-webview": "13.12.5"
|
"react-native-webview": "13.12.5"
|
||||||
},
|
},
|
||||||
@ -8238,6 +8240,19 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-localization": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-kUrXiV/Pq9r7cG+TMt+Qa49IUQ9Y/czVwen4hmiboTclTopcWdIeCzYZv6JGtufoPpjEO9vVx1QJrXYl9V2u0Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"rtl-detect": "^1.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*",
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-modules-autolinking": {
|
"node_modules/expo-modules-autolinking": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.0.8.tgz",
|
||||||
@ -9337,6 +9352,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/html-parse-stringify": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"void-elements": "3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
@ -9406,6 +9430,37 @@
|
|||||||
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "24.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.2.tgz",
|
||||||
|
"integrity": "sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
@ -14291,6 +14346,28 @@
|
|||||||
"react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0"
|
"react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-i18next": {
|
||||||
|
"version": "15.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz",
|
||||||
|
"integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.0",
|
||||||
|
"html-parse-stringify": "^3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"i18next": ">= 23.2.3",
|
||||||
|
"react": ">= 16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
@ -14446,92 +14523,6 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-native-vector-icons": {
|
|
||||||
"version": "10.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz",
|
|
||||||
"integrity": "sha512-n5HGcxUuVaTf9QJPs/W22xQpC2Z9u0nb0KgLPnVltP8vdUvOp6+R26gF55kilP/fV4eL4vsAHUqUjewppJMBOQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"prop-types": "^15.7.2",
|
|
||||||
"yargs": "^16.1.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"fa-upgrade.sh": "bin/fa-upgrade.sh",
|
|
||||||
"fa5-upgrade": "bin/fa5-upgrade.sh",
|
|
||||||
"fa6-upgrade": "bin/fa6-upgrade.sh",
|
|
||||||
"generate-icon": "bin/generate-icon.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-native-vector-icons/node_modules/cliui": {
|
|
||||||
"version": "7.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
|
||||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"string-width": "^4.2.0",
|
|
||||||
"strip-ansi": "^6.0.0",
|
|
||||||
"wrap-ansi": "^7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-native-vector-icons/node_modules/emoji-regex": {
|
|
||||||
"version": "8.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/react-native-vector-icons/node_modules/string-width": {
|
|
||||||
"version": "4.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"emoji-regex": "^8.0.0",
|
|
||||||
"is-fullwidth-code-point": "^3.0.0",
|
|
||||||
"strip-ansi": "^6.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-native-vector-icons/node_modules/strip-ansi": {
|
|
||||||
"version": "6.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": "^5.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-native-vector-icons/node_modules/yargs": {
|
|
||||||
"version": "16.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
|
||||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"cliui": "^7.0.2",
|
|
||||||
"escalade": "^3.1.1",
|
|
||||||
"get-caller-file": "^2.0.5",
|
|
||||||
"require-directory": "^2.1.1",
|
|
||||||
"string-width": "^4.2.0",
|
|
||||||
"y18n": "^5.0.5",
|
|
||||||
"yargs-parser": "^20.2.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-native-vector-icons/node_modules/yargs-parser": {
|
|
||||||
"version": "20.2.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
|
||||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-native-web": {
|
"node_modules/react-native-web": {
|
||||||
"version": "0.19.13",
|
"version": "0.19.13",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.tgz",
|
||||||
@ -15091,6 +15082,12 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rtl-detect": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@ -17220,6 +17217,15 @@
|
|||||||
"integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==",
|
"integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/void-elements": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-xmlserializer": {
|
"node_modules/w3c-xmlserializer": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
|
||||||
|
@ -37,15 +37,18 @@
|
|||||||
"expo-symbols": "0.2.2",
|
"expo-symbols": "0.2.2",
|
||||||
"expo-system-ui": "4.0.8",
|
"expo-system-ui": "4.0.8",
|
||||||
"expo-web-browser": "14.0.2",
|
"expo-web-browser": "14.0.2",
|
||||||
|
"i18next": "^24.2.2",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
|
"react-i18next": "^15.4.1",
|
||||||
"react-native": "0.76.7",
|
"react-native": "0.76.7",
|
||||||
"react-native-gesture-handler": "2.20.2",
|
"react-native-gesture-handler": "2.20.2",
|
||||||
"react-native-reanimated": "3.16.7",
|
"react-native-reanimated": "3.16.7",
|
||||||
"react-native-safe-area-context": "4.12.0",
|
"react-native-safe-area-context": "4.12.0",
|
||||||
"react-native-screens": "4.4.0",
|
"react-native-screens": "4.4.0",
|
||||||
"react-native-web": "0.19.13",
|
"react-native-web": "0.19.13",
|
||||||
"react-native-webview": "13.12.5"
|
"react-native-webview": "13.12.5",
|
||||||
|
"expo-localization": "~16.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.26.9",
|
"@babel/core": "7.26.9",
|
||||||
|
Loading…
Reference in New Issue
Block a user