feat(i18n): integrate internationalization for game pages

- Added translation support for various game-related texts in the home, daily, infinite, login, and profile pages.
- Replaced hardcoded French strings with translation keys using the `$t` function.
- Updated titles, descriptions, and button texts to enhance localization.
This commit is contained in:
2026-03-15 20:19:26 +01:00
parent 6d2dccd47f
commit bd121b7d85
15 changed files with 805 additions and 191 deletions
+5 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import ProfileButton from '$lib/components/ProfileButton.svelte';
import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte';
import { resolve } from '$app/paths';
let { children, data } = $props();
@@ -11,7 +12,10 @@
<a href={resolve("/")} class="text-lg font-black uppercase tracking-[0.15em] text-amber-50 transition hover:text-amber-100">
OnePieceDle
</a>
<ProfileButton user={data.user} />
<div class="flex items-center gap-3">
<LanguageSwitcher />
<ProfileButton user={data.user} />
</div>
</div>
</header>
<main class="pt-20">
+17 -16
View File
@@ -2,6 +2,7 @@
export let data;
import { resolve } from '$app/paths';
import { t } from '$lib/i18n';
$: yesterdayCharacter = data.yesterdayCharacter;
</script>
@@ -23,30 +24,30 @@
OnePieceDle
</h1>
<p class="mt-4 max-w-2xl text-base text-slate-200 sm:text-lg">
Devine le personnage de l'equipage, des marines ou du vaste monde. Chaque indice te rapproche du tresor.
{$t.game.home.heroDescription}
</p>
</div>
<div class="grid w-full gap-4 sm:grid-cols-2">
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Personnage du jour</h2>
<p class="mt-3 text-lg font-semibold text-white">Nouveau mystere toutes les 24 heures</p>
<p class="mt-2 text-sm text-slate-200">Compare tes essais, debloque des indices et garde ta serie.</p>
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">{$t.game.home.dailyTitle}</h2>
<p class="mt-3 text-lg font-semibold text-white">{$t.game.home.dailySubtitle}</p>
<p class="mt-2 text-sm text-slate-200">{$t.game.home.dailyDescription}</p>
<a
href={resolve("/daily")}
class="mt-5 inline-flex w-full items-center justify-center rounded-full bg-amber-300 px-5 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200"
>
Commencer
{$t.game.home.dailyCta}
</a>
</div>
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Mode Infini</h2>
<p class="mt-3 text-lg font-semibold text-white">Des defis sans fin</p>
<p class="mt-2 text-sm text-slate-200">Enchaine les personnages et croise ton score. Pas de limite, que du plaisir.</p>
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">{$t.game.home.infiniteTitle}</h2>
<p class="mt-3 text-lg font-semibold text-white">{$t.game.home.infiniteSubtitle}</p>
<p class="mt-2 text-sm text-slate-200">{$t.game.home.infiniteDescription}</p>
<a
href={resolve("/infinite")}
class="mt-5 inline-flex w-full items-center justify-center 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"
>
Jouer
{$t.game.home.infiniteCta}
</a>
</div>
</div>
@@ -61,11 +62,11 @@
/>
{:else}
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
Photo
{$t.game.home.photoFallback}
</div>
{/if}
<div class="flex-1">
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage d'hier</p>
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">{$t.game.home.yesterdayCharacter}</p>
<p class="mt-2 text-lg font-semibold text-white">{yesterdayCharacter.name}</p>
{#if yesterdayCharacter.epithets}
<p class="mt-1 text-sm text-slate-400">
@@ -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}
</a>
</div>
{:else}
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
Photo
{$t.game.home.photoFallback}
</div>
<div class="flex-1">
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage d'hier</p>
<p class="mt-2 text-lg font-semibold text-white">Aucun personnage</p>
<p class="mt-1 text-sm text-slate-200">Aucun personnage d'hier disponible</p>
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">{$t.game.home.yesterdayCharacter}</p>
<p class="mt-2 text-lg font-semibold text-white">{$t.game.home.noCharacter}</p>
<p class="mt-1 text-sm text-slate-200">{$t.game.home.noYesterdayCharacter}</p>
</div>
</div>
{/if}
+8 -7
View File
@@ -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 @@
</script>
<svelte:head>
<title>OnePieceDle - Mode du jour</title>
<title>{$t.game.daily.metaTitle}</title>
<style>
@keyframes shadow-pulse {
0% {
@@ -264,10 +265,10 @@
<div class="flex w-full items-center justify-between gap-4">
<div>
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 sm:text-5xl">
Personnage du jour
{$t.game.daily.title}
</h1>
<p class="mt-2 text-sm text-amber-300">
{data.winCount} {data.winCount > 1 ? 'personnes' : 'personne'} {data.winCount > 1 ? 'ont' : 'a'} trouvé aujourd'hui 🎉
{data.winCount} {data.winCount > 1 ? $t.game.daily.winsPeoplePlural : $t.game.daily.winsPeopleSingular} {data.winCount > 1 ? $t.game.daily.winsVerbPlural : $t.game.daily.winsVerbSingular} {$t.game.daily.winsSuffix}
</p>
</div>
{#if hasWon}
@@ -275,12 +276,12 @@
class="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"
onclick={resetHistory}
>
Recommencer
{$t.game.daily.reset}
</button>
{/if}
</div>
<p class="max-w-2xl text-base text-slate-200 sm:text-lg">
Devine le personnage. Chaque indice se débloque après un certain nombre de tentatives. Bonne chance !
{$t.game.daily.description}
</p>
</header>
@@ -313,7 +314,7 @@
{#if hasWon && data.friendsTodayResults && data.friendsTodayResults.length > 0}
<section class="mt-6 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100 text-center">Tes amis aujourd'hui</p>
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100 text-center">{$t.game.daily.friendsToday}</p>
<div class="mt-4 space-y-2">
{#each data.friendsTodayResults as friendResult (friendResult.userId)}
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-slate-950/50 px-4 py-2">
@@ -332,7 +333,7 @@
<p class="text-sm font-semibold text-slate-100">{friendResult.name}</p>
</div>
<p class="text-sm text-amber-300">
{friendResult.tryCount} {friendResult.tryCount > 1 ? 'coups' : 'coup'}
{friendResult.tryCount} {friendResult.tryCount > 1 ? $t.game.daily.friendTryPlural : $t.game.daily.friendTrySingular}
</p>
</div>
{/each}
+38 -40
View File
@@ -5,6 +5,7 @@
import WinPanel from '$lib/components/WinPanel.svelte';
import HintsPanel from '$lib/components/HintsPanel.svelte';
import type { CharacterWithRelations } from '$lib/server/daily-character.js';
import { t } from '$lib/i18n';
export let data;
@@ -18,17 +19,7 @@
let availableArcs: ArcFilterOption[] = [];
let hasWon = false;
let columnVisibility: Record<string, boolean> = {};
const columnDisplayNames: Record<string, string> = {
status: 'Statut',
gender: 'Genre',
affiliations: 'Affiliations',
devilFruitType: 'Fruit',
haki: 'Haki',
bounty: 'Prime',
height: 'Taille',
origin: 'Origine',
arc: 'Arc'
};
let columnDisplayNames: Record<string, string> = {};
// Character filters
let characterFilters = {
@@ -291,6 +282,17 @@
} else if (!hasWon) {
isGeckoMoriaWin = false;
}
$: columnDisplayNames = {
status: $t.game.components.guessHistory.status,
gender: $t.game.components.guessHistory.gender,
affiliations: $t.game.components.guessHistory.affiliations,
devilFruitType: $t.game.components.guessHistory.fruit,
haki: $t.game.components.guessHistory.haki,
bounty: $t.game.components.guessHistory.bounty,
height: $t.game.components.guessHistory.height,
origin: $t.game.components.guessHistory.origin,
arc: $t.game.components.guessHistory.arc
};
function generateNewCharacter() {
if (characters.length === 0) return;
@@ -473,7 +475,7 @@
</script>
<svelte:head>
<title>OnePieceDle - Mode Infini</title>
<title>{$t.game.infinite.metaTitle}</title>
<style>
@keyframes shadow-pulse {
0% {
@@ -564,22 +566,21 @@
<div class="flex w-full items-center justify-between gap-4">
<div>
<h1 class="text-3xl font-black tracking-[0.25em] text-amber-50 uppercase sm:text-5xl">
Mode Infini
{$t.game.infinite.title}
</h1>
<p class="mt-2 text-2xl font-bold text-amber-300">Score: {score}</p>
<p class="mt-2 text-2xl font-bold text-amber-300">{$t.game.infinite.score}: {score}</p>
</div>
{#if score > 0}
<button
class="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"
onclick={resetScore}
>
Réinitialiser
{$t.game.infinite.resetScore}
</button>
{/if}
</div>
<p class="max-w-2xl text-base text-slate-200 sm:text-lg">
Devine des personnages à l'infini ! Chaque indice se débloque après un certain nombre de
tentatives. Bonne chance !
{$t.game.infinite.description}
</p>
</header>
@@ -593,7 +594,7 @@
onclick={nextCharacter}
class="mt-4 w-full rounded-full bg-emerald-500 px-6 py-2 text-sm font-semibold text-white transition hover:bg-emerald-600"
>
Recommencer
{$t.game.infinite.nextCharacter}
</button>
</div>
{:else}
@@ -611,7 +612,7 @@
onclick={revealAnswer}
class="rounded-lg border border-red-600/40 bg-red-900/20 px-4 py-2 text-sm text-red-300 transition hover:border-red-500 hover:bg-red-900/40 hover:text-red-200"
>
Révéler la réponse
{$t.game.infinite.revealAnswer}
</button>
</div>
{/if}
@@ -625,7 +626,7 @@
<div
class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur"
>
<p class="text-center text-slate-300">Chargement du personnage...</p>
<p class="text-center text-slate-300">{$t.game.infinite.loadingCharacter}</p>
</div>
{/if}
</section>
@@ -642,7 +643,7 @@
<div class="rounded-2xl border border-white/10 bg-white/5 p-3 backdrop-blur sm:p-4">
<div class="mb-3 flex items-center justify-between gap-3">
<h3 class="text-xs font-semibold tracking-[0.2em] text-amber-200 uppercase">
Filtres de personnages
{$t.game.infinite.filtersTitle}
</h3>
{#if characterFilters.gender.length > 0 || characterFilters.hasHaki || characterFilters.hasDevilFruit !== null || characterFilters.status.length > 0 || characterFilters.hasHeight || characterFilters.hasOrigin || characterFilters.arcs.length > 0}
<button
@@ -650,7 +651,7 @@
onclick={clearAllFilters}
class="text-xs text-red-300 transition hover:text-red-200"
>
Réinitialiser
{$t.game.infinite.clearFilters}
</button>
{/if}
</div>
@@ -658,7 +659,7 @@
<div class="space-y-3">
<!-- Gender Filter -->
<div>
<p class="mb-2 text-xs text-slate-400">Genre</p>
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterGender}</p>
<div class="flex flex-wrap gap-2">
{#each ['Male', 'Female'] as gender (gender)}
<button
@@ -670,7 +671,7 @@
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
>
{gender === 'Male' ? 'Homme' : 'Femme'}
{gender === 'Male' ? $t.game.infinite.male : $t.game.infinite.female}
</button>
{/each}
</div>
@@ -678,7 +679,7 @@
<!-- Status Filter -->
<div>
<p class="mb-2 text-xs text-slate-400">Statut</p>
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterStatus}</p>
<div class="flex flex-wrap gap-2">
{#each ['Alive', 'Dead', 'Unknown'] as status (status)}
<button
@@ -690,7 +691,7 @@
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
>
{status === 'Alive' ? 'Vivant' : status === 'Dead' ? 'Mort' : 'Inconnu'}
{status === 'Alive' ? $t.game.infinite.alive : status === 'Dead' ? $t.game.infinite.dead : $t.game.infinite.unknown}
</button>
{/each}
</div>
@@ -698,7 +699,7 @@
<!-- Haki Filter -->
<div>
<p class="mb-2 text-xs text-slate-400">Capacités</p>
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterAbilities}</p>
<div class="flex flex-wrap gap-2">
<button
type="button"
@@ -707,7 +708,7 @@
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
>
A du Haki
{$t.game.infinite.hasHaki}
</button>
<button
type="button"
@@ -720,17 +721,17 @@
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
>
{characterFilters.hasDevilFruit === null
? 'Fruit (Tous)'
? $t.game.infinite.fruitAll
: characterFilters.hasDevilFruit
? 'Avec Fruit'
: 'Sans Fruit'}
? $t.game.infinite.withFruit
: $t.game.infinite.withoutFruit}
</button>
</div>
</div>
<!-- Informations Filter -->
<div>
<p class="mb-2 text-xs text-slate-400">Informations</p>
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterInformation}</p>
<div class="flex flex-wrap gap-2">
<button
type="button"
@@ -739,7 +740,7 @@
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
>
Taille définie
{$t.game.infinite.heightDefined}
</button>
<button
type="button"
@@ -748,14 +749,14 @@
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
>
Origine définie
{$t.game.infinite.originDefined}
</button>
</div>
</div>
<!-- Arc Filter -->
<div>
<p class="mb-2 text-xs text-slate-400">Arcs</p>
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterArcs}</p>
<div class="flex flex-wrap gap-2">
{#each availableArcs as arc (arc.id)}
<button
@@ -774,10 +775,7 @@
</div>
<p class="mt-2 text-xs text-slate-500">
{characters.length} personnage{characters.length > 1 ? 's' : ''} disponible{characters.length >
1
? 's'
: ''}
{characters.length} {characters.length > 1 ? $t.game.infinite.availableCharactersPlural : $t.game.infinite.availableCharactersSingular}
</p>
</div>
</div>
@@ -788,7 +786,7 @@
<div class="rounded-2xl border border-white/10 bg-white/5 p-3 backdrop-blur sm:p-4">
<div class="mb-3 flex items-center justify-between gap-3">
<h3 class="text-xs font-semibold tracking-[0.2em] text-amber-200 uppercase">
Colonnes
{$t.game.infinite.columnsTitle}
</h3>
<p class="text-xs text-slate-400">
{Object.values(columnVisibility).filter(Boolean).length}/{Object.keys(
+15 -14
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { resolve } from '$app/paths';
import { t } from '$lib/i18n';
import type { ActionData } from './$types';
export let form: ActionData;
@@ -25,7 +26,7 @@
</script>
<svelte:head>
<title>OnePieceDle - {isSignUp ? 'Inscription' : 'Connexion'}</title>
<title>OnePieceDle - {isSignUp ? $t.game.login.titleSignUp : $t.game.login.titleSignIn}</title>
</svelte:head>
<main class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100">
@@ -42,7 +43,7 @@
OnePieceDle
</h1>
<p class="mt-4 text-slate-300">
{isSignUp ? 'Créer votre compte' : 'Bienvenue, pirate'}
{isSignUp ? $t.game.login.headerSignUp : $t.game.login.headerSignIn}
</p>
</div>
@@ -64,7 +65,7 @@
{#if isSignUp}
<div>
<label for="name" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Nom
{$t.game.login.nameLabel}
</label>
<input
id="name"
@@ -72,7 +73,7 @@
name="name"
bind:value={name}
required
placeholder="Votre nom"
placeholder={$t.game.login.namePlaceholder}
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
/>
</div>
@@ -82,7 +83,7 @@
{#if isSignUp}
<div>
<label for="username" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Nom d'utilisateur
{$t.game.login.usernameLabel}
</label>
<input
id="username"
@@ -90,7 +91,7 @@
name="username"
bind:value={username}
required
placeholder="ex: luffy_gear5"
placeholder={$t.game.login.usernamePlaceholder}
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
/>
</div>
@@ -99,7 +100,7 @@
<!-- Email / Username Field -->
<div>
<label for="identifier" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
{isSignUp ? 'E-mail' : 'E-mail ou nom d\'utilisateur'}
{isSignUp ? $t.game.login.identifierLabelSignUp : $t.game.login.identifierLabelSignIn}
</label>
<input
id="identifier"
@@ -107,7 +108,7 @@
name={isSignUp ? 'email' : 'identifier'}
bind:value={email}
required
placeholder={isSignUp ? 'votremail@email.com' : 'votremail@email.com ou luffy_gear5'}
placeholder={isSignUp ? $t.game.login.identifierPlaceholderSignUp : $t.game.login.identifierPlaceholderSignIn}
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
/>
</div>
@@ -115,7 +116,7 @@
<!-- Password Field -->
<div>
<label for="password" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Mot de passe
{$t.game.login.passwordLabel}
</label>
<input
id="password"
@@ -135,7 +136,7 @@
for="confirmPassword"
class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100"
>
Confirmer le mot de passe
{$t.game.login.confirmPasswordLabel}
</label>
<input
id="confirmPassword"
@@ -162,20 +163,20 @@
disabled={isLoading}
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
>
{isLoading ? 'Chargement...' : isSignUp ? 'Créer un compte' : 'Se connecter'}
{isLoading ? $t.game.login.loading : isSignUp ? $t.game.login.submitSignUp : $t.game.login.submitSignIn}
</button>
</form>
<!-- Toggle Sign Up / Login -->
<div class="mt-6 border-t border-white/10 pt-6">
<p class="text-center text-sm text-slate-400">
{isSignUp ? 'Vous avez déjà un compte ?' : "Vous n'avez pas de compte ?"}
{isSignUp ? $t.game.login.togglePromptSignUp : $t.game.login.togglePromptSignIn}
<button
type="button"
on:click={handleToggle}
class="text-amber-300 transition hover:text-amber-200"
>
{isSignUp ? 'Se connecter' : "S'inscrire"}
{isSignUp ? $t.game.login.toggleActionSignUp : $t.game.login.toggleActionSignIn}
</button>
</p>
</div>
@@ -184,7 +185,7 @@
<!-- Back to Home -->
<div class="text-center">
<a href={resolve("/")} class="text-sm text-slate-400 transition hover:text-slate-300">
Retour à l'accueil
{$t.game.login.backHome}
</a>
</div>
</div>
+47 -46
View File
@@ -2,6 +2,7 @@
import { enhance } from '$app/forms';
import type { PageData } from './$types';
import { resolve } from '$app/paths';
import { t, language } from '$lib/i18n';
interface Props {
data: PageData;
@@ -56,7 +57,7 @@
</script>
<svelte:head>
<title>Mon Profil - OnePieceDle</title>
<title>{$t.game.profile.pageTitle} - OnePieceDle</title>
</svelte:head>
<main class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100">
@@ -68,10 +69,10 @@
<!-- Header -->
<div class="text-center">
<h1 class="text-3xl font-black uppercase tracking-[0.3em] text-amber-50 sm:text-4xl">
Mon Profil
{$t.game.profile.headerTitle}
</h1>
<p class="mt-2 text-sm text-slate-300">
Modifie les informations de ton profil
{$t.game.profile.headerSubtitle}
</p>
</div>
@@ -83,7 +84,7 @@
? 'border-b-2 border-amber-300 text-amber-100'
: 'text-slate-400 hover:text-slate-100'}"
>
Profil
{$t.game.profile.tabProfile}
</button>
<button
onclick={() => handleTabChange('password')}
@@ -91,7 +92,7 @@
? 'border-b-2 border-amber-300 text-amber-100'
: 'text-slate-400 hover:text-slate-100'}"
>
Mot de passe
{$t.game.profile.tabPassword}
</button>
<button
onclick={() => handleTabChange('daily')}
@@ -99,7 +100,7 @@
? 'border-b-2 border-amber-300 text-amber-100'
: 'text-slate-400 hover:text-slate-100'}"
>
Historique Daily
{$t.game.profile.tabDaily}
</button>
<button
onclick={() => handleTabChange('sessions')}
@@ -107,7 +108,7 @@
? 'border-b-2 border-amber-300 text-amber-100'
: 'text-slate-400 hover:text-slate-100'}"
>
Sessions
{$t.game.profile.tabSessions}
</button>
<button
onclick={() => handleTabChange('friends')}
@@ -115,7 +116,7 @@
? 'border-b-2 border-amber-300 text-amber-100'
: 'text-slate-400 hover:text-slate-100'}"
>
Amis
{$t.game.profile.tabFriends}
</button>
</div>
@@ -127,7 +128,7 @@
{#if data.user.image}
<img
src={data.user.image}
alt={data.user.name || 'Profil'}
alt={data.user.name || $t.game.profile.avatarFallbackAlt}
class="h-24 w-24 rounded-full border-2 border-amber-300 object-cover"
/>
{:else}
@@ -136,7 +137,7 @@
</div>
{/if}
<div class="text-center">
<p class="text-sm text-slate-400">Email</p>
<p class="text-sm text-slate-400">{$t.game.profile.email}</p>
<p class="font-semibold text-white">{data.user.email}</p>
</div>
</div>
@@ -158,7 +159,7 @@
<!-- Name Field -->
<div>
<label for="name" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Nom d'affichage
{$t.game.profile.displayName}
</label>
<input
id="name"
@@ -166,7 +167,7 @@
name="name"
bind:value={name}
required
placeholder="Ton nom"
placeholder={$t.game.profile.displayNamePlaceholder}
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
/>
</div>
@@ -181,7 +182,7 @@
<!-- Success Message -->
{#if showSuccess}
<div class="rounded-lg border border-green-500/30 bg-green-900/20 px-4 py-3 text-sm text-green-200">
Profil mis à jour avec succès !
{$t.game.profile.profileUpdateSuccess}
</div>
{/if}
@@ -191,7 +192,7 @@
disabled={isLoading}
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
>
{isLoading ? 'Mise à jour...' : 'Enregistrer les modifications'}
{isLoading ? $t.game.profile.updating : $t.game.profile.saveChanges}
</button>
</form>
</div>
@@ -201,7 +202,7 @@
{#if activeTab === 'friends'}
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
Système d'amis
{$t.game.profile.friendsTitle}
</h2>
<form
@@ -218,7 +219,7 @@
class="mb-8 space-y-3"
>
<label for="friendUsername" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Ajouter un ami par nom d'utilisateur
{$t.game.profile.addFriendByUsername}
</label>
<div class="flex gap-2">
<input
@@ -227,7 +228,7 @@
name="friendUsername"
required
bind:value={friendUsername}
placeholder="ex: luffy_gear5"
placeholder={$t.game.profile.friendUsernamePlaceholder}
class="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
/>
<button
@@ -235,7 +236,7 @@
disabled={isLoading}
class="rounded-full bg-amber-300 px-4 py-2 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
>
{isLoading ? 'Envoi...' : 'Envoyer'}
{isLoading ? $t.game.profile.sending : $t.game.profile.send}
</button>
</div>
{#if form?.message}
@@ -245,9 +246,9 @@
<div class="space-y-8">
<div>
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Demandes reçues</h3>
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">{$t.game.profile.incomingRequests}</h3>
{#if incomingRequests.length === 0}
<p class="text-sm text-slate-400">Aucune demande reçue.</p>
<p class="text-sm text-slate-400">{$t.game.profile.noIncomingRequests}</p>
{:else}
<div class="space-y-3">
{#each incomingRequests as req (req.id)}
@@ -259,11 +260,11 @@
<div class="flex gap-2">
<form method="POST" action="?/acceptFriendRequest" use:enhance>
<input type="hidden" name="friendshipId" value={req.id} />
<button type="submit" class="rounded-lg border border-emerald-400/50 bg-emerald-900/20 px-3 py-1.5 text-xs font-semibold text-emerald-300 transition hover:bg-emerald-900/40">Accepter</button>
<button type="submit" class="rounded-lg border border-emerald-400/50 bg-emerald-900/20 px-3 py-1.5 text-xs font-semibold text-emerald-300 transition hover:bg-emerald-900/40">{$t.game.profile.accept}</button>
</form>
<form method="POST" action="?/declineFriendRequest" use:enhance>
<input type="hidden" name="friendshipId" value={req.id} />
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Refuser</button>
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">{$t.game.profile.decline}</button>
</form>
</div>
</div>
@@ -273,9 +274,9 @@
</div>
<div>
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Demandes envoyées</h3>
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">{$t.game.profile.outgoingRequests}</h3>
{#if outgoingRequests.length === 0}
<p class="text-sm text-slate-400">Aucune demande envoyée.</p>
<p class="text-sm text-slate-400">{$t.game.profile.noOutgoingRequests}</p>
{:else}
<div class="space-y-3">
{#each outgoingRequests as req (req.id)}
@@ -286,7 +287,7 @@
</div>
<form method="POST" action="?/cancelFriendRequest" use:enhance>
<input type="hidden" name="friendshipId" value={req.id} />
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Annuler</button>
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">{$t.game.profile.cancel}</button>
</form>
</div>
{/each}
@@ -295,9 +296,9 @@
</div>
<div>
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Mes amis</h3>
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">{$t.game.profile.myFriends}</h3>
{#if friends.length === 0}
<p class="text-sm text-slate-400">Tu n'as pas encore d'amis.</p>
<p class="text-sm text-slate-400">{$t.game.profile.noFriends}</p>
{:else}
<div class="space-y-3">
{#each friends as friend (friend.id)}
@@ -308,7 +309,7 @@
</div>
<form method="POST" action="?/removeFriend" use:enhance>
<input type="hidden" name="friendshipId" value={friend.id} />
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Supprimer</button>
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">{$t.game.profile.remove}</button>
</form>
</div>
{/each}
@@ -323,7 +324,7 @@
{#if activeTab === 'password'}
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
Changer le mot de passe
{$t.game.profile.changePasswordTitle}
</h2>
<!-- Form -->
@@ -345,7 +346,7 @@
<!-- Old Password Field -->
<div>
<label for="oldPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Mot de passe actuel
{$t.game.profile.currentPassword}
</label>
<input
id="oldPassword"
@@ -361,7 +362,7 @@
<!-- New Password Field -->
<div>
<label for="newPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Nouveau mot de passe
{$t.game.profile.newPassword}
</label>
<input
id="newPassword"
@@ -377,7 +378,7 @@
<!-- Confirm Password Field -->
<div>
<label for="confirmPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
Confirmer le mot de passe
{$t.game.profile.confirmPassword}
</label>
<input
id="confirmPassword"
@@ -400,7 +401,7 @@
<!-- Success Message -->
{#if showSuccess}
<div class="rounded-lg border border-green-500/30 bg-green-900/20 px-4 py-3 text-sm text-green-200">
Mot de passe changé avec succès !
{$t.game.profile.passwordChangeSuccess}
</div>
{/if}
@@ -410,7 +411,7 @@
disabled={isLoading}
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
>
{isLoading ? 'Changement en cours...' : 'Changer le mot de passe'}
{isLoading ? $t.game.profile.changing : $t.game.profile.changePassword}
</button>
</form>
</div>
@@ -420,11 +421,11 @@
{#if activeTab === 'daily'}
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
Historique des Daily
{$t.game.profile.dailyHistoryTitle}
</h2>
{#if dailyHistory.length === 0}
<p class="text-center text-slate-400">Aucun historique disponible</p>
<p class="text-center text-slate-400">{$t.game.profile.noDailyHistory}</p>
{:else}
<div class="space-y-4">
{#each dailyHistory as day (day.id)}
@@ -439,7 +440,7 @@
/>
{:else}
<div class="flex h-16 w-16 items-center justify-center rounded-lg border border-white/20 bg-slate-700">
<span class="text-xs text-slate-400">N/A</span>
<span class="text-xs text-slate-400">{$t.game.profile.noImage}</span>
</div>
{/if}
</div>
@@ -448,7 +449,7 @@
<div class="flex-1">
<p class="font-semibold text-white">{day.characterName}</p>
<p class="text-xs text-slate-400">
{new Date(day.date).toLocaleDateString('fr-FR', {
{new Date(day.date).toLocaleDateString($language === 'fr' ? 'fr-FR' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
@@ -459,7 +460,7 @@
<!-- Tries -->
<div class="flex flex-col items-end">
<p class="text-xs text-slate-400">
{day.tryCount} {day.tryCount === 1 ? 'tentative' : 'tentatives'}
{day.tryCount} {day.tryCount === 1 ? $t.game.profile.trySingular : $t.game.profile.tryPlural}
</p>
</div>
</div>
@@ -473,24 +474,24 @@
{#if activeTab === 'sessions'}
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
Sessions actives
{$t.game.profile.activeSessionsTitle}
</h2>
{#if sessions.length === 0}
<p class="text-center text-slate-400">Aucune session active</p>
<p class="text-center text-slate-400">{$t.game.profile.noActiveSessions}</p>
{:else}
<div class="space-y-4">
{#each sessions as sess (sess.id)}
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-white/5 px-4 py-4">
<div class="flex-1">
<p class="font-semibold text-white">
{sess.userAgent || 'Appareil inconnu'}
{sess.userAgent || $t.game.profile.unknownDevice}
</p>
<p class="text-xs text-slate-400">
IP: {sess.ipAddress || 'Inconnue'}
{$t.game.profile.ip}: {sess.ipAddress || $t.game.profile.unknown}
</p>
<p class="mt-1 text-xs text-slate-500">
Créée: {new Date(sess.createdAt).toLocaleDateString('fr-FR', {
{$t.game.profile.created}: {new Date(sess.createdAt).toLocaleDateString($language === 'fr' ? 'fr-FR' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
@@ -514,7 +515,7 @@
type="submit"
class="rounded-lg border border-red-500/50 bg-red-900/20 px-4 py-2 text-xs font-semibold text-red-300 transition hover:border-red-500 hover:bg-red-900/40"
>
Terminer
{$t.game.profile.terminate}
</button>
</form>
</div>
@@ -527,7 +528,7 @@
<!-- Back to Home -->
<div class="text-center">
<a href={resolve("/")} class="text-sm text-slate-400 transition hover:text-slate-300">
Retour à l'accueil
{$t.game.profile.backHome}
</a>
</div>
</div>