feat: add French localization support for character attributes and improve character display logic
Build Docker Image / build (push) Successful in 1m18s

- Added optional French names, affiliations, origins, and epithets to character records.
- Updated character import logic to handle new French fields.
- Enhanced character search and display components to show French names and epithets based on selected language.
- Modified database schema to include French fields for characters.
- Improved error handling in daily character setup to check for existing characters.
- Refactored components to utilize helper functions for displaying names and attributes based on language.
This commit is contained in:
2026-03-15 22:00:19 +01:00
parent bd121b7d85
commit 997b2f1781
15 changed files with 655 additions and 236 deletions
+51 -29
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import type { CharacterWithRelations } from '$lib/server/daily-character';
import { onMount } from 'svelte';
import { t } from '$lib/i18n';
import { language, t } from '$lib/i18n';
let {
characters,
@@ -20,6 +20,46 @@
searchContainer: null as HTMLDivElement | null
});
const isFrench = $derived($language === 'fr');
function parseEpithets(value: unknown): string[] {
if (Array.isArray(value)) {
return value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
return parsed.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
}
} catch {
if (value.length > 0) {
return [value];
}
}
}
return [];
}
function getDisplayName(character: CharacterWithRelations): string {
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
return character.frName;
}
return character.name;
}
function getDisplayEpithets(character: CharacterWithRelations): string[] {
const frenchEpithets = parseEpithets(character.frEpithets);
if (isFrench && frenchEpithets.length > 0) {
return frenchEpithets;
}
return parseEpithets(character.epithets);
}
function normalizeSearchText(value: string): string {
return value
.normalize('NFD')
@@ -40,25 +80,12 @@
const searchTerm = normalizeSearchText(state.searchInput);
return characters.filter((char) => {
const nameMatches = normalizeSearchText(char.name).includes(searchTerm);
let epithetsMatches = false;
if (char.epithets) {
try {
const parsedEpithets =
typeof char.epithets === 'string' ? JSON.parse(char.epithets) : char.epithets;
if (Array.isArray(parsedEpithets)) {
epithetsMatches = parsedEpithets.some((epithet: string) =>
normalizeSearchText(epithet).includes(searchTerm)
);
} else if (typeof parsedEpithets === 'string') {
epithetsMatches = normalizeSearchText(parsedEpithets).includes(searchTerm);
}
} catch {
epithetsMatches = normalizeSearchText(String(char.epithets)).includes(searchTerm);
}
}
const displayName = getDisplayName(char);
const displayEpithets = getDisplayEpithets(char);
const nameMatches = normalizeSearchText(displayName).includes(searchTerm);
const epithetsMatches = displayEpithets.some((epithet) =>
normalizeSearchText(epithet).includes(searchTerm)
);
return (nameMatches || epithetsMatches) &&
!selectedCharacters.some((selected) => selected.id === char.id);
@@ -161,7 +188,7 @@
{#if character.pictureUrl}
<img
src={character.pictureUrl}
alt={character.name}
alt={getDisplayName(character)}
loading="lazy"
class="w-12 h-12 rounded-full object-cover border border-amber-200/30"
/>
@@ -171,16 +198,11 @@
</div>
{/if}
<div class="flex-1">
<span class="font-semibold text-amber-100">{character.name}</span>
{#if character.epithets}
{@const parsedEpithets = typeof character.epithets === 'string'
? JSON.parse(character.epithets)
: character.epithets}
{#if Array.isArray(parsedEpithets) && parsedEpithets.length > 0}
<span class="font-semibold text-amber-100">{getDisplayName(character)}</span>
{#if getDisplayEpithets(character).length > 0}
<span class="ml-2 text-xs text-slate-400">
{parsedEpithets.join(', ')}
{getDisplayEpithets(character).join(', ')}
</span>
{/if}
{/if}
</div>
</button>
+56 -11
View File
@@ -1,7 +1,8 @@
<script lang="ts">
import { formatBounty } from '$lib';
import { resolve } from '$app/paths';
import type { CharacterWithRelations } from '$lib/server/daily-character';
import { t } from '$lib/i18n';
import { language, t } from '$lib/i18n';
export let selectedCharacters: CharacterWithRelations[];
export let dailyCharacter: CharacterWithRelations;
@@ -62,6 +63,52 @@
const dailyPrimary = firstAffiliation(dailyAffiliations);
return characterPrimary === dailyPrimary;
}
$: isFrench = $language === 'fr';
function getDisplayName(character: CharacterWithRelations): string {
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
return character.frName;
}
return character.name;
}
function getWikiUrl(character: CharacterWithRelations): string {
if (isFrench && typeof character.frUrl === 'string' && character.frUrl.length > 0) {
return character.frUrl;
}
return character.url || '';
}
function getWikiBaseUrl(): string {
return isFrench ? 'https://onepiece.fandom.com/fr/wiki/' : 'https://onepiece.fandom.com/wiki/';
}
function getDisplayOrigin(character: CharacterWithRelations): string | null {
if (isFrench && typeof character.frOrigin === 'string' && character.frOrigin.length > 0) {
return character.frOrigin;
}
return character.origin;
}
function hasMatchingOrigin(characterEntry: CharacterWithRelations, dailyEntry: CharacterWithRelations): boolean {
return getDisplayOrigin(characterEntry) === getDisplayOrigin(dailyEntry);
}
function getDisplayArcName(character: CharacterWithRelations): string | null {
if (isFrench && typeof character.frArcName === 'string' && character.frArcName.length > 0) {
return character.frArcName;
}
return character.arcName;
}
function hasMatchingArc(characterEntry: CharacterWithRelations, dailyEntry: CharacterWithRelations): boolean {
return getDisplayArcName(characterEntry) === getDisplayArcName(dailyEntry);
}
</script>
<section
@@ -197,14 +244,14 @@
>
{#if character.pictureUrl}
<a
href={'https://onepiece.fandom.com/fr/wiki/' + character.url}
href={getWikiBaseUrl() + getWikiUrl(character)}
target="_blank"
rel="noopener noreferrer"
class="block h-full w-full"
>
<img
src={character.pictureUrl}
alt={character.name}
alt={getDisplayName(character)}
class="h-full w-full cursor-pointer object-cover transition-opacity hover:opacity-80"
/>
</a>
@@ -214,7 +261,7 @@
>
<span
class="line-clamp-3 text-center text-xs font-semibold sm:text-sm md:text-xl"
>{character.name}</span
>{getDisplayName(character)}</span
>
</div>
{/if}
@@ -407,13 +454,12 @@
<!-- Origine -->
{#if columnVisibility.origin !== false}
<div
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.origin ===
dailyCharacter.origin
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {hasMatchingOrigin(character, dailyCharacter)
? 'bg-emerald-600/90'
: '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 || $t.game.components.guessHistory.unknown}
{getDisplayOrigin(character) || $t.game.components.guessHistory.unknown}
</p>
</div>
{/if}
@@ -421,12 +467,11 @@
<!-- Arc -->
{#if columnVisibility.arc !== false}
<div
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.arcName ===
dailyCharacter.arcName
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {hasMatchingArc(character, dailyCharacter)
? 'bg-emerald-600/90'
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
>
{#if character.arcName !== dailyCharacter.arcName && character.firstAppearance && dailyCharacter.firstAppearance && character.firstAppearance !== dailyCharacter.firstAppearance}
{#if !hasMatchingArc(character, dailyCharacter) && character.firstAppearance && dailyCharacter.firstAppearance && character.firstAppearance !== dailyCharacter.firstAppearance}
<div
class="pointer-events-none absolute h-full w-full opacity-30"
style="
@@ -440,7 +485,7 @@
<p
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
>
{character.arcName || $t.game.components.guessHistory.unknown}
{getDisplayArcName(character) || $t.game.components.guessHistory.unknown}
</p>
</div>
{/if}
+11 -2
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import type { CharacterWithRelations } from "$lib/server/daily-character";
import { t } from '$lib/i18n';
import { language, t } from '$lib/i18n';
export let dailyCharacter: CharacterWithRelations;
export let selectedCharacters: CharacterWithRelations[];
@@ -16,6 +16,15 @@
$: isOriginAvailable = selectedCharacters.length >= 5;
$: isFruitAvailable = selectedCharacters.length >= 10;
$: isAffiliationAvailable = selectedCharacters.length >= 15;
$: isFrench = $language === 'fr';
function getDisplayOrigin(character: CharacterWithRelations): string | null {
if (isFrench && typeof character.frOrigin === 'string' && character.frOrigin.length > 0) {
return character.frOrigin;
}
return character.origin;
}
</script>
<svelte:head>
@@ -47,7 +56,7 @@
>
<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 || $t.game.components.hints.unknown}</p>
<p class="mt-2 text-xs text-white font-semibold">{getDisplayOrigin(dailyCharacter) || $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)} {$t.game.components.hints.beforeUnlock}</p>
{:else}
+29 -7
View File
@@ -1,11 +1,33 @@
<script lang="ts">
import type { CharacterWithRelations } from "$lib/server/daily-character";
import { t } from '$lib/i18n';
import { language, t } from '$lib/i18n';
export let selectedCharacter: CharacterWithRelations;
export let selectedCharacters: CharacterWithRelations[];
export let isGeckoMoriaWin: boolean = false;
$: isFrench = $language === 'fr';
function getDisplayName(character: CharacterWithRelations): string {
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
return character.frName;
}
return character.name;
}
function getWikiUrl(character: CharacterWithRelations): string {
if (isFrench && typeof character.frUrl === 'string' && character.frUrl.length > 0) {
return character.frUrl;
}
return character.url || '';
}
function getWikiBaseUrl(): string {
return isFrench ? 'https://onepiece.fandom.com/fr/wiki/' : 'https://onepiece.fandom.com/wiki/';
}
const pickMessage = (messages: readonly string[]) => messages[Math.floor(Math.random() * messages.length)];
const getAttemptMessage = (attempts: number): string => {
@@ -48,19 +70,19 @@
<div class="mt-3">
{#if selectedCharacter.pictureUrl}
<a
href={"https://onepiece.fandom.com/fr/wiki/" + selectedCharacter.url}
href={getWikiBaseUrl() + getWikiUrl(selectedCharacter)}
target="_blank"
rel="noopener noreferrer"
class="inline-block"
>
<img
src={selectedCharacter.pictureUrl}
alt={selectedCharacter.name}
alt={getDisplayName(selectedCharacter)}
class="w-20 h-20 mx-auto rounded-full border-2 border-slate-600 shadow-lg object-cover hover:border-slate-500 transition-colors cursor-pointer opacity-80"
/>
</a>
{/if}
<p class="mt-2 text-lg font-bold text-slate-200">{selectedCharacter.name}</p>
<p class="mt-2 text-lg font-bold text-slate-200">{getDisplayName(selectedCharacter)}</p>
</div>
</div>
</div>
@@ -74,19 +96,19 @@
<div class="mt-3">
{#if selectedCharacter.pictureUrl}
<a
href={"https://onepiece.fandom.com/fr/wiki/" + selectedCharacter.url}
href={getWikiBaseUrl() + getWikiUrl(selectedCharacter)}
target="_blank"
rel="noopener noreferrer"
class="inline-block"
>
<img
src={selectedCharacter.pictureUrl}
alt={selectedCharacter.name}
alt={getDisplayName(selectedCharacter)}
class="w-20 h-20 mx-auto rounded-full border-2 border-emerald-400 shadow-lg object-cover hover:border-emerald-300 transition-colors cursor-pointer"
/>
</a>
{/if}
<p class="mt-2 text-lg font-bold text-white">{selectedCharacter.name}</p>
<p class="mt-2 text-lg font-bold text-white">{getDisplayName(selectedCharacter)}</p>
</div>
</div>
</div>
+73 -15
View File
@@ -1,8 +1,57 @@
<script lang="ts">
import type { CharacterWithRelations } from "$lib/server/daily-character";
import { t } from '$lib/i18n';
import { language, t } from '$lib/i18n';
export let yesterdayCharacter: CharacterWithRelations | null;
$: isFrench = $language === 'fr';
function parseEpithets(value: unknown): string[] {
if (Array.isArray(value)) {
return value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
return parsed.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
}
} catch {
if (value.length > 0) {
return [value];
}
}
}
return [];
}
function getDisplayName(character: CharacterWithRelations): string {
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
return character.frName;
}
return character.name;
}
function getDisplayEpithets(character: CharacterWithRelations): string[] {
const frenchEpithets = parseEpithets(character.frEpithets);
if (isFrench && frenchEpithets.length > 0) {
return frenchEpithets;
}
return parseEpithets(character.epithets);
}
function getWikiUrl(character: CharacterWithRelations): string {
if (isFrench && typeof character.frUrl === 'string' && character.frUrl.length > 0) {
return character.frUrl;
}
return character.url || '';
}
</script>
<section class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
@@ -11,7 +60,7 @@
{#if yesterdayCharacter.pictureUrl}
<img
src={yesterdayCharacter.pictureUrl}
alt={yesterdayCharacter.name}
alt={getDisplayName(yesterdayCharacter)}
class="h-20 w-20 rounded-full border border-amber-200/40 object-cover"
/>
{:else}
@@ -21,23 +70,32 @@
{/if}
<div class="flex-1">
<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-2 text-lg font-semibold text-white">{getDisplayName(yesterdayCharacter)}</p>
{#if getDisplayEpithets(yesterdayCharacter).length > 0}
<p class="mt-1 text-sm text-slate-400">
{typeof yesterdayCharacter.epithets === 'string'
? JSON.parse(yesterdayCharacter.epithets).join(', ')
: (yesterdayCharacter.epithets as string[]).join(', ')}
{getDisplayEpithets(yesterdayCharacter).join(', ')}
</p>
{/if}
</div>
<a
href={"https://onepiece.fandom.com/fr/wiki/" + yesterdayCharacter.url}
target="_blank"
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"
>
{$t.game.components.yesterdayCharacter.openPage}
</a>
{#if isFrench}
<a
href="https://onepiece.fandom.com/fr/wiki/{getWikiUrl(yesterdayCharacter)}"
target="_blank"
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"
>
{$t.game.components.yesterdayCharacter.openPage}
</a>
{:else}
<a
href="https://onepiece.fandom.com/wiki/{getWikiUrl(yesterdayCharacter)}"
target="_blank"
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"
>
{$t.game.components.yesterdayCharacter.openPage}
</a>
{/if}
</div>
{:else}
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
+10 -1
View File
@@ -8,9 +8,11 @@ const RANDOM_SEED = Math.random();
const characterWithRelationsSelect = {
id: character.id,
name: character.name,
frName: character.frName,
gender: character.gender,
age: character.age,
affiliations: character.affiliations,
frAffiliations: character.frAffiliations,
devilFruitId: character.devilFruitId,
devilFruitName: devilFruit.name,
devilFruitType: devilFruit.type,
@@ -20,19 +22,24 @@ const characterWithRelationsSelect = {
bounty: character.bounty,
height: character.height,
origin: character.origin,
frOrigin: character.frOrigin,
firstAppearance: character.firstAppearance,
pictureUrl: character.pictureUrl,
epithets: character.epithets,
frEpithets: character.frEpithets,
status: character.status,
url: character.url,
frUrl: character.frUrl,
arcId: character.arcId,
arcName: arc.name
arcName: arc.name,
frArcName: arc.frName,
};
export type CharacterWithRelations = Character & {
devilFruitName: string | null;
devilFruitType: string | null;
arcName: string | null;
frArcName: string | null;
};
type RelationMaps = {
@@ -68,8 +75,10 @@ function mergeCharacterWithOverride(
if (relationMaps) {
if (mergedCharacter.arcId) {
mergedCharacter.arcName = relationMaps.arcNameById.get(mergedCharacter.arcId) ?? null;
mergedCharacter.frArcName = relationMaps.arcNameById.get(mergedCharacter.arcId) ?? null;
} else {
mergedCharacter.arcName = null;
mergedCharacter.frArcName = null;
}
if (mergedCharacter.devilFruitId) {
+5
View File
@@ -44,6 +44,7 @@ export const character = sqliteTable('character', {
gender: text('gender'),
age: integer('age'),
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
frAffiliations: text('fr_affiliations', { mode: 'json' }).$type<string[]>(),
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id),
hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false),
hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false),
@@ -72,6 +73,7 @@ export const characterOverride = sqliteTable('character_override', {
gender: text('gender'),
age: integer('age'),
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
frAffiliations: text('fr_affiliations', { mode: 'json' }).$type<string[]>(),
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id, { onDelete: 'set null' }),
hakiObservation: integer('haki_observation', { mode: 'boolean' }),
hakiArmament: integer('haki_armament', { mode: 'boolean' }),
@@ -79,9 +81,11 @@ export const characterOverride = sqliteTable('character_override', {
bounty: integer('bounty'),
height: real('height'),
origin: text('origin'),
frOrigin: text('fr_origin'),
firstAppearance: integer('first_appearance'),
pictureUrl: text('picture_url'),
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
frEpithets: text('fr_epithets', { mode: 'json' }).$type<string[]>(),
status: text('status').$type<Status | null>(),
arcId: text('arc_id').references(() => arc.id, { onDelete: 'set null' }),
url: text('url'),
@@ -99,6 +103,7 @@ export const characterScrapeValidation = sqliteTable('character_scrape_validatio
gender: text('gender'),
age: integer('age'),
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
frAffiliations: text('fr_affiliations', { mode: 'json' }).$type<string[]>(),
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id, { onDelete: 'set null' }),
hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false),
hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false),
+73 -15
View File
@@ -2,9 +2,58 @@
export let data;
import { resolve } from '$app/paths';
import { t } from '$lib/i18n';
import { language, t } from '$lib/i18n';
import type { CharacterWithRelations } from '$lib/server/daily-character';
$: yesterdayCharacter = data.yesterdayCharacter;
$: isFrench = $language === 'fr';
function parseEpithets(value: unknown): string[] {
if (Array.isArray(value)) {
return value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
return parsed.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
}
} catch {
if (value.length > 0) {
return [value];
}
}
}
return [];
}
function getDisplayName(character: CharacterWithRelations | null): string {
if (isFrench && typeof character?.frName === 'string' && character.frName.length > 0) {
return character.frName;
}
return character?.name || '';
}
function getDisplayEpithets(character: CharacterWithRelations | null): string[] {
const frenchEpithets = parseEpithets(character?.frEpithets);
if (isFrench && frenchEpithets.length > 0) {
return frenchEpithets;
}
return parseEpithets(character?.epithets);
}
function getWikiUrl(character: CharacterWithRelations | null): string {
if (isFrench && typeof character?.frUrl === 'string' && character.frUrl.length > 0) {
return character.frUrl;
}
return character?.url || '';
}
</script>
<svelte:head>
@@ -57,7 +106,7 @@
{#if yesterdayCharacter.pictureUrl}
<img
src={yesterdayCharacter.pictureUrl}
alt={yesterdayCharacter.name}
alt={getDisplayName(yesterdayCharacter)}
class="h-20 w-20 rounded-full border border-amber-200/40 object-cover"
/>
{:else}
@@ -67,23 +116,32 @@
{/if}
<div class="flex-1">
<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-2 text-lg font-semibold text-white">{getDisplayName(yesterdayCharacter)}</p>
{#if getDisplayEpithets(yesterdayCharacter).length > 0}
<p class="mt-1 text-sm text-slate-400">
{typeof yesterdayCharacter.epithets === 'string'
? JSON.parse(yesterdayCharacter.epithets).join(', ')
: (yesterdayCharacter.epithets as string[]).join(', ')}
{getDisplayEpithets(yesterdayCharacter).join(', ')}
</p>
{/if}
</div>
<a
href={"https://onepiece.fandom.com/fr/wiki/" + yesterdayCharacter.url}
target="_blank"
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"
>
{$t.game.home.openPage}
</a>
{#if isFrench}
<a
href="https://onepiece.fandom.com/fr/wiki/{getWikiUrl(yesterdayCharacter)}"
target="_blank"
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"
>
{$t.game.home.openPage}
</a>
{:else}
<a
href="https://onepiece.fandom.com/wiki/{getWikiUrl(yesterdayCharacter)}"
target="_blank"
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"
>
{$t.game.home.openPage}
</a>
{/if}
</div>
{:else}
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
+20 -4
View File
@@ -5,7 +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';
import { language, t } from '$lib/i18n';
export let data;
@@ -213,16 +213,32 @@
}
$: allCharacters = data.characters || [];
$: isFrench = $language === 'fr';
function getDisplayArcName(character: CharacterWithRelations, useFrench: boolean): string | null {
if (useFrench && typeof character.frArcName === 'string' && character.frArcName.length > 0) {
return character.frArcName;
}
return character.arcName;
}
// Extract unique arcs from all characters
$: {
const useFrench = isFrench;
const arcMap = new Map<string, ArcFilterOption>(
allCharacters
.filter(
(char: CharacterWithRelations): char is CharacterWithRelations & { arcId: string; arcName: string } =>
typeof char.arcId === 'string' && char.arcId.length > 0 && typeof char.arcName === 'string' && char.arcName.length > 0
(char: CharacterWithRelations): char is CharacterWithRelations & { arcId: string } =>
typeof char.arcId === 'string' &&
char.arcId.length > 0 &&
typeof getDisplayArcName(char, useFrench) === 'string' &&
getDisplayArcName(char, useFrench)!.length > 0
)
.map((char: CharacterWithRelations & { arcId: string; arcName: string }) => [char.arcId, { id: char.arcId, name: char.arcName }])
.map((char: CharacterWithRelations & { arcId: string }) => [
char.arcId,
{ id: char.arcId, name: getDisplayArcName(char, useFrench) as string }
])
);
availableArcs = [...arcMap.values()].sort((a, b) => a.name.localeCompare(b.name));