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:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { CharacterWithRelations } from '$lib/server/daily-character';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
let {
|
||||
characters,
|
||||
@@ -138,13 +139,13 @@
|
||||
</script>
|
||||
|
||||
<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 z-10">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Entrer une supposition</h2>
|
||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">{$t.game.components.searchInput.title}</h2>
|
||||
<div class="mt-4 flex flex-col gap-3 sm:flex-row">
|
||||
<div bind:this={state.searchContainer} class="relative w-full">
|
||||
<input
|
||||
bind:value={state.searchInput}
|
||||
class="w-full rounded-full border border-amber-200/30 bg-slate-900/60 px-5 py-3 text-sm text-slate-100 placeholder:text-slate-400 focus:border-amber-200/70 focus:outline-none"
|
||||
placeholder="Nom du personnage"
|
||||
placeholder={$t.game.components.searchInput.placeholder}
|
||||
type="text"
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
@@ -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}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { formatBounty } from '$lib';
|
||||
import type { CharacterWithRelations } from '$lib/server/daily-character';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
export let selectedCharacters: CharacterWithRelations[];
|
||||
export let dailyCharacter: CharacterWithRelations;
|
||||
@@ -68,10 +69,10 @@
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col items-center gap-4 text-center">
|
||||
<p class="text-xs font-semibold tracking-[0.28em] text-amber-100 uppercase">Historique</p>
|
||||
<p class="text-xs font-semibold tracking-[0.28em] text-amber-100 uppercase">{$t.game.components.guessHistory.title}</p>
|
||||
</div>
|
||||
{#if selectedCharacters.length === 0}
|
||||
<p class="text-center text-sm text-slate-200">Aucune tentative pour le moment.</p>
|
||||
<p class="text-center text-sm text-slate-200">{$t.game.components.guessHistory.empty}</p>
|
||||
{:else}
|
||||
<div class="-mx-6 overflow-x-auto px-6 pb-2 sm:mx-0 sm:px-0">
|
||||
<div class="mx-auto w-max min-w-max">
|
||||
@@ -83,7 +84,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Personnage
|
||||
{$t.game.components.guessHistory.character}
|
||||
</p>
|
||||
</div>
|
||||
{#if columnVisibility.status !== false}
|
||||
@@ -93,7 +94,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Statut
|
||||
{$t.game.components.guessHistory.status}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -104,7 +105,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Genre
|
||||
{$t.game.components.guessHistory.gender}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -115,7 +116,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Affiliations
|
||||
{$t.game.components.guessHistory.affiliations}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -126,7 +127,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Fruit
|
||||
{$t.game.components.guessHistory.fruit}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -137,7 +138,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Haki
|
||||
{$t.game.components.guessHistory.haki}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -148,7 +149,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Prime
|
||||
{$t.game.components.guessHistory.bounty}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -159,7 +160,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Taille
|
||||
{$t.game.components.guessHistory.height}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -170,7 +171,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Origine
|
||||
{$t.game.components.guessHistory.origin}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -181,7 +182,7 @@
|
||||
<p
|
||||
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||
>
|
||||
Arc
|
||||
{$t.game.components.guessHistory.arc}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -229,14 +230,14 @@
|
||||
>
|
||||
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
||||
{character.status === 'Alive'
|
||||
? 'Vivant'
|
||||
? $t.game.components.guessHistory.alive
|
||||
: character.status === 'Dead'
|
||||
? 'Mort'
|
||||
? $t.game.components.guessHistory.dead
|
||||
: character.status === 'Unknown'
|
||||
? 'Inconnu'
|
||||
? $t.game.components.guessHistory.unknown
|
||||
: character.status === null
|
||||
? '-'
|
||||
: character.status || 'Inconnu'}
|
||||
: character.status || $t.game.components.guessHistory.unknown}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -251,10 +252,10 @@
|
||||
>
|
||||
<p class="text-center text-xs font-bold text-white sm:text-sm md:text-base">
|
||||
{character.gender === 'Male'
|
||||
? 'Homme'
|
||||
? $t.game.components.guessHistory.male
|
||||
: character.gender === 'Female'
|
||||
? 'Femme'
|
||||
: character.gender || 'Inconnu'}
|
||||
? $t.game.components.guessHistory.female
|
||||
: character.gender || $t.game.components.guessHistory.unknown}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -322,10 +323,10 @@
|
||||
})()} flex items-center justify-center p-1 sm:p-2"
|
||||
>
|
||||
<p class="text-center text-sm font-bold text-white sm:text-lg md:text-2xl">
|
||||
{#if character.hakiObservation}<span title="Haki de l'Observation">👁️</span
|
||||
{#if character.hakiObservation}<span title={$t.game.components.guessHistory.obsHakiTitle}>👁️</span
|
||||
>{/if}
|
||||
{#if character.hakiArmament}<span title="Haki de l'Armement">🦾</span>{/if}
|
||||
{#if character.hakiConqueror}<span title="Haki des Rois">👑</span>{/if}
|
||||
{#if character.hakiArmament}<span title={$t.game.components.guessHistory.armHakiTitle}>🦾</span>{/if}
|
||||
{#if character.hakiConqueror}<span title={$t.game.components.guessHistory.kingHakiTitle}>👑</span>{/if}
|
||||
{#if !character.hakiObservation && !character.hakiArmament && !character.hakiConqueror}
|
||||
<span class="text-2xl sm:text-3xl md:text-5xl">✕</span>
|
||||
{/if}
|
||||
@@ -362,7 +363,7 @@
|
||||
<p
|
||||
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||
>
|
||||
Inconnue
|
||||
{$t.game.components.guessHistory.unknown}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -397,7 +398,7 @@
|
||||
<p
|
||||
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||
>
|
||||
Inconnue
|
||||
{$t.game.components.guessHistory.unknown}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -412,7 +413,7 @@
|
||||
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
||||
>
|
||||
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
||||
{character.origin || 'Inconnue'}
|
||||
{character.origin || $t.game.components.guessHistory.unknown}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -439,7 +440,7 @@
|
||||
<p
|
||||
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||
>
|
||||
{character.arcName || 'Inconnu'}
|
||||
{character.arcName || $t.game.components.guessHistory.unknown}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { CharacterWithRelations } from "$lib/server/daily-character";
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
export let dailyCharacter: CharacterWithRelations;
|
||||
export let selectedCharacters: CharacterWithRelations[];
|
||||
@@ -44,13 +45,13 @@
|
||||
disabled={!isOriginAvailable}
|
||||
onclick={() => showHintOrigin = !showHintOrigin}
|
||||
>
|
||||
<p class="text-sm font-medium text-amber-100">Origine</p>
|
||||
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.origin}</p>
|
||||
{#if showHintOrigin}
|
||||
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.origin || 'Inconnue'}</p>
|
||||
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.origin || $t.game.components.hints.unknown}</p>
|
||||
{:else if Math.max(0, 5 - selectedCharacters.length) > 0}
|
||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 5 - selectedCharacters.length)} essais avant déblocage</p>
|
||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 5 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
|
||||
{:else}
|
||||
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
|
||||
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
@@ -59,13 +60,13 @@
|
||||
disabled={!isFruitAvailable}
|
||||
onclick={() => showHintFruit = !showHintFruit}
|
||||
>
|
||||
<p class="text-sm font-medium text-amber-100">Fruit du démon</p>
|
||||
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.devilFruit}</p>
|
||||
{#if showHintFruit}
|
||||
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.devilFruitName || 'Aucun'}</p>
|
||||
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.devilFruitName || $t.game.components.hints.none}</p>
|
||||
{:else if Math.max(0, 10 - selectedCharacters.length) > 0}
|
||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 10 - selectedCharacters.length)} essais avant déblocage</p>
|
||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 10 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
|
||||
{:else}
|
||||
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
|
||||
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
@@ -74,16 +75,16 @@
|
||||
disabled={!isAffiliationAvailable}
|
||||
onclick={() => showHintAffiliation = !showHintAffiliation}
|
||||
>
|
||||
<p class="text-sm font-medium text-amber-100">Affiliation</p>
|
||||
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.affiliation}</p>
|
||||
{#if showHintAffiliation}
|
||||
{@const affiliations = typeof dailyCharacter.affiliations === 'string'
|
||||
? ((dailyCharacter.affiliations as string).includes('[') ? JSON.parse(dailyCharacter.affiliations) : (dailyCharacter.affiliations as string).split(',').map((a: string) => a.trim()))
|
||||
: dailyCharacter.affiliations}
|
||||
<p class="mt-2 text-xs text-white font-semibold">{Array.isArray(affiliations) ? affiliations[0] : affiliations || 'Inconnue'}</p>
|
||||
<p class="mt-2 text-xs text-white font-semibold">{Array.isArray(affiliations) ? affiliations[0] : affiliations || $t.game.components.hints.unknown}</p>
|
||||
{:else if Math.max(0, 15 - selectedCharacters.length) > 0}
|
||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 15 - selectedCharacters.length)} essais avant déblocage</p>
|
||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 15 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
|
||||
{:else}
|
||||
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
|
||||
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
104
src/lib/components/LanguageSwitcher.svelte
Normal file
104
src/lib/components/LanguageSwitcher.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { availableLanguages, language, setLanguage } from '$lib/i18n';
|
||||
|
||||
let isOpen = false;
|
||||
let rootElement: HTMLDivElement | undefined;
|
||||
|
||||
const languageLabels: Record<string, string> = {
|
||||
en: 'English',
|
||||
fr: 'Francais'
|
||||
};
|
||||
|
||||
const languageFlags: Record<string, string> = {
|
||||
en: 'GB',
|
||||
fr: 'FR'
|
||||
};
|
||||
|
||||
function getLanguageLabel(lang: string): string {
|
||||
return languageLabels[lang] || lang.toUpperCase();
|
||||
}
|
||||
|
||||
function getFlagCode(lang: string): string {
|
||||
return languageFlags[lang] || 'UN';
|
||||
}
|
||||
|
||||
function toFlagEmoji(code: string): string {
|
||||
const normalized = code.toUpperCase();
|
||||
if (normalized.length !== 2) {
|
||||
return 'UN';
|
||||
}
|
||||
|
||||
const first = normalized.codePointAt(0);
|
||||
const second = normalized.codePointAt(1);
|
||||
if (!first || !second) {
|
||||
return 'UN';
|
||||
}
|
||||
|
||||
return String.fromCodePoint(127397 + first, 127397 + second);
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
isOpen = !isOpen;
|
||||
}
|
||||
|
||||
function selectLanguage(lang: string) {
|
||||
setLanguage(lang);
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const onDocumentClick = (event: MouseEvent) => {
|
||||
if (!rootElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rootElement.contains(event.target as Node)) {
|
||||
isOpen = false;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', onDocumentClick);
|
||||
return () => document.removeEventListener('click', onDocumentClick);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={rootElement} class="relative">
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleMenu}
|
||||
class="flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-2 text-sm font-semibold text-slate-100 transition hover:border-amber-300/50 hover:bg-white/10"
|
||||
aria-haspopup="true"
|
||||
aria-expanded={isOpen}
|
||||
aria-label="Change language"
|
||||
>
|
||||
<span class="text-base" aria-hidden="true">{toFlagEmoji(getFlagCode($language))}</span>
|
||||
<span class="uppercase text-xs tracking-wider">{$language}</span>
|
||||
<svg
|
||||
class="h-3.5 w-3.5 transition-transform {isOpen ? 'rotate-180' : ''}"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="absolute right-0 top-full z-20 mt-2 w-44 rounded-xl border border-white/10 bg-slate-900/95 p-1 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||
{#each availableLanguages as lang (lang)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => selectLanguage(lang)}
|
||||
class="flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm transition {lang === $language ? 'bg-amber-300 text-slate-900' : 'text-slate-100 hover:bg-white/5'}"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="text-base" aria-hidden="true">{toFlagEmoji(getFlagCode(lang))}</span>
|
||||
<span>{getLanguageLabel(lang)}</span>
|
||||
</span>
|
||||
<span class="text-xs uppercase tracking-wide opacity-70">{lang}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,28 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { CharacterWithRelations } from "$lib/server/daily-character";
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
export let selectedCharacter: CharacterWithRelations;
|
||||
export let selectedCharacters: CharacterWithRelations[];
|
||||
export let isGeckoMoriaWin: boolean = false;
|
||||
|
||||
const oneTryMessages = ['Tricheur 👀', '1 essai ? Avoue, tu avais la réponse 😏', 'Premier coup direct... suspect 🤨'];
|
||||
const twoTryMessages = ['Bien joué ! ⚡', 'Deux essais, propre ! 👏', 'Tu chauffes vite, bien joué 🔥'];
|
||||
const tenPlusMessages = [
|
||||
'${attempts} essais... même un escargophone aurait trouvé plus vite 📞',
|
||||
'${attempts} tentatives ? Le Grand Line est moins long que ça 😵',
|
||||
'${attempts} essais : performance légendaire... dans le mauvais sens 🫠'
|
||||
];
|
||||
const fivePlusMessages = [
|
||||
"${attempts} essais ? On va dire que c'était pour le suspense 😅",
|
||||
'Ça en fait des essais... mais au moins tu y es arrivé 😬',
|
||||
'Tu ne lâches rien, même après plusieurs essais 😂'
|
||||
];
|
||||
const defaultMessages = ['Pas mal du tout !', 'Bien tenté, bon rythme 👍', 'Ça se passe bien, continue comme ça ✨'];
|
||||
|
||||
const pickMessage = (messages: string[]) => messages[Math.floor(Math.random() * messages.length)];
|
||||
const pickMessage = (messages: readonly string[]) => messages[Math.floor(Math.random() * messages.length)];
|
||||
|
||||
const getAttemptMessage = (attempts: number): string => {
|
||||
if (attempts <= 0) return '';
|
||||
const oneTryMessages = $t.game.components.winPanel.oneTryMessages;
|
||||
const twoTryMessages = $t.game.components.winPanel.twoTryMessages;
|
||||
const tenPlusMessages = $t.game.components.winPanel.tenPlusMessages;
|
||||
const fivePlusMessages = $t.game.components.winPanel.fivePlusMessages;
|
||||
const defaultMessages = $t.game.components.winPanel.defaultMessages;
|
||||
if (attempts === 1) {
|
||||
return pickMessage(oneTryMessages);
|
||||
}
|
||||
@@ -41,14 +33,17 @@
|
||||
|
||||
$: attempts = selectedCharacters.length;
|
||||
$: attemptMessage = getAttemptMessage(attempts);
|
||||
$: attemptWord = selectedCharacters.length > 1
|
||||
? $t.game.components.winPanel.attemptPlural
|
||||
: $t.game.components.winPanel.attemptSingular;
|
||||
</script>
|
||||
|
||||
{#if isGeckoMoriaWin}
|
||||
<div class="rounded-3xl border border-slate-700/80 bg-slate-950/80 p-4 shadow-[0_24px_60px_rgba(0,0,0,0.8)] backdrop-blur gecko-moria-effect">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl mb-2">🌑</div>
|
||||
<h2 class="text-xl font-bold text-slate-300 mb-1">Moria vous contrôle...</h2>
|
||||
<p class="text-sm text-slate-400">Vous avez succombé à l'ombre en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !</p>
|
||||
<h2 class="text-xl font-bold text-slate-300 mb-1">{$t.game.components.winPanel.moriaTitle}</h2>
|
||||
<p class="text-sm text-slate-400">{$t.game.components.winPanel.moriaPrefix} {selectedCharacters.length} {attemptWord} !</p>
|
||||
<p class="text-xs text-slate-300 mt-1">{attemptMessage}</p>
|
||||
<div class="mt-3">
|
||||
{#if selectedCharacter.pictureUrl}
|
||||
@@ -73,8 +68,8 @@
|
||||
<div class="rounded-3xl border border-emerald-500/50 bg-emerald-500/10 p-4 shadow-[0_24px_60px_rgba(16,185,129,0.3)] backdrop-blur">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl mb-2">🎉</div>
|
||||
<h2 class="text-xl font-bold text-emerald-400 mb-1">Félicitations !</h2>
|
||||
<p class="text-sm text-emerald-300">Vous avez trouvé le personnage en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !</p>
|
||||
<h2 class="text-xl font-bold text-emerald-400 mb-1">{$t.game.components.winPanel.winTitle}</h2>
|
||||
<p class="text-sm text-emerald-300">{$t.game.components.winPanel.winPrefix} {selectedCharacters.length} {attemptWord} !</p>
|
||||
<p class="text-xs text-emerald-200 mt-1">{attemptMessage}</p>
|
||||
<div class="mt-3">
|
||||
{#if selectedCharacter.pictureUrl}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { CharacterWithRelations } from "$lib/server/daily-character";
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
export let yesterdayCharacter: CharacterWithRelations | null;
|
||||
</script>
|
||||
@@ -15,11 +16,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.components.yesterdayCharacter.photo}
|
||||
</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.components.yesterdayCharacter.title}</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">
|
||||
@@ -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}
|
||||
</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.components.yesterdayCharacter.photo}
|
||||
</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.components.yesterdayCharacter.title}</p>
|
||||
<p class="mt-2 text-lg font-semibold text-white">{$t.game.components.yesterdayCharacter.none}</p>
|
||||
<p class="mt-1 text-sm text-slate-200">{$t.game.components.yesterdayCharacter.noneAvailable}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
227
src/lib/i18n/en.json
Normal file
227
src/lib/i18n/en.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
227
src/lib/i18n/fr.json
Normal file
227
src/lib/i18n/fr.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/lib/i18n/index.ts
Normal file
51
src/lib/i18n/index.ts
Normal file
@@ -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<string, Messages> = { 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<string> = writable(getInitialLanguage());
|
||||
|
||||
// Create derived store for the current messages
|
||||
export const t: Readable<Messages> = 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);
|
||||
@@ -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,8 +12,11 @@
|
||||
<a href={resolve("/")} class="text-lg font-black uppercase tracking-[0.15em] text-amber-50 transition hover:text-amber-100">
|
||||
OnePieceDle
|
||||
</a>
|
||||
<div class="flex items-center gap-3">
|
||||
<LanguageSwitcher />
|
||||
<ProfileButton user={data.user} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main class="pt-20">
|
||||
{@render children()}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user