feat(i18n): add internationalization with i18next and expo-localization
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { View, Text, Pressable, ActivityIndicator } from "react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import type { RootStackParamList } from "@navigation/types";
|
||||
@@ -20,6 +21,8 @@ type WorkoutWithSets = {
|
||||
};
|
||||
|
||||
export function PreSwimScreen() {
|
||||
const { t } = useTranslation("active-workout");
|
||||
const { t: tc } = useTranslation("common");
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const route = useRoute<RouteProp<RootStackParamList, "PreSwim">>();
|
||||
const [workout, setWorkout] = useState<WorkoutWithSets | null>(null);
|
||||
@@ -72,16 +75,16 @@ export function PreSwimScreen() {
|
||||
<View className="flex-1 bg-white p-4 items-center justify-center">
|
||||
<View className="bg-green-50 rounded-2xl p-6 items-center mb-6 w-full">
|
||||
<Text className="text-4xl mb-3">⌚</Text>
|
||||
<Text className="text-xl font-bold text-green-800 mb-2">Workout sent to Watch!</Text>
|
||||
<Text className="text-xl font-bold text-green-800 mb-2">{t("workoutSentToWatch")}</Text>
|
||||
<Text className="text-green-600 text-center">
|
||||
Open the SwimBuddy app on your Apple Watch to start tracking.
|
||||
{t("openWatchApp")}
|
||||
</Text>
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={() => navigation.popToTop()}
|
||||
className="bg-slate-900 rounded-2xl py-4 items-center w-full"
|
||||
>
|
||||
<Text className="text-white font-bold text-lg">Done</Text>
|
||||
<Text className="text-white font-bold text-lg">{tc("done")}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
@@ -92,16 +95,16 @@ export function PreSwimScreen() {
|
||||
<View className="flex-1 bg-white p-4 items-center justify-center">
|
||||
<View className="bg-red-50 rounded-2xl p-6 items-center mb-6 w-full">
|
||||
<Text className="text-4xl mb-3">⚠️</Text>
|
||||
<Text className="text-xl font-bold text-red-800 mb-2">Failed to send</Text>
|
||||
<Text className="text-xl font-bold text-red-800 mb-2">{t("failedToSend")}</Text>
|
||||
<Text className="text-red-600 text-center">
|
||||
Could not send workout to Watch. Starting without Watch instead.
|
||||
{t("couldNotSend")}
|
||||
</Text>
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={() => navigation.navigate("PostSwim", { workoutId: route.params.workoutId })}
|
||||
className="bg-sky-500 rounded-2xl py-4 items-center w-full"
|
||||
>
|
||||
<Text className="text-white font-bold text-lg">Start Without Watch</Text>
|
||||
<Text className="text-white font-bold text-lg">{t("startWithoutWatch")}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
@@ -111,16 +114,16 @@ export function PreSwimScreen() {
|
||||
<View className="flex-1 bg-white p-4">
|
||||
<View className="items-center mb-6">
|
||||
<Text className="text-2xl font-bold text-slate-900 mb-1">
|
||||
{workout.name || "Workout"}
|
||||
{workout.name || tc("workout")}
|
||||
</Text>
|
||||
<Text className="text-slate-500">
|
||||
{formatDistance(workout.totalDistance ?? 0)} · {workout.poolLength}m pool
|
||||
{formatDistance(workout.totalDistance ?? 0)} · {tc("pool", { length: workout.poolLength })}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="bg-sky-50 rounded-2xl p-4 mb-4 border border-sky-100">
|
||||
<Text className="text-xs font-semibold text-sky-700 mb-3 uppercase tracking-wide">
|
||||
Sets
|
||||
{tc("sets")}
|
||||
</Text>
|
||||
{workout.sets.map((set, i) => (
|
||||
<View
|
||||
@@ -132,7 +135,7 @@ export function PreSwimScreen() {
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: STROKE_COLORS[set.stroke] ?? "#94a3b8" }}
|
||||
/>
|
||||
<Text className="text-slate-700 font-medium">{set.stroke}</Text>
|
||||
<Text className="text-slate-700 font-medium">{tc(`stroke.${set.stroke.toLowerCase()}`)}</Text>
|
||||
</View>
|
||||
<Text className="text-slate-600">
|
||||
{set.repCount}×{set.distancePerRep}m
|
||||
@@ -143,9 +146,9 @@ export function PreSwimScreen() {
|
||||
|
||||
<View className="bg-slate-50 rounded-2xl p-4 mb-6">
|
||||
<View className="flex-row justify-between mb-2">
|
||||
<Text className="text-slate-500">Rest</Text>
|
||||
<Text className="text-slate-500">{t("rest")}</Text>
|
||||
<Text className="text-slate-900 font-medium">
|
||||
{workout.sets[0]?.restDuration ?? 0}s between sets
|
||||
{t("betweenSets", { duration: workout.sets[0]?.restDuration ?? 0 })}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -158,12 +161,12 @@ export function PreSwimScreen() {
|
||||
/>
|
||||
<Text className="text-sm text-slate-600">
|
||||
{isReady
|
||||
? "Apple Watch ready"
|
||||
? t("watchStatus.ready")
|
||||
: isPaired
|
||||
? isInstalled
|
||||
? "Watch paired but not in range"
|
||||
: "Watch paired, app not installed"
|
||||
: "No Watch paired"}
|
||||
? t("watchStatus.pairedNotInRange")
|
||||
: t("watchStatus.pairedNotInstalled")
|
||||
: t("watchStatus.noWatch")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -178,15 +181,15 @@ export function PreSwimScreen() {
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
<Text className="text-white font-bold text-lg">
|
||||
{isReady ? "Send to Watch & Start" : "Start Without Watch"}
|
||||
{isReady ? t("sendToWatchAndStart") : t("startWithoutWatch")}
|
||||
</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
|
||||
<Text className="text-slate-400 text-xs text-center mt-3">
|
||||
{isReady
|
||||
? "Workout will be synced to your Apple Watch"
|
||||
: "Workout will be tracked on this device"}
|
||||
? t("syncToWatch")
|
||||
: t("trackedOnDevice")}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user