diff --git a/src/lib/components/CharacterSearchInput.svelte b/src/lib/components/CharacterSearchInput.svelte index 8eb2f45..76aa791 100644 --- a/src/lib/components/CharacterSearchInput.svelte +++ b/src/lib/components/CharacterSearchInput.svelte @@ -1,6 +1,7 @@
-

Entrer une supposition

+

{$t.game.components.searchInput.title}

@@ -193,7 +194,7 @@ disabled={state.searchInput.length === 0 || filteredCharacters.length === 0} class="rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200 disabled:cursor-not-allowed disabled:opacity-50" > - Valider + {$t.game.components.searchInput.submit}
diff --git a/src/lib/components/GuessHistoryTable.svelte b/src/lib/components/GuessHistoryTable.svelte index 1f3e4a3..c141373 100644 --- a/src/lib/components/GuessHistoryTable.svelte +++ b/src/lib/components/GuessHistoryTable.svelte @@ -1,6 +1,7 @@ + +
+ + + {#if isOpen} +
+ {#each availableLanguages as lang (lang)} + + {/each} +
+ {/if} +
diff --git a/src/lib/components/WinPanel.svelte b/src/lib/components/WinPanel.svelte index 5b5df3a..2ca4510 100644 --- a/src/lib/components/WinPanel.svelte +++ b/src/lib/components/WinPanel.svelte @@ -1,28 +1,20 @@ {#if isGeckoMoriaWin}
🌑
-

Moria vous contrôle...

-

Vous avez succombé à l'ombre en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !

+

{$t.game.components.winPanel.moriaTitle}

+

{$t.game.components.winPanel.moriaPrefix} {selectedCharacters.length} {attemptWord} !

{attemptMessage}

{#if selectedCharacter.pictureUrl} @@ -73,8 +68,8 @@
🎉
-

Félicitations !

-

Vous avez trouvé le personnage en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !

+

{$t.game.components.winPanel.winTitle}

+

{$t.game.components.winPanel.winPrefix} {selectedCharacters.length} {attemptWord} !

{attemptMessage}

{#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}

-

Personnage du jour

-

Nouveau mystere toutes les 24 heures

-

Compare tes essais, debloque des indices et garde ta serie.

+

{$t.game.home.dailyTitle}

+

{$t.game.home.dailySubtitle}

+

{$t.game.home.dailyDescription}

- Commencer + {$t.game.home.dailyCta}
-

Mode Infini

-

Des defis sans fin

-

Enchaine les personnages et croise ton score. Pas de limite, que du plaisir.

+

{$t.game.home.infiniteTitle}

+

{$t.game.home.infiniteSubtitle}

+

{$t.game.home.infiniteDescription}

- Jouer + {$t.game.home.infiniteCta}
@@ -61,11 +62,11 @@ /> {:else}
- Photo + {$t.game.home.photoFallback}
{/if}
-

Personnage d'hier

+

{$t.game.home.yesterdayCharacter}

{yesterdayCharacter.name}

{#if yesterdayCharacter.epithets}

@@ -81,18 +82,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.home.openPage}

{:else}
- Photo + {$t.game.home.photoFallback}
-

Personnage d'hier

-

Aucun personnage

-

Aucun personnage d'hier disponible

+

{$t.game.home.yesterdayCharacter}

+

{$t.game.home.noCharacter}

+

{$t.game.home.noYesterdayCharacter}

{/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 @@ - OnePieceDle - Mode du jour + {$t.game.daily.metaTitle}