Vous avez trouvé le personnage en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !
{$t.game.components.winPanel.winPrefix} {selectedCharacters.length} {attemptWord} !
{#if selectedCharacter.pictureUrl}
diff --git a/src/lib/components/YesterdayCharacter.svelte b/src/lib/components/YesterdayCharacter.svelte
index ca84248..ae8ef64 100644
--- a/src/lib/components/YesterdayCharacter.svelte
+++ b/src/lib/components/YesterdayCharacter.svelte
@@ -1,5 +1,6 @@
@@ -15,11 +16,11 @@
/>
{:else}
- Photo
+ {$t.game.components.yesterdayCharacter.photo}
{/if}
-
Personnage d'hier
+
{$t.game.components.yesterdayCharacter.title}
{yesterdayCharacter.name}
{#if yesterdayCharacter.epithets}
@@ -35,18 +36,18 @@
rel="noopener noreferrer"
class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto"
>
- Voir la page
+ {$t.game.components.yesterdayCharacter.openPage}
{:else}
- Photo
+ {$t.game.components.yesterdayCharacter.photo}
-
Personnage d'hier
-
Aucun personnage
-
Aucun personnage d'hier disponible
+
{$t.game.components.yesterdayCharacter.title}
+
{$t.game.components.yesterdayCharacter.none}
+
{$t.game.components.yesterdayCharacter.noneAvailable}
{/if}
diff --git a/src/lib/i18n/en.json b/src/lib/i18n/en.json
new file mode 100644
index 0000000..8c6b911
--- /dev/null
+++ b/src/lib/i18n/en.json
@@ -0,0 +1,227 @@
+{
+ "common": {
+ "language": "Language",
+ "selectLanguage": "Select Language",
+ "english": "English",
+ "french": "Français",
+ "german": "Deutsch",
+ "spanish": "Español"
+ },
+ "game": {
+ "home": {
+ "heroDescription": "Guess the character from pirate crews, marines, or the wider world. Every hint brings you closer to the treasure.",
+ "dailyTitle": "Daily Character",
+ "dailySubtitle": "A new mystery every 24 hours",
+ "dailyDescription": "Compare your guesses, unlock hints, and keep your streak alive.",
+ "dailyCta": "Start",
+ "infiniteTitle": "Infinite Mode",
+ "infiniteSubtitle": "Endless challenges",
+ "infiniteDescription": "Chain characters and chase your score. No limits, only fun.",
+ "infiniteCta": "Play",
+ "photoFallback": "Photo",
+ "yesterdayCharacter": "Yesterday's character",
+ "openPage": "Open page",
+ "noCharacter": "No character",
+ "noYesterdayCharacter": "No character from yesterday available"
+ },
+ "login": {
+ "titleSignUp": "Sign Up",
+ "titleSignIn": "Sign In",
+ "headerSignUp": "Create your account",
+ "headerSignIn": "Welcome, pirate",
+ "nameLabel": "Name",
+ "namePlaceholder": "Your name",
+ "usernameLabel": "Username",
+ "usernamePlaceholder": "e.g. luffy_gear5",
+ "identifierLabelSignUp": "Email",
+ "identifierLabelSignIn": "Email or username",
+ "identifierPlaceholderSignUp": "yourmail@email.com",
+ "identifierPlaceholderSignIn": "yourmail@email.com or luffy_gear5",
+ "passwordLabel": "Password",
+ "confirmPasswordLabel": "Confirm password",
+ "loading": "Loading...",
+ "submitSignUp": "Create an account",
+ "submitSignIn": "Log in",
+ "togglePromptSignUp": "Already have an account?",
+ "togglePromptSignIn": "Don't have an account?",
+ "toggleActionSignUp": "Log in",
+ "toggleActionSignIn": "Sign up",
+ "backHome": "Back to home"
+ },
+ "profile": {
+ "pageTitle": "My Profile",
+ "headerTitle": "My Profile",
+ "headerSubtitle": "Edit your profile information",
+ "tabProfile": "Profile",
+ "tabPassword": "Password",
+ "tabDaily": "Daily History",
+ "tabSessions": "Sessions",
+ "tabFriends": "Friends",
+ "avatarFallbackAlt": "Profile",
+ "email": "Email",
+ "displayName": "Display name",
+ "displayNamePlaceholder": "Your name",
+ "profileUpdateSuccess": "Profile updated successfully!",
+ "updating": "Updating...",
+ "saveChanges": "Save changes",
+ "friendsTitle": "Friends System",
+ "addFriendByUsername": "Add a friend by username",
+ "friendUsernamePlaceholder": "e.g. luffy_gear5",
+ "sending": "Sending...",
+ "send": "Send",
+ "incomingRequests": "Incoming requests",
+ "noIncomingRequests": "No incoming requests.",
+ "accept": "Accept",
+ "decline": "Decline",
+ "outgoingRequests": "Outgoing requests",
+ "noOutgoingRequests": "No outgoing requests.",
+ "cancel": "Cancel",
+ "myFriends": "My friends",
+ "noFriends": "You don't have any friends yet.",
+ "remove": "Remove",
+ "changePasswordTitle": "Change password",
+ "currentPassword": "Current password",
+ "newPassword": "New password",
+ "confirmPassword": "Confirm password",
+ "passwordChangeSuccess": "Password changed successfully!",
+ "changing": "Changing...",
+ "changePassword": "Change password",
+ "dailyHistoryTitle": "Daily history",
+ "noDailyHistory": "No history available",
+ "noImage": "N/A",
+ "trySingular": "try",
+ "tryPlural": "tries",
+ "activeSessionsTitle": "Active sessions",
+ "noActiveSessions": "No active session",
+ "unknownDevice": "Unknown device",
+ "unknown": "Unknown",
+ "ip": "IP",
+ "created": "Created",
+ "terminate": "Terminate",
+ "backHome": "Back to home"
+ },
+ "daily": {
+ "metaTitle": "OnePieceDle - Daily Mode",
+ "title": "Daily Character",
+ "winsPeopleSingular": "person",
+ "winsPeoplePlural": "people",
+ "winsVerbSingular": "has",
+ "winsVerbPlural": "have",
+ "winsSuffix": "found it today 🎉",
+ "reset": "Play again",
+ "description": "Guess the character. Each hint unlocks after a certain number of guesses. Good luck!",
+ "friendsToday": "Your friends today",
+ "friendTrySingular": "try",
+ "friendTryPlural": "tries"
+ },
+ "infinite": {
+ "metaTitle": "OnePieceDle - Infinite Mode",
+ "title": "Infinite Mode",
+ "score": "Score",
+ "resetScore": "Reset",
+ "description": "Guess characters endlessly. Each hint unlocks after a certain number of guesses. Good luck!",
+ "nextCharacter": "Play again",
+ "revealAnswer": "Reveal answer",
+ "loadingCharacter": "Loading character...",
+ "filtersTitle": "Character filters",
+ "clearFilters": "Reset",
+ "filterGender": "Gender",
+ "filterStatus": "Status",
+ "filterAbilities": "Abilities",
+ "filterInformation": "Information",
+ "filterArcs": "Arcs",
+ "male": "Male",
+ "female": "Female",
+ "alive": "Alive",
+ "dead": "Dead",
+ "unknown": "Unknown",
+ "hasHaki": "Has Haki",
+ "fruitAll": "Fruit (All)",
+ "withFruit": "With Fruit",
+ "withoutFruit": "Without Fruit",
+ "heightDefined": "Height defined",
+ "originDefined": "Origin defined",
+ "availableCharactersSingular": "character available",
+ "availableCharactersPlural": "characters available",
+ "columnsTitle": "Columns"
+ },
+ "components": {
+ "searchInput": {
+ "title": "Enter a guess",
+ "placeholder": "Character name",
+ "submit": "Submit"
+ },
+ "hints": {
+ "origin": "Origin",
+ "devilFruit": "Devil fruit",
+ "affiliation": "Affiliation",
+ "unknown": "Unknown",
+ "none": "None",
+ "beforeUnlock": "guesses before unlock",
+ "available": "Hint available!"
+ },
+ "guessHistory": {
+ "title": "History",
+ "empty": "No guesses yet.",
+ "character": "Character",
+ "status": "Status",
+ "gender": "Gender",
+ "affiliations": "Affiliations",
+ "fruit": "Fruit",
+ "haki": "Haki",
+ "bounty": "Bounty",
+ "height": "Height",
+ "origin": "Origin",
+ "arc": "Arc",
+ "alive": "Alive",
+ "dead": "Dead",
+ "unknown": "Unknown",
+ "male": "Male",
+ "female": "Female",
+ "obsHakiTitle": "Observation Haki",
+ "armHakiTitle": "Armament Haki",
+ "kingHakiTitle": "Conqueror's Haki"
+ },
+ "winPanel": {
+ "attemptSingular": "attempt",
+ "attemptPlural": "attempts",
+ "moriaTitle": "Moria controls you...",
+ "moriaPrefix": "You succumbed to the shadows in",
+ "winTitle": "Congratulations!",
+ "winPrefix": "You found the character in",
+ "oneTryMessages": [
+ "Cheater 👀",
+ "1 guess? Admit it, you already knew 😏",
+ "First try... suspicious 🤨"
+ ],
+ "twoTryMessages": [
+ "Well played! ⚡",
+ "Two guesses, clean! 👏",
+ "You warmed up fast, nice 🔥"
+ ],
+ "tenPlusMessages": [
+ "${attempts} guesses... even a transponder snail would be faster 📞",
+ "${attempts} attempts? The Grand Line is shorter than that 😵",
+ "${attempts} guesses: legendary performance... in the wrong direction 🫠"
+ ],
+ "fivePlusMessages": [
+ "${attempts} guesses? Let's say it was for suspense 😅",
+ "That is a lot of guesses... but you made it 😬",
+ "You never give up, even after several guesses 😂"
+ ],
+ "defaultMessages": [
+ "Not bad at all!",
+ "Nice try, good pace 👍",
+ "Things are going well, keep it up ✨"
+ ]
+ },
+ "yesterdayCharacter": {
+ "photo": "Photo",
+ "title": "Yesterday's character",
+ "openPage": "Open page",
+ "none": "No character",
+ "noneAvailable": "No character from yesterday available"
+ }
+ }
+ }
+}
diff --git a/src/lib/i18n/fr.json b/src/lib/i18n/fr.json
new file mode 100644
index 0000000..ac6d574
--- /dev/null
+++ b/src/lib/i18n/fr.json
@@ -0,0 +1,227 @@
+{
+ "common": {
+ "language": "Langue",
+ "selectLanguage": "Sélectionnez la Langue",
+ "english": "English",
+ "french": "Français",
+ "german": "Deutsch",
+ "spanish": "Español"
+ },
+ "game": {
+ "home": {
+ "heroDescription": "Devine le personnage de l'equipage, des marines ou du vaste monde. Chaque indice te rapproche du tresor.",
+ "dailyTitle": "Personnage du jour",
+ "dailySubtitle": "Nouveau mystere toutes les 24 heures",
+ "dailyDescription": "Compare tes essais, debloque des indices et garde ta serie.",
+ "dailyCta": "Commencer",
+ "infiniteTitle": "Mode Infini",
+ "infiniteSubtitle": "Des defis sans fin",
+ "infiniteDescription": "Enchaine les personnages et croise ton score. Pas de limite, que du plaisir.",
+ "infiniteCta": "Jouer",
+ "photoFallback": "Photo",
+ "yesterdayCharacter": "Personnage d'hier",
+ "openPage": "Voir la page",
+ "noCharacter": "Aucun personnage",
+ "noYesterdayCharacter": "Aucun personnage d'hier disponible"
+ },
+ "login": {
+ "titleSignUp": "Inscription",
+ "titleSignIn": "Connexion",
+ "headerSignUp": "Creer votre compte",
+ "headerSignIn": "Bienvenue, pirate",
+ "nameLabel": "Nom",
+ "namePlaceholder": "Votre nom",
+ "usernameLabel": "Nom d'utilisateur",
+ "usernamePlaceholder": "ex: luffy_gear5",
+ "identifierLabelSignUp": "E-mail",
+ "identifierLabelSignIn": "E-mail ou nom d'utilisateur",
+ "identifierPlaceholderSignUp": "votremail@email.com",
+ "identifierPlaceholderSignIn": "votremail@email.com ou luffy_gear5",
+ "passwordLabel": "Mot de passe",
+ "confirmPasswordLabel": "Confirmer le mot de passe",
+ "loading": "Chargement...",
+ "submitSignUp": "Creer un compte",
+ "submitSignIn": "Se connecter",
+ "togglePromptSignUp": "Vous avez deja un compte ?",
+ "togglePromptSignIn": "Vous n'avez pas de compte ?",
+ "toggleActionSignUp": "Se connecter",
+ "toggleActionSignIn": "S'inscrire",
+ "backHome": "Retour a l'accueil"
+ },
+ "profile": {
+ "pageTitle": "Mon Profil",
+ "headerTitle": "Mon Profil",
+ "headerSubtitle": "Modifie les informations de ton profil",
+ "tabProfile": "Profil",
+ "tabPassword": "Mot de passe",
+ "tabDaily": "Historique Daily",
+ "tabSessions": "Sessions",
+ "tabFriends": "Amis",
+ "avatarFallbackAlt": "Profil",
+ "email": "Email",
+ "displayName": "Nom d'affichage",
+ "displayNamePlaceholder": "Ton nom",
+ "profileUpdateSuccess": "Profil mis a jour avec succes !",
+ "updating": "Mise a jour...",
+ "saveChanges": "Enregistrer les modifications",
+ "friendsTitle": "Systeme d'amis",
+ "addFriendByUsername": "Ajouter un ami par nom d'utilisateur",
+ "friendUsernamePlaceholder": "ex: luffy_gear5",
+ "sending": "Envoi...",
+ "send": "Envoyer",
+ "incomingRequests": "Demandes recues",
+ "noIncomingRequests": "Aucune demande recue.",
+ "accept": "Accepter",
+ "decline": "Refuser",
+ "outgoingRequests": "Demandes envoyees",
+ "noOutgoingRequests": "Aucune demande envoyee.",
+ "cancel": "Annuler",
+ "myFriends": "Mes amis",
+ "noFriends": "Tu n'as pas encore d'amis.",
+ "remove": "Supprimer",
+ "changePasswordTitle": "Changer le mot de passe",
+ "currentPassword": "Mot de passe actuel",
+ "newPassword": "Nouveau mot de passe",
+ "confirmPassword": "Confirmer le mot de passe",
+ "passwordChangeSuccess": "Mot de passe change avec succes !",
+ "changing": "Changement en cours...",
+ "changePassword": "Changer le mot de passe",
+ "dailyHistoryTitle": "Historique des Daily",
+ "noDailyHistory": "Aucun historique disponible",
+ "noImage": "N/A",
+ "trySingular": "tentative",
+ "tryPlural": "tentatives",
+ "activeSessionsTitle": "Sessions actives",
+ "noActiveSessions": "Aucune session active",
+ "unknownDevice": "Appareil inconnu",
+ "unknown": "Inconnue",
+ "ip": "IP",
+ "created": "Creee",
+ "terminate": "Terminer",
+ "backHome": "Retour a l'accueil"
+ },
+ "daily": {
+ "metaTitle": "OnePieceDle - Mode du jour",
+ "title": "Personnage du jour",
+ "winsPeopleSingular": "personne",
+ "winsPeoplePlural": "personnes",
+ "winsVerbSingular": "a",
+ "winsVerbPlural": "ont",
+ "winsSuffix": "trouve aujourd'hui 🎉",
+ "reset": "Recommencer",
+ "description": "Devine le personnage. Chaque indice se debloque apres un certain nombre de tentatives. Bonne chance !",
+ "friendsToday": "Tes amis aujourd'hui",
+ "friendTrySingular": "coup",
+ "friendTryPlural": "coups"
+ },
+ "infinite": {
+ "metaTitle": "OnePieceDle - Mode Infini",
+ "title": "Mode Infini",
+ "score": "Score",
+ "resetScore": "Reinitialiser",
+ "description": "Devine des personnages a l'infini ! Chaque indice se debloque apres un certain nombre de tentatives. Bonne chance !",
+ "nextCharacter": "Recommencer",
+ "revealAnswer": "Reveler la reponse",
+ "loadingCharacter": "Chargement du personnage...",
+ "filtersTitle": "Filtres de personnages",
+ "clearFilters": "Reinitialiser",
+ "filterGender": "Genre",
+ "filterStatus": "Statut",
+ "filterAbilities": "Capacites",
+ "filterInformation": "Informations",
+ "filterArcs": "Arcs",
+ "male": "Homme",
+ "female": "Femme",
+ "alive": "Vivant",
+ "dead": "Mort",
+ "unknown": "Inconnu",
+ "hasHaki": "A du Haki",
+ "fruitAll": "Fruit (Tous)",
+ "withFruit": "Avec Fruit",
+ "withoutFruit": "Sans Fruit",
+ "heightDefined": "Taille definie",
+ "originDefined": "Origine definie",
+ "availableCharactersSingular": "personnage disponible",
+ "availableCharactersPlural": "personnages disponibles",
+ "columnsTitle": "Colonnes"
+ },
+ "components": {
+ "searchInput": {
+ "title": "Entrer une supposition",
+ "placeholder": "Nom du personnage",
+ "submit": "Valider"
+ },
+ "hints": {
+ "origin": "Origine",
+ "devilFruit": "Fruit du demon",
+ "affiliation": "Affiliation",
+ "unknown": "Inconnue",
+ "none": "Aucun",
+ "beforeUnlock": "essais avant deblocage",
+ "available": "Indice disponible !"
+ },
+ "guessHistory": {
+ "title": "Historique",
+ "empty": "Aucune tentative pour le moment.",
+ "character": "Personnage",
+ "status": "Statut",
+ "gender": "Genre",
+ "affiliations": "Affiliations",
+ "fruit": "Fruit",
+ "haki": "Haki",
+ "bounty": "Prime",
+ "height": "Taille",
+ "origin": "Origine",
+ "arc": "Arc",
+ "alive": "Vivant",
+ "dead": "Mort",
+ "unknown": "Inconnu",
+ "male": "Homme",
+ "female": "Femme",
+ "obsHakiTitle": "Haki de l'Observation",
+ "armHakiTitle": "Haki de l'Armement",
+ "kingHakiTitle": "Haki des Rois"
+ },
+ "winPanel": {
+ "attemptSingular": "tentative",
+ "attemptPlural": "tentatives",
+ "moriaTitle": "Moria vous controle...",
+ "moriaPrefix": "Vous avez succombe a l'ombre en",
+ "winTitle": "Felicitations !",
+ "winPrefix": "Vous avez trouve le personnage en",
+ "oneTryMessages": [
+ "Tricheur 👀",
+ "1 essai ? Avoue, tu avais la reponse 😏",
+ "Premier coup direct... suspect 🤨"
+ ],
+ "twoTryMessages": [
+ "Bien joue ! ⚡",
+ "Deux essais, propre ! 👏",
+ "Tu chauffes vite, bien joue 🔥"
+ ],
+ "tenPlusMessages": [
+ "${attempts} essais... meme un escargophone aurait trouve plus vite 📞",
+ "${attempts} tentatives ? Le Grand Line est moins long que ca 😵",
+ "${attempts} essais : performance legendaire... dans le mauvais sens 🫠"
+ ],
+ "fivePlusMessages": [
+ "${attempts} essais ? On va dire que c'etait pour le suspense 😅",
+ "Ca en fait des essais... mais au moins tu y es arrive 😬",
+ "Tu ne laches rien, meme apres plusieurs essais 😂"
+ ],
+ "defaultMessages": [
+ "Pas mal du tout !",
+ "Bien tente, bon rythme 👍",
+ "Ca se passe bien, continue comme ca ✨"
+ ]
+ },
+ "yesterdayCharacter": {
+ "photo": "Photo",
+ "title": "Personnage d'hier",
+ "openPage": "Voir la page",
+ "none": "Aucun personnage",
+ "noneAvailable": "Aucun personnage d'hier disponible"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts
new file mode 100644
index 0000000..2e8b4db
--- /dev/null
+++ b/src/lib/i18n/index.ts
@@ -0,0 +1,51 @@
+import { writable, derived } from 'svelte/store';
+import type { Writable, Readable } from 'svelte/store';
+
+import en from './en.json';
+import fr from './fr.json';
+
+type Messages = typeof en;
+
+const translations: Record
= { en, fr };
+
+// Get initial language
+function getInitialLanguage(): string {
+ if (typeof window !== 'undefined') {
+ const stored = localStorage.getItem('language');
+ if (stored && stored in translations) {
+ return stored;
+ }
+ const browserLang = navigator.language.split('-')[0];
+ if (browserLang in translations) {
+ return browserLang;
+ }
+ }
+ return 'en';
+}
+
+// Create writable store for the current language
+export const language: Writable = writable(getInitialLanguage());
+
+// Create derived store for the current messages
+export const t: Readable = derived(language, ($language) => {
+ return translations[$language] || translations['en'];
+});
+
+export function setLanguage(lang: string) {
+ if (lang in translations) {
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('language', lang);
+ }
+ language.set(lang);
+ }
+}
+
+export function getLanguage(): string {
+ let currentLang = 'en';
+ language.subscribe((lang) => {
+ currentLang = lang;
+ })();
+ return currentLang;
+}
+
+export const availableLanguages = Object.keys(translations);
diff --git a/src/routes/(game)/+layout.svelte b/src/routes/(game)/+layout.svelte
index 26230c4..dbc2880 100644
--- a/src/routes/(game)/+layout.svelte
+++ b/src/routes/(game)/+layout.svelte
@@ -1,5 +1,6 @@
@@ -23,30 +24,30 @@
OnePieceDle
- Devine le personnage de l'equipage, des marines ou du vaste monde. Chaque indice te rapproche du tresor.
+ {$t.game.home.heroDescription}
{/if}
diff --git a/src/routes/(game)/daily/+page.svelte b/src/routes/(game)/daily/+page.svelte
index e146432..0f02df7 100644
--- a/src/routes/(game)/daily/+page.svelte
+++ b/src/routes/(game)/daily/+page.svelte
@@ -6,6 +6,7 @@
import GuessHistoryTable from '$lib/components/GuessHistoryTable.svelte';
import WinPanel from '$lib/components/WinPanel.svelte';
import type { CharacterWithRelations } from '$lib/server/daily-character.js';
+ import { t } from '$lib/i18n';
export let data;
@@ -186,7 +187,7 @@