From 28e81894ce868ecbf227340557e5ca3663681d87 Mon Sep 17 00:00:00 2001 From: Sebastien Riviere Date: Sat, 21 Feb 2026 02:46:58 +0100 Subject: [PATCH] Working EN traduction + wait page + permission page --- .../traque_14_12_25/compte_rendu.txt | 3 +- .../traque_20_09_25/compte_rendu.txt | 2 +- mobile/docs/TODO.md | 9 +- .../app/(auth)/location-permission.jsx | 36 +++++++- mobile/traque-app/app/(auth)/login.jsx | 22 +++-- mobile/traque-app/app/(game)/play.jsx | 8 +- mobile/traque-app/app/(game)/wait.jsx | 58 +++++++++++-- mobile/traque-app/app/_layout.jsx | 35 +++++++- mobile/traque-app/src/assets/images/flag.png | Bin 0 -> 12611 bytes .../traque-app/src/assets/images/language.png | Bin 0 -> 5683 bytes mobile/traque-app/src/assets/images/team.png | Bin 0 -> 11127 bytes .../traque-app/src/components/game/Header.jsx | 20 ++--- .../src/components/game/MapLayers.jsx | 2 +- .../src/components/game/TargetInfoDrawer.jsx | 17 ++-- .../src/components/game/TeamStats.jsx | 14 +-- .../traque-app/src/components/game/Toasts.jsx | 8 +- mobile/traque-app/src/hooks/usePickImage.jsx | 2 +- mobile/traque-app/src/i18n/locales/en.json | 82 +++++++++++++++++- mobile/traque-app/src/i18n/locales/fr.json | 72 +++++++++------ 19 files changed, 299 insertions(+), 91 deletions(-) create mode 100644 mobile/traque-app/src/assets/images/flag.png create mode 100644 mobile/traque-app/src/assets/images/language.png create mode 100644 mobile/traque-app/src/assets/images/team.png diff --git a/docs/historique/traque_14_12_25/compte_rendu.txt b/docs/historique/traque_14_12_25/compte_rendu.txt index 084179a..94fd50c 100644 --- a/docs/historique/traque_14_12_25/compte_rendu.txt +++ b/docs/historique/traque_14_12_25/compte_rendu.txt @@ -9,7 +9,7 @@ Météo : 8°C, pas de pluie, brouillard (visibilité <400m), vent 10km/h Problèmes [ ] Une équipe perdait sans arrêt la connection avec le serveur -[ ] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif) +[x] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif) [x] La photo d'une des équipes ne parvenait pas jusqu'au serveur. Tout semblait normal pour l'équipe. (Potentiellement un problème de conversion : 046512 -> 46512) [x] Le focus sur une équipe dans la page principale des admins est à revoir. Par exemple, le zoom seul devrait désactiver le focus @@ -19,7 +19,6 @@ voyants des équipes encore en jeu soit mis en avant. [x] La visibilité des éléments de la map en calque satellite est mauvaise. Il faut revoir les couleurs. [x] Les zones polygonales s'affichent mal sur la version prod de l'app mobile. Les anciennes n'ont pas l'air de disparaitre. [x] La zone de jeu (en rouge) est mal comprise, les joueurs évitent de rester dedans alors qu'ils ont le droit. -[ ] L'application n'est pas pleinement intuitive, par exemple le bouton pour actualiser sa position ou alors les icônes des stats. À faire diff --git a/docs/historique/traque_20_09_25/compte_rendu.txt b/docs/historique/traque_20_09_25/compte_rendu.txt index 658c7ac..ffed170 100644 --- a/docs/historique/traque_20_09_25/compte_rendu.txt +++ b/docs/historique/traque_20_09_25/compte_rendu.txt @@ -8,7 +8,7 @@ Cette traque est la première à se faire avec le nouveau site web et la version Problèmes [ ] Une équipe perdait sans arrêt la connection avec le serveur -[ ] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif) +[x] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif) [ ] Aux deux tiers de la partie toutes les équipes ont été déconnectée en même temps mais ont pu se reconnecter. Ça s'est produit sans raisons apparentes [ ] Une équipe avait deux téléphones connectés sauf que le premier ne marchait pas bien. Par ailleurs, l'équipe disaient n'avoir diff --git a/mobile/docs/TODO.md b/mobile/docs/TODO.md index c830824..401e21b 100644 --- a/mobile/docs/TODO.md +++ b/mobile/docs/TODO.md @@ -11,18 +11,13 @@ - [x] Centrer la map sur la position à l'ouverture + bouton centrage - [x] Indiquer que l'équipe est hors zone. - [x] Mettre les stats dans le tiroir (distance, temps, vitesse moy, nb captures, nb envoi) +- [x] Traduction anglaise - [ ] Implémenter des notifs lors du background (hors zone, position envoyée, update zone) -- [ ] Ajouter les logs de la partie - [ ] Créer le menu paramètre (idées de section : langue, photo équipe, notifs, mode sombre, unitées) - [ ] Afficher la trajectoire passée sur la carte (désactivable) - [ ] Afficher les évènements passés sur la carte (captures, envois, départ) (désactivable) - [ ] Permettre le changement du style de la carte (schéma, satellite, relief etc) - [ ] Ajouter imprécision de la position au besoin (comme sur google maps) - [ ] Synchroniser les horloges sur l'interface -- [ ] Avoir un récap des évènement de la partie +- [ ] Améliorer l'intuitivité de l'interface (bouton d'actualisation / stats) - [ ] Publier sur le playstore - -## Autres idées - -- Améliorer l'accessibilité (traduction anglaise notamment). -- Améliorer l'UI. diff --git a/mobile/traque-app/app/(auth)/location-permission.jsx b/mobile/traque-app/app/(auth)/location-permission.jsx index 5ec6f86..0bfb323 100644 --- a/mobile/traque-app/app/(auth)/location-permission.jsx +++ b/mobile/traque-app/app/(auth)/location-permission.jsx @@ -1,7 +1,39 @@ -import { Text } from 'react-native'; +//React +import { StyleSheet, Text, View, Image } from 'react-native'; +import { useTranslation } from 'react-i18next'; const LocationPermission = () => { - return {"Veuillez activer la géolocalisation en arrière plan dans les paramètres, puis relancez l'application."}; + const { t } = useTranslation(); + + return (<> + + + {t("location-permission.title")} + {t("location-permission.subtitle")} + + ); }; export default LocationPermission; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: "center", + justifyContent: "center", + padding: 20, + gap: 20 + }, + image: { + width: 150, + height: 150, + marginTop: -100, + }, + title: { + fontSize: 20, + fontWeight: "bold", + }, + subtitle: { + fontSize: 15, + }, +}); diff --git a/mobile/traque-app/app/(auth)/login.jsx b/mobile/traque-app/app/(auth)/login.jsx index 30bd9b8..2ef8ce6 100644 --- a/mobile/traque-app/app/(auth)/login.jsx +++ b/mobile/traque-app/app/(auth)/login.jsx @@ -1,6 +1,6 @@ // React import { useState } from 'react'; -import { ScrollView, View, Text, StyleSheet, Image, Alert, TouchableHighlight } from 'react-native'; +import { Keyboard, ScrollView, View, Text, StyleSheet, Image, Alert, TouchableHighlight } from 'react-native'; import { useTranslation } from 'react-i18next'; // Components import { TouchableImage } from '@/components/common/Image'; @@ -28,7 +28,9 @@ const Login = () => { const regex = /^\d{6}$/; if (!regex.test(teamId)) { - setTimeout(() => Alert.alert(t("error.title"), t("error.invalid_team_id")), 100); + Keyboard.dismiss(); + Alert.alert(t("error.default.title"), t("error.default.invalid_team_id")); + setIsSubmitting(false); return; } @@ -39,10 +41,12 @@ const Login = () => { uploadTeamImage(teamId, image?.uri); setTeamId(""); } else { - setTimeout(() => Alert.alert(t("error.title"), t("error.unknown_team_id")), 100); + Keyboard.dismiss(); + Alert.alert(t("error.default.title"), t("error.default.unknown_team_id")); } } catch (error) { - setTimeout(() => Alert.alert(t("error.title"), t("error.server_connection")), 100); + Keyboard.dismiss(); + Alert.alert(t("error.default.title"), t("error.default.server_connection")); } finally { setIsSubmitting(false); } @@ -53,20 +57,20 @@ const Login = () => { - {t("index.header.title")} + {t("login.header.title")} - + - {t("index.form.image_label")} - {t("index.form.image_sublabel")} + {t("login.form.image_label")} + {t("login.form.image_sublabel")} - {isSubmitting ? "..." : t("index.form.validate_button")} + {isSubmitting ? "..." : t("login.form.validate_button")} diff --git a/mobile/traque-app/app/(game)/play.jsx b/mobile/traque-app/app/(game)/play.jsx index f100231..086d6ae 100644 --- a/mobile/traque-app/app/(game)/play.jsx +++ b/mobile/traque-app/app/(game)/play.jsx @@ -35,8 +35,8 @@ const Play = () => {
- - + + @@ -49,8 +49,8 @@ const Play = () => { - Alert.alert(t("interface.map.previous_marker_title"), t("interface.map.previous_marker_description"))} /> - Alert.alert(t("interface.map.enemy_marker_title"), t("interface.map.enemy_marker_description"))} /> + Alert.alert(t("play.map.previous_marker_title"), t("play.map.previous_marker_description"))} /> + Alert.alert(t("play.map.enemy_marker_title"), t("play.map.enemy_marker_description"))} /> diff --git a/mobile/traque-app/app/(game)/wait.jsx b/mobile/traque-app/app/(game)/wait.jsx index fadc55b..0625eb6 100644 --- a/mobile/traque-app/app/(game)/wait.jsx +++ b/mobile/traque-app/app/(game)/wait.jsx @@ -1,16 +1,35 @@ // React -import { View, Text, StyleSheet } from 'react-native'; +import { View, Text, StyleSheet, Image } from 'react-native'; +import { useTranslation } from 'react-i18next'; // Components import { Header } from '@/components/game/Header'; // Constants import { COLORS } from '@/constants'; const Wait = () => { + const { t } = useTranslation(); + return ( - -
- Veuillez patienter, la partie va bientôt commencer ! +
+ + {t("wait.title")} + + + {t("wait.placement_rule")} + + + {t("wait.capture_rule")} + + + + + {t("wait.zone_rule")} + + + {t("wait.team_rule")} + + ); @@ -22,10 +41,35 @@ const styles = StyleSheet.create({ globalContainer: { backgroundColor: COLORS.background, flex: 1, + padding: 20, }, - topContainer: { - width: '100%', + rulesContainer: { + flex: 1, alignItems: 'center', - padding: 15, + gap: 30 + }, + title: { + backgroundColor: "white", + textAlign: 'center', + fontSize: 30, + fontWeight: "bold", + borderWidth: 2, + borderRadius: 10, + padding: 10, + }, + section: { + width: '100%', + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 20, + }, + image: { + width: 100, + height: 100, + }, + description: { + flex: 1, + } }); diff --git a/mobile/traque-app/app/_layout.jsx b/mobile/traque-app/app/_layout.jsx index 71f171e..b98e677 100644 --- a/mobile/traque-app/app/_layout.jsx +++ b/mobile/traque-app/app/_layout.jsx @@ -1,17 +1,22 @@ // React import { useEffect } from 'react'; +import { View, StyleSheet } from 'react-native'; +import { useTranslation } from 'react-i18next'; // Expo import { Slot, useRouter, usePathname } from 'expo-router'; +// Components +import { IconButton } from '@/components/common/IconButton'; // Contexts import { AuthProvider } from "@/contexts/authContext"; import { TeamProvider } from "@/contexts/teamContext"; +// Hook +import { useUserState } from '@/hooks/useUserState'; // Services import { startLocationTracking , stopLocationTracking } from '@/services/tasks/backgroundLocation'; // Constants import { USER_STATE } from '@/constants'; // Traduction import '@/i18n/config'; -import { useUserState } from '@/hooks/useUserState'; const NavigationManager = () => { const router = useRouter(); @@ -72,15 +77,43 @@ const NavigationManager = () => { return null; }; +const Language = () => { + const { i18n } = useTranslation(); + + const toggleLanguage = () => { + i18n.changeLanguage(i18n.language === 'fr' ? 'en' : 'fr'); + }; + + return ( + + + + ); +}; + const RootLayout = () => { return ( + ); }; export default RootLayout; + +const styles = StyleSheet.create({ + languageButton: { + position: 'absolute', + top: 0, + right: 0, + backgroundColor: "rgb(126, 182, 199)", + borderBottomLeftRadius: 20, + padding: 5, + justifyContent: 'center', + alignItems: 'center', + } +}); diff --git a/mobile/traque-app/src/assets/images/flag.png b/mobile/traque-app/src/assets/images/flag.png new file mode 100644 index 0000000000000000000000000000000000000000..8981a76a87907013a85dec238f464526a726b457 GIT binary patch literal 12611 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 Y%aBxUMkCPh_i;xfsJULf4l?0?0jz^raR2}S literal 0 HcmV?d00001 diff --git a/mobile/traque-app/src/components/game/Header.jsx b/mobile/traque-app/src/components/game/Header.jsx index f6674e6..c9d0699 100644 --- a/mobile/traque-app/src/components/game/Header.jsx +++ b/mobile/traque-app/src/components/game/Header.jsx @@ -1,5 +1,6 @@ // React -import { View, Text, Alert, StyleSheet } from 'react-native'; +import { View, Text, StyleSheet } from 'react-native'; +import { useTranslation } from 'react-i18next'; // Contexts import { useAuth } from '@/contexts/authContext'; import { useTeam } from '@/contexts/teamContext'; @@ -7,18 +8,16 @@ import { useTeam } from '@/contexts/teamContext'; import { IconButton } from '@/components/common/IconButton'; export const Header = () => { + const { t } = useTranslation(); const { logout } = useAuth(); const { teamInfos } = useTeam(); const { name } = teamInfos; return ( - - - Alert.alert("Settings")} /> - + - {name ?? "Inconnue"} + {name ?? t("common.no_value")} ); @@ -27,17 +26,12 @@ export const Header = () => { const styles = StyleSheet.create({ container: { width: '100%', - alignItems: 'center' - }, - buttonsContainer: { - width: "100%", - flexDirection: "row", - justifyContent: 'space-between' }, nameContainer: { width: '100%', alignItems: 'center', - justifyContent: 'center' + justifyContent: 'center', + marginBottom: 20 }, name: { fontSize: 36, diff --git a/mobile/traque-app/src/components/game/MapLayers.jsx b/mobile/traque-app/src/components/game/MapLayers.jsx index 222969a..0b14277 100644 --- a/mobile/traque-app/src/components/game/MapLayers.jsx +++ b/mobile/traque-app/src/components/game/MapLayers.jsx @@ -13,7 +13,7 @@ export const StartZone = () => { const { startingArea } = teamInfos; return useMemo(() => { - if (startingArea) return null; + if (!startingArea) return null; return ( { emitCapture(enemyCaptureCode) .then((response) => { if (response.hasCaptured) { - Alert.alert("Bravo !", "Vous avez réussi à capturer votre cible. Une nouvelle cible vient de vous être attribuée."); + Keyboard.dismiss(); + Alert.alert(t("info.success.title"), t("info.success.capture_success")); setEnemyCaptureCode(""); } else { - Alert.alert("Échec !", "Le code que vous venez de rentrer n'est pas celui de votre cible."); + Keyboard.dismiss(); + Alert.alert(t("info.failure.title"), t("info.failure.capture_failure")); } }) .catch(() => { - Alert.alert(t("error.title"), t("error.server_connection")); + Keyboard.dismiss(); + Alert.alert(t("error.default.title"), t("error.default.server_connection")); }) .finally(() => setIsCapturing(false)); }; @@ -46,16 +49,16 @@ export const TargetInfoDrawer = ({ height }) => { return ( - {t("interface.drawer.capture_code", {name: name ?? t("general.no_value"), code: String(captureCode).padStart(4,"0")})} + {t("play.drawer.capture_code", {name: name ?? t("common.no_value"), code: String(captureCode).padStart(4,"0")})} - {t("interface.drawer.target_name", {name: enemyName ?? t("general.no_value")})} + {t("play.drawer.target_name", {name: enemyName ?? t("common.no_value")})} - + diff --git a/mobile/traque-app/src/components/game/TeamStats.jsx b/mobile/traque-app/src/components/game/TeamStats.jsx index 730742f..7a2abf3 100644 --- a/mobile/traque-app/src/components/game/TeamStats.jsx +++ b/mobile/traque-app/src/components/game/TeamStats.jsx @@ -10,8 +10,10 @@ import { useTimeSinceSeconds } from '@/hooks/useTimeDelta'; import { secondsToHHMMSS } from '@/utils/functions'; const Stat = ({ children, source, description }) => { + const { t } = useTranslation(); + return ( - Alert.alert("Info", description) : null}> + Alert.alert(t("info.default.title"), description) : null}> {children} @@ -36,13 +38,13 @@ export const TeamStats = () => { return ( - {Math.floor(distance / 100) / 10}km - {secondsToHHMMSS((finishDate ? Math.floor((finishDate - startDate) / 1000) : timeSinceGameStart))} - {avgSpeed}km/h + {Math.floor(distance / 100) / 10}km + {secondsToHHMMSS((finishDate ? Math.floor((finishDate - startDate) / 1000) : timeSinceGameStart))} + {avgSpeed}km/h - {nCaptures} - {nSentLocation} + {nCaptures} + {nSentLocation} ); diff --git a/mobile/traque-app/src/components/game/Toasts.jsx b/mobile/traque-app/src/components/game/Toasts.jsx index a3eb814..b93c80f 100644 --- a/mobile/traque-app/src/components/game/Toasts.jsx +++ b/mobile/traque-app/src/components/game/Toasts.jsx @@ -22,28 +22,28 @@ export const Toasts = () => { { condition: userState === USER_STATE.PLACEMENT, id: 'placement', - text: ready ? t("interface.placed") : t("interface.not_placed"), + text: ready ? t("play.toast.placed") : t("play.toast.not_placed"), toastColor: ready ? "rgb(25, 165, 25)" : "rgb(204, 51, 51)" , textColor: "white" }, { condition: userState === USER_STATE.PLAYING && !outOfZone && enemyHasHandicap, id: 'enemy_revealed', - text: t("interface.enemy_position_revealed"), + text: t("play.toast.enemy_position_revealed"), toastColor: "white", textColor: "black" }, { condition: userState === USER_STATE.PLAYING && outOfZone && hasHandicap, id: 'out_of_zone', - text: `${t("interface.go_in_zone")}\n${t("interface.team_position_revealed")}`, + text: `${t("play.toast.go_in_zone")}\n${t("play.toast.team_position_revealed")}`, toastColor: "white", textColor: "black" }, { condition: userState === USER_STATE.PLAYING && outOfZone && !hasHandicap, id: 'has_handicap', - text: `${t("interface.go_in_zone")}\n${t("interface.out_of_zone_message", {time: secondsToMMSS(outOfZoneTimeLeft)})}`, + text: `${t("play.toast.go_in_zone")}\n${t("play.toast.out_of_zone_message", {time: secondsToMMSS(outOfZoneTimeLeft)})}`, toastColor: "white", textColor: "black" } diff --git a/mobile/traque-app/src/hooks/usePickImage.jsx b/mobile/traque-app/src/hooks/usePickImage.jsx index 5809b2b..28c0a39 100644 --- a/mobile/traque-app/src/hooks/usePickImage.jsx +++ b/mobile/traque-app/src/hooks/usePickImage.jsx @@ -33,7 +33,7 @@ export const usePickImage = () => { } } catch (error) { console.error('Error picking image;', error); - Alert.alert(t("error.title"), t("error.image_selection")); + Alert.alert(t("error.default.title"), t("error.default.image_selection")); } }, [t]); diff --git a/mobile/traque-app/src/i18n/locales/en.json b/mobile/traque-app/src/i18n/locales/en.json index 0db3279..db2e9da 100644 --- a/mobile/traque-app/src/i18n/locales/en.json +++ b/mobile/traque-app/src/i18n/locales/en.json @@ -1,3 +1,83 @@ { - + "common": { + "no_value": "Unavailable" + }, + "location-permission": { + "title": "Please enable background location in settings and restart the app.", + "subtitle": "Each team's location must be known by the server and organizers to ensure the game runs smoothly." + }, + "login": { + "header": { + "title": "LA TRAQUE" + }, + "form": { + "team_id_input": "Team ID", + "image_label": "Tap to change team photo", + "image_sublabel": "Upper body must be visible", + "validate_button": "Validate" + } + }, + "wait": { + "title": "Rules Reminder !", + "placement_rule": "Go to your starting zone and wait for the game to begin.", + "capture_rule": "Track your target using their photo and the positions they reveal. To obtain your target's last known position, you must reveal your own. Once captured, enter their code in the app.", + "zone_rule": "Move on foot while making sure to stay within the game zone to avoid penalties ! If you stay outside the zone for too long, your interface will be disabled and your hunter will know your exact position.", + "team_rule": "Set up team strategies ! But always stay within earshot, do not communicate with other teams, and remain in public areas." + }, + "play": { + "info": { + "zone_reduction_label": "Zone reduction in", + "send_position_label": "Position sent in" + }, + "toast": { + "placed": "Placed", + "not_placed": "Not placed", + "enemy_position_revealed": "Enemy position continuously revealed !", + "go_in_zone": "Return to the zone !", + "team_position_revealed": "Position continuously revealed.", + "out_of_zone_message": "Handicap in {{time}}" + }, + "map": { + "previous_marker_title": "Position sent", + "previous_marker_description": "This is your last position known by the server", + "enemy_marker_title": "Enemy position", + "enemy_marker_description": "This is the last known position of your enemies" + }, + "drawer": { + "capture_code": "{{name}}'s code : {{code}}", + "target_name": "Target ({{name}})", + "target_code_input": "Target code", + "stat_distance_label": "Distance traveled", + "stat_time_label": "Elapsed time (HH:MM:SS)", + "stat_speed_label": "Average speed", + "stat_capture_label": "Total captures by your team", + "stat_reveal_label": "Total times your position was sent" + } + }, + "info": { + "default": { + "title": "Info" + }, + "succes": { + "title": "Success !", + "capture_success": "You have successfully captured your target. A new target has been assigned to you." + }, + "failure": { + "title": "Failure...", + "capture_failure": "The capture failed. Please check the code and try again." + } + }, + "error": { + "default": { + "title": "Error", + "invalid_team_id": "Please enter a valid team ID.", + "unknown_team_id": "Unknown team ID.", + "server_connection": "Server connection failed.", + "image_selection": "An error occurred during image selection." + }, + "permission": { + "title": "Permission denied", + "storage_acces": "Enable storage or gallery access in settings." + } + } } diff --git a/mobile/traque-app/src/i18n/locales/fr.json b/mobile/traque-app/src/i18n/locales/fr.json index a7ad7cc..bda3fd3 100644 --- a/mobile/traque-app/src/i18n/locales/fr.json +++ b/mobile/traque-app/src/i18n/locales/fr.json @@ -1,8 +1,12 @@ { - "general": { + "common": { "no_value": "Indisponible" }, - "index": { + "location-permission": { + "title": "Veuillez activer la localisation en arrière plan dans les paramètres et relancer l'application.", + "subtitle": "La localisation de chaque équipe doit être connue par le serveur et les organisateurs pour veiller au bon déroulement du jeu." + }, + "login": { "header": { "title": "LA TRAQUE" }, @@ -13,24 +17,27 @@ "validate_button": "Valider" } }, - "interface": { - "placed": "Placé", - "not_placed": "Non placé", - "zone_reduction_label": "Réduction de la zone dans", - "send_position_label": "Position envoyée dans", - "enemy_position_revealed": "Position ennemie révélée en continue !", - "waiting_default_message": "Préparation de la partie", - "placement_default_message": "Phase de placement", - "go_in_zone": "Retournez dans la zone !", - "team_position_revealed": "Position révélée en continue.", - "out_of_zone_message": "Handicap dans {{time}}", - "playing_message": "La partie est en cours", - "winner_message": "Vous avez gagné !", - "loser_message": "Vous avez perdu...", - "captured_message": "Vous avez été éliminé...", + "wait": { + "title": "Rappel des règles !", + "placement_rule": "Rejoignez votre zone de départ et attendez le début de la partie.", + "capture_rule": "Traquez votre cible grâce à sa photo et aux positions qu'elle révèle. Pour obtenir la dernière position connue de votre cible, vous devrez révéler la vôtre. Une fois capturée, entrez son code dans l'application.", + "zone_rule": "Déplacez-vous à pied tout en veillant à rester dans la zone de jeu pour ne pas être pénalisé ! Si vous restez trop longtemps en dehors de la zone, votre interface sera désactivée et votre chasseur connaîtra précisément votre position.", + "team_rule": "Mettez en place des stratégies d'équipe ! Mais restez toujours à portée de voix, ne communiquez pas avec les autres équipes et restez dans les lieux publics." + }, + "play": { + "info": { + "zone_reduction_label": "Réduction de la zone dans", + "send_position_label": "Position envoyée dans" + }, + "toast": { + "placed": "Placé", + "not_placed": "Non placé", + "enemy_position_revealed": "Position ennemie révélée en continue !", + "go_in_zone": "Retournez dans la zone !", + "team_position_revealed": "Position révélée en continue.", + "out_of_zone_message": "Handicap dans {{time}}" + }, "map": { - "team_marker_title": "Position actuelle", - "team_marker_description": "Ceci est votre position", "previous_marker_title": "Position envoyée", "previous_marker_description": "Ceci est votre dernière position connue par le serveur", "enemy_marker_title": "Position ennemie", @@ -41,18 +48,33 @@ "target_name": "Cible ({{name}})", "target_code_input": "Code cible", "stat_distance_label": "Distance parcourue", - "stat_time_label": "Temps écoulé au format HH:MM:SS", + "stat_time_label": "Temps écoulé (HH:MM:SS)", "stat_speed_label": "Vitesse moyenne", "stat_capture_label": "Nombre total de captures par votre équipe", "stat_reveal_label": "Nombre total d'envois de votre position" } }, + "info": { + "default": { + "title": "Info" + }, + "succes": { + "title": "Bravo !", + "capture_success": "Vous avez réussi à capturer votre cible. Une nouvelle cible vient de vous être attribuée." + }, + "failure": { + "title": "Raté...", + "capture_failure": "La capture a échoué. Vérifiez le code et réessayez." + } + }, "error": { - "title": "Erreur", - "invalid_team_id": "Veuillez entrer un ID d'équipe valide.", - "unknown_team_id": "L'ID d'équipe est inconnu.", - "server_connection": "La connexion au serveur a échoué.", - "image_selection": "Une erreur est survenue lors de la sélection d'une image.", + "default": { + "title": "Erreur", + "invalid_team_id": "Veuillez entrer un ID d'équipe valide.", + "unknown_team_id": "L'ID d'équipe est inconnu.", + "server_connection": "La connexion au serveur a échoué.", + "image_selection": "Une erreur est survenue lors de la sélection d'une image." + }, "permission": { "title": "Permission refusée", "storage_acces": "Activez l'accès au stockage ou à la gallerie dans les paramètres."