feat(i18n): add internationalization with i18next and expo-localization

This commit is contained in:
2026-08-08 10:09:30 +02:00
parent 408a2fa10c
commit 9946e78858
81 changed files with 1820 additions and 125 deletions
+17 -14
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { View, Text, ScrollView } from "react-native";
import { useTranslation } from "react-i18next";
import { getWeeklyStats, getTotalStats, getPersonalBests } from "@db/operations";
import { formatDistance } from "@utils/formatDistance";
import { formatTime } from "@utils/formatTime";
@@ -24,6 +25,8 @@ type PersonalBest = {
};
export function StatsScreen() {
const { t } = useTranslation("history");
const { t: tc } = useTranslation("common");
const [weekly, setWeekly] = useState<WeeklyStats | null>(null);
const [total, setTotal] = useState<TotalStats | null>(null);
const [bests, setBests] = useState<PersonalBest[]>([]);
@@ -45,79 +48,79 @@ export function StatsScreen() {
return (
<ScrollView className="flex-1 bg-white p-4">
<Text className="text-2xl font-bold text-slate-900 mb-6">Statistics</Text>
<Text className="text-2xl font-bold text-slate-900 mb-6">{t("statistics")}</Text>
<Text className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
This Week
{t("thisWeek")}
</Text>
<View className="bg-sky-50 rounded-2xl p-4 mb-6">
{weekly ? (
<View className="flex-row justify-between">
<View className="items-center flex-1">
<Text className="text-2xl font-bold text-sky-600">{weekly.count}</Text>
<Text className="text-xs text-slate-500">Workouts</Text>
<Text className="text-xs text-slate-500">{tc("workouts")}</Text>
</View>
<View className="items-center flex-1">
<Text className="text-2xl font-bold text-sky-600">
{formatDistance(weekly.totalDistance)}
</Text>
<Text className="text-xs text-slate-500">Distance</Text>
<Text className="text-xs text-slate-500">{tc("distance")}</Text>
</View>
<View className="items-center flex-1">
<Text className="text-2xl font-bold text-sky-600">
{formatTime(weekly.totalDuration)}
</Text>
<Text className="text-xs text-slate-500">Duration</Text>
<Text className="text-xs text-slate-500">{tc("duration")}</Text>
</View>
</View>
) : (
<Text className="text-slate-400 text-center py-4">No data yet</Text>
<Text className="text-slate-400 text-center py-4">{t("noDataYet")}</Text>
)}
</View>
<Text className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
All Time
{t("allTime")}
</Text>
<View className="bg-slate-50 rounded-2xl p-4 mb-6">
{total ? (
<View className="flex-row justify-between">
<View className="items-center flex-1">
<Text className="text-2xl font-bold text-slate-700">{total.totalWorkouts}</Text>
<Text className="text-xs text-slate-500">Workouts</Text>
<Text className="text-xs text-slate-500">{tc("workouts")}</Text>
</View>
<View className="items-center flex-1">
<Text className="text-2xl font-bold text-slate-700">
{formatDistance(total.totalDistance)}
</Text>
<Text className="text-xs text-slate-500">Distance</Text>
<Text className="text-xs text-slate-500">{tc("distance")}</Text>
</View>
<View className="items-center flex-1">
<Text className="text-2xl font-bold text-slate-700">
{formatTime(total.totalDuration)}
</Text>
<Text className="text-xs text-slate-500">Duration</Text>
<Text className="text-xs text-slate-500">{tc("duration")}</Text>
</View>
</View>
) : (
<Text className="text-slate-400 text-center py-4">No data yet</Text>
<Text className="text-slate-400 text-center py-4">{t("noDataYet")}</Text>
)}
</View>
{bests.length > 0 && (
<>
<Text className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
Personal Bests
{t("personalBests")}
</Text>
{bests.map((best) => (
<View
key={best.distance}
className="bg-green-50 rounded-2xl p-4 mb-2 flex-row justify-between items-center"
>
<Text className="text-green-700 font-medium">{best.distance}m</Text>
<Text className="text-green-700 font-medium">{tc("meter", { count: best.distance })}</Text>
<View className="items-end">
<Text className="text-green-800 font-bold">{formatTime(best.bestTime)}</Text>
{best.bestSwolf && (
<Text className="text-green-600 text-xs">SWOLF: {best.bestSwolf}</Text>
<Text className="text-green-600 text-xs">{t("swolf", { value: best.bestSwolf })}</Text>
)}
</View>
</View>