From 997b2f1781909d5dd5839df6a1046393567b48ca Mon Sep 17 00:00:00 2001 From: whidix Date: Sun, 15 Mar 2026 22:00:19 +0100 Subject: [PATCH] feat: add French localization support for character attributes and improve character display logic - 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. --- ...slide.sql => 0000_huge_doctor_octopus.sql} | 5 + drizzle/meta/0000_snapshot.json | 37 +- drizzle/meta/_journal.json | 4 +- scripts/daily-characters.json | 395 +++++++++++------- scripts/import-json.ts | 3 + scripts/set-daily-mode.ts | 35 +- .../components/CharacterSearchInput.svelte | 80 ++-- src/lib/components/GuessHistoryTable.svelte | 67 ++- src/lib/components/HintsPanel.svelte | 13 +- src/lib/components/WinPanel.svelte | 36 +- src/lib/components/YesterdayCharacter.svelte | 88 +++- src/lib/server/daily-character.ts | 11 +- src/lib/server/db/schema.ts | 5 + src/routes/(game)/+page.svelte | 88 +++- src/routes/(game)/infinite/+page.svelte | 24 +- 15 files changed, 655 insertions(+), 236 deletions(-) rename drizzle/{0000_keen_rockslide.sql => 0000_huge_doctor_octopus.sql} (98%) diff --git a/drizzle/0000_keen_rockslide.sql b/drizzle/0000_huge_doctor_octopus.sql similarity index 98% rename from drizzle/0000_keen_rockslide.sql rename to drizzle/0000_huge_doctor_octopus.sql index 7a856cd..674730a 100644 --- a/drizzle/0000_keen_rockslide.sql +++ b/drizzle/0000_huge_doctor_octopus.sql @@ -14,6 +14,7 @@ CREATE TABLE `character` ( `gender` text, `age` integer, `affiliations` text, + `fr_affiliations` text, `devil_fruit_id` text, `haki_observation` integer DEFAULT false, `haki_armament` integer DEFAULT false, @@ -52,6 +53,7 @@ CREATE TABLE `character_override` ( `gender` text, `age` integer, `affiliations` text, + `fr_affiliations` text, `devil_fruit_id` text, `haki_observation` integer, `haki_armament` integer, @@ -59,9 +61,11 @@ CREATE TABLE `character_override` ( `bounty` integer, `height` real, `origin` text, + `fr_origin` text, `first_appearance` integer, `picture_url` text, `epithets` text, + `fr_epithets` text, `status` text, `arc_id` text, `url` text, @@ -79,6 +83,7 @@ CREATE TABLE `character_scrape_validation` ( `gender` text, `age` integer, `affiliations` text, + `fr_affiliations` text, `devil_fruit_id` text, `haki_observation` integer DEFAULT false, `haki_armament` integer DEFAULT false, diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 293fecc..b14ed81 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "8ffd14bd-bf33-410f-9778-92bc1abc8938", + "id": "4b4f14a1-b37b-44f4-aed3-7289bd8cb6a0", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "arc": { @@ -101,6 +101,13 @@ "notNull": false, "autoincrement": false }, + "fr_affiliations": { + "name": "fr_affiliations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "devil_fruit_id": { "name": "devil_fruit_id", "type": "text", @@ -372,6 +379,13 @@ "notNull": false, "autoincrement": false }, + "fr_affiliations": { + "name": "fr_affiliations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "devil_fruit_id": { "name": "devil_fruit_id", "type": "text", @@ -421,6 +435,13 @@ "notNull": false, "autoincrement": false }, + "fr_origin": { + "name": "fr_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "first_appearance": { "name": "first_appearance", "type": "integer", @@ -442,6 +463,13 @@ "notNull": false, "autoincrement": false }, + "fr_epithets": { + "name": "fr_epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "status": { "name": "status", "type": "text", @@ -569,6 +597,13 @@ "notNull": false, "autoincrement": false }, + "fr_affiliations": { + "name": "fr_affiliations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "devil_fruit_id": { "name": "devil_fruit_id", "type": "text", diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 2b72475..a8a9caf 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1773447741334, - "tag": "0000_keen_rockslide", + "when": 1773602933375, + "tag": "0000_huge_doctor_octopus", "breakpoints": true } ] diff --git a/scripts/daily-characters.json b/scripts/daily-characters.json index c18df31..0ee7aa9 100644 --- a/scripts/daily-characters.json +++ b/scripts/daily-characters.json @@ -1,144 +1,255 @@ [ - "aladdin_aladdin", - "alvida_alvida", - "aramaki_aramaki", - "arlong_arlong", - "ashura_doji_ashura_doji", - "baby_5_baby_5", - "baggy_baggy", - "bartholomew_kuma_bartholomew_kuma", - "bartolomeo_bartolomeo", - "basil_hawkins_basil_hawkins", - "batman_batman", - "bellamy_bellamy", - "belo_betty_belo_betty", - "ben_beckman_ben_beckman", - "bentham_bentham", - "bepo_bepo", - "black_maria_black_maria", - "boa_hancock_boa_hancock", - "boa_marigold_boa_marigold", - "boa_sandersonia_boa_sandersonia", - "borsalino_borsalino", - "brogy_brogy", - "brook_brook", - "camie_camie", - "capone_bege_capone_bege", - "caribou_caribou", - "carrot_carrot", - "catarina_devon_catarina_devon", - "cavendish_cavendish", - "cesar_clown_cesar_clown", - "chinjao_chinjao", - "coby_coby", - "corazon_corazon", - "crocodile_crocodile", - "crocus_crocus", - "curly_dadan_curly_dadan", - "dalton_dalton", - "daz_bones_daz_bones", - "denjiro_denjiro", - "diamante_diamante", - "doc_q_doc_q", - "don_quichotte_doflamingo_don_quichotte_doflamingo", - "don_quichotte_rossinante_don_quichotte_rossinante", - "dorry_dorry", - "dracule_mihawk_dracule_mihawk", - "duval_duval", - "edward_newgate_edward_newgate", - "edward_weevil_edward_weevil", - "emporio_ivankov_emporio_ivankov", - "enel_enel", - "eustass_kid_eustass_kid", - "fisher_tiger_fisher_tiger", - "foxy_foxy", - "franky_franky", - "fujitora_fujitora", - "gan_forr_gan_forr", - "gecko_moria_gecko_moria", - "gin_gin", - "gol_d_roger_gol_d_roger", - "haguar_d_sauro_haguar_d_sauro", - "hajrudin_hajrudin", - "hannyabal_hannyabal", - "hatchan_hatchan", - "hina_hina", - "hody_jones_hody_jones", - "hyogoro_hyogoro", - "iceburg_iceburg", - "imu_imu", - "inazuma_inazuma", - "inuarashi_inuarashi", - "issho_issho", - "izo_izo", - "jabra_jabra", - "jack_jack", - "jesus_burgess_jesus_burgess", - "jewelry_bonney_jewelry_bonney", - "jinbei_jinbei", - "joy_boy_joy_boy", - "kaidou_kaidou", - "kaku_kaku", - "kalgara_kalgara", - "kalifa_kalifa", - "karasu_karasu", - "karoo_karoo", - "kawamatsu_kawamatsu", - "kaya_kaya", - "killer_killer", - "kinemon_kinemon", - "koala_koala", - "koby_koby", - "kong_kong", - "kozuki_hiyori_kozuki_hiyori", - "kozuki_momonosuke_kozuki_momonosuke", - "kozuki_oden_kozuki_oden", - "krieg_krieg", - "kureha_kureha", - "kuro_kuro", - "kurozumi_orochi_kurozumi_orochi", - "kuzan_kuzan", - "kyros_kyros", - "laboon_laboon", - "laffitte_laffitte", - "lao_g_lao_g", - "leo_leo", - "lindbergh_lindbergh", - "loki_loki", - "lucky_roux_lucky_roux", - "magellan_magellan", - "makino_makino", - "marco_marco", - "marshall_d_teach_marshall_d_teach", - "monkey_d_dragon_monkey_d_dragon", - "monkey_d_garp_monkey_d_garp", - "monkey_d_luffy_monkey_d_luffy", - "montblanc_norland_montblanc_norland", - "morgans_morgans", - "morley_morley", - "mr_3_mr_3", - "nami_nami", - "nefertari_cobra_nefertari_cobra", - "nefertari_vivi_nefertari_vivi", - "nekomamushi_nekomamushi", - "neptune_neptune", - "nico_robin_nico_robin", - "oars_oars", - "otohime_otohime", - "page_one_page_one", - "pandaman_pandaman", - "pekoms_pekoms", - "pell_pell", - "perona_perona", - "pica_pica", - "portgas_d_ace_portgas_d_ace", - "queen_queen", - "raizo_raizo", - "rebecca_rebecca", - "rob_lucci_rob_lucci", - "rocks_d_xebec_rocks_d_xebec", - "roronoa_zoro_roronoa_zoro", - "sabo_sabo", - "vegapunk_vegapunk", - "yamato_yamato" +"absalom_absalom", +"king_king", +"alvida_alvida", +"aramaki_aramaki", +"arlong_arlong", +"ashura_doji_ashura_doji", +"vegapunk/atlas_atlas", +"avalo_pizarro_avalo_pizarro", +"baby_5_baby_5", +"buggy_buggy", +"bartholomew_kuma_bartholomew_kuma", +"bartolomeo_bartolomeo", +"basil_hawkins_basil_hawkins", +"bell-mère_bell-mère", +"bellamy_bellamy", +"belo_betty_belo_betty", +"benn_beckman_ben_beckman", +"bentham_bentham", +"bepo_bepo", +"black_maria_black_maria", +"blueno_blueno", +"boa_hancock_boa_hancock", +"boa_marigold_boa_marigold", +"boa_sandersonia_boa_sandersonia", +"borsalino_borsalino", +"brogy_brogy", +"brook_brook", +"buckingham_stussy_buckingham_stussy", +"buffalo_buffalo", +"camie_camie", +"capone_bege_capone_bege", +"carmel_carmel", +"caribou_caribou", +"carrot_carrot", +"catarina_devon_catarina_devon", +"cavendish_cavendish", +"caesar_clown_caesar_clown", +"charlotte_brûlée_charlotte_brûlée", +"charlotte_cracker_charlotte_cracker", +"charlotte_katakuri_charlotte_katakuri", +"charlotte_linlin_charlotte_linlin", +"charlotte_mont-d'or_charlotte_mont-d'or", +"charlotte_oven_charlotte_oven", +"charlotte_perospero_charlotte_perospero", +"charlotte_pudding_charlotte_pudding", +"charlotte_smoothie_charlotte_smoothie", +"chinjao_chinjao", +"clou_d_clover_clou_d_clover", +"crocodile_crocodile", +"crocus_crocus", +"curly_dadan_curly_dadan", +"dalton_dalton", +"daz_bonez_daz_bonez", +"denjiro_denjiro", +"diamante_diamante", +"doc_q_doc_q", +"donquixote_doflamingo_donquixote_doflamingo", +"donquixote_rosinante_donquixote_rosinante", +"dorry_dorry", +"dracule_mihawk_dracule_mihawk", +"vegapunk/edison_edison", +"edward_newgate_edward_newgate", +"edward_weevil_edward_weevil", +"emporio_ivankov_emporio_ivankov", +"enel_enel", +"eustass_kid_eustass_kid", +"fisher_tiger_fisher_tiger", +"foxy_foxy", +"franky_franky", +"fukaboshi_fukaboshi", +"fukurou_fukurou", +"galdino_galdino", +"gan_fall_gan_fall", +"gecko_moria_gecko_moria", +"gem_gem", +"genzo_genzo", +"gin_gin", +"ginny_ginny", +"gol_d_roger_gol_d_roger", +"guernika_guernika", +"hack_hack", +"jaguar_d_saul_jaguar_d_saul", +"hajrudin_hajrudin", +"hannyabal_hannyabal", +"harald_harald", +"haredas_haredas", +"heracles_heracles", +"helmeppo_helmeppo", +"hibari_hibari", +"hiriluk_hiriluk", +"hina_hina", +"hody_jones_hody_jones", +"hogback_hogback", +"hyougoro_hyougoro", +"iceburg_iceburg", +"igaram_igaram", +"imu_imu", +"inazuma_inazuma", +"inuarashi_inuarashi", +"issho_issho", +"izou_izou", +"jabra_jabra", +"jack_jack", +"jango_jango", +"jesus_burgess_jesus_burgess", +"jewelry_bonney_jewelry_bonney", +"jinbe_jinbe", +"giolla_giolla", +"joy_boy_joy_boy", +"jozu_jozu", +"kaidou_kaidou", +"kaku_kaku", +"kalgara_kalgara", +"kalifa_kalifa", +"karasu_karasu", +"karoo_karoo", +"kawamatsu_kawamatsu", +"kaya_kaya", +"kelly_funk_kelly_funk", +"kikunojo_kikunojo", +"killer_killer", +"kin'emon_kin'emon", +"koala_koala", +"koby_koby", +"kokoro_kokoro", +"kouzuki_hiyori_kouzuki_hiyori", +"kouzuki_momonosuke_kouzuki_momonosuke", +"kouzuki_oden_kouzuki_oden", +"kouzuki_sukiyaki_kouzuki_sukiyaki", +"kouzuki_toki_kouzuki_toki", +"krieg_krieg", +"kumadori_kumadori", +"kureha_kureha", +"kuro_kuro", +"kurozumi_kanjuro_kurozumi_kanjuro", +"kurozumi_orochi_kurozumi_orochi", +"kurozumi_tama_kurozumi_tama", +"kuzan_kuzan", +"kyros_kyros", +"laboon_laboon", +"laffitte_laffitte", +"lao_g_lao_g", +"leo_leo", +"vegapunk/lilith_lilith", +"lindbergh_lindbergh", +"loki_loki", +"lucky_roux_lucky_roux", +"magellan_magellan", +"makino_makino", +"mansherry_mansherry", +"marco_marco", +"marshall_d_teach_marshall_d_teach", +"merry_merry", +"momoo_momoo", +"mocha_mocha", +"monet_monet", +"monkey_d_dragon_monkey_d_dragon", +"monkey_d_garp_monkey_d_garp", +"monkey_d_luffy_monkey_d_luffy", +"mont_blanc_cricket_mont_blanc_cricket", +"mont_blanc_noland_mont_blanc_noland", +"morgans_morgans", +"morgan_morgan", +"morley_morley", +"nami_nami", +"nefertari_cobra_nefertari_cobra", +"nefertari_vivi_nefertari_vivi", +"nekomamushi_nekomamushi", +"neptune_neptune", +"nico_olvia_nico_olvia", +"nico_robin_nico_robin", +"nojiko_nojiko", +"hatchan_hatchan", +"otohime_otohime", +"oars_oars", +"page_one_page_one", +"pandaman_pandaman", +"paulie_paulie", +"pedro_pedro", +"pekoms_pekoms", +"pell_pell", +"perona_perona", +"pica_pica", +"portgas_d_ace_portgas_d_ace", +"vegapunk/pythagoras_pythagoras", +"queen_queen", +"raizo_raizo", +"rebecca_rebecca", +"riku_doldo_iii_riku_doldo_iii", +"rob_lucci_rob_lucci", +"rocks_d_xebec_rocks_d_xebec", +"roronoa_zoro_roronoa_zoro", +"s-bear_s-bear", +"s-hawk_s-hawk", +"s-snake_s-snake", +"sabo_sabo", +"sadi_sadi", +"donquixote_mjosgard_donquixote_mjosgard", +"rimoshifu_killingham_rimoshifu_killingham", +"manmayer_gunko_manmayer_gunko", +"shepherd_sommers_shepherd_sommers", +"sakazuki_sakazuki", +"sanjuan_wolf_sanjuan_wolf", +"sasaki_sasaki", +"scratchmen_apoo_scratchmen_apoo", +"sengoku_sengoku", +"senor_pink_senor_pink", +"sentomaru_sentomaru", +"vegapunk/shaka_shaka", +"shakuyaku_shakuyaku", +"shanks_shanks", +"shiryu_shiryu", +"shimotsuki_kuina_shimotsuki_kuina", +"shimotsuki_yasuie_shimotsuki_yasuie", +"shinobu_shinobu", +"shirahoshi_shirahoshi", +"silvers_rayleigh_silvers_rayleigh", +"smoker_smoker", +"spandam_spandam", +"speed_speed", +"stussy_stussy", +"sugar_sugar", +"tamago_tamago", +"tashigi_tashigi", +"toko_toko", +"tom_tom", +"tony_tony_chopper_tony_tony_chopper", +"trafalgar_d_water_law_trafalgar_d_water_law", +"trebol_trebol", +"tsuru_tsuru", +"ulti_ulti", +"urouge_urouge", +"usopp_usopp", +"uta_uta", +"van_augur_van_augur", +"vander_decken_ix_vander_decken_ix", +"vegapunk_vegapunk", +"vergo_vergo", +"vinsmoke_ichiji_vinsmoke_ichiji", +"vinsmoke_judge_vinsmoke_judge", +"vinsmoke_niji_vinsmoke_niji", +"vinsmoke_reiju_vinsmoke_reiju", +"sanji_sanji", +"vinsmoke_yonji_vinsmoke_yonji", +"viola_viola", +"wadatsumi_wadatsumi", +"wapol_wapol", +"wyper_wyper", +"x_drake_x_drake", +"yamato_yamato", +"yasopp_yasopp", +"vegapunk/york_york", +"zeff_zeff" ] \ No newline at end of file diff --git a/scripts/import-json.ts b/scripts/import-json.ts index 8385e94..45082ea 100644 --- a/scripts/import-json.ts +++ b/scripts/import-json.ts @@ -9,6 +9,7 @@ type Status = 'Alive' | 'Dead' | 'Unknown'; type ArcRecord = { id: string; name: string; + frName?: string | null; startChapter: number; endChapter?: number | null; url?: string | null; @@ -170,6 +171,7 @@ async function importFromJson(): Promise { .values({ id: item.id, name: item.name, + frName: toNullable(item.frName), startChapter: item.startChapter, endChapter: toNullable(item.endChapter), url: toNullable(item.url) @@ -178,6 +180,7 @@ async function importFromJson(): Promise { target: arc.id, set: { name: item.name, + frName: toNullable(item.frName), startChapter: item.startChapter, endChapter: toNullable(item.endChapter), url: toNullable(item.url) diff --git a/scripts/set-daily-mode.ts b/scripts/set-daily-mode.ts index 496c4c4..48d0292 100644 --- a/scripts/set-daily-mode.ts +++ b/scripts/set-daily-mode.ts @@ -1,6 +1,6 @@ import { createClient } from '@libsql/client'; import { drizzle } from 'drizzle-orm/libsql'; -import { eq } from 'drizzle-orm'; +import { eq, inArray } from 'drizzle-orm'; import fs from 'fs'; import { character, characterHistory } from '../src/lib/server/db/schema'; @@ -24,13 +24,14 @@ function getErrorMessage(error: unknown): string { async function setDailyCharacters(): Promise { try { - const dailyCharacterIds = readJsonFile('./scripts/daily-characters.json'); + const dailyCharacterIdsRaw = readJsonFile('./scripts/daily-characters.json'); - if (!dailyCharacterIds || dailyCharacterIds.length === 0) { - console.error('❌ No daily characters found in daily-characters.json'); - process.exit(1); + if (!dailyCharacterIdsRaw || dailyCharacterIdsRaw.length === 0) { + throw new Error('No daily characters found in daily-characters.json'); } + const dailyCharacterIds = dailyCharacterIdsRaw; + console.log(`\n=== Setting Daily Mode Characters ===\n`); console.log(`Found ${dailyCharacterIds.length} characters to set as daily\n`); @@ -45,16 +46,36 @@ async function setDailyCharacters(): Promise { let successCount = 0; let errorCount = 0; + const existingCharacters = await db + .select({ id: character.id }) + .from(character) + .where(inArray(character.id, dailyCharacterIds)); + + const existingIdSet = new Set(existingCharacters.map((c) => c.id)); + const missingIds = dailyCharacterIds.filter((id) => !existingIdSet.has(id)); + + if (missingIds.length > 0) { + errorCount += missingIds.length; + console.error(`✗ ${missingIds.length} character ID(s) were not found in database:`); + for (const missingId of missingIds) { + console.error(` - ${missingId}`); + } + console.error(''); + } + for (let i = 0; i < dailyCharacterIds.length; i++) { const charId = dailyCharacterIds[i]; + if (!existingIdSet.has(charId)) { + continue; + } + try { - const result = await db + await db .update(character) .set({ isInDailyMode: true }) .where(eq(character.id, charId)); successCount++; - process.stdout.write(`\rUpdated: ${successCount}/${dailyCharacterIds.length}`); } catch (error) { errorCount++; console.error(`\n✗ Error updating character ${i + 1}:`); diff --git a/src/lib/components/CharacterSearchInput.svelte b/src/lib/components/CharacterSearchInput.svelte index 76aa791..3cebd5e 100644 --- a/src/lib/components/CharacterSearchInput.svelte +++ b/src/lib/components/CharacterSearchInput.svelte @@ -1,7 +1,7 @@
{#if character.pictureUrl} {character.name} @@ -214,7 +261,7 @@ > {character.name}{getDisplayName(character)} {/if} @@ -407,13 +454,12 @@ {#if columnVisibility.origin !== false}

- {character.origin || $t.game.components.guessHistory.unknown} + {getDisplayOrigin(character) || $t.game.components.guessHistory.unknown}

{/if} @@ -421,12 +467,11 @@ {#if columnVisibility.arc !== false}
- {#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}
- {character.arcName || $t.game.components.guessHistory.unknown} + {getDisplayArcName(character) || $t.game.components.guessHistory.unknown}

{/if} diff --git a/src/lib/components/HintsPanel.svelte b/src/lib/components/HintsPanel.svelte index 12d84e1..1c9c79c 100644 --- a/src/lib/components/HintsPanel.svelte +++ b/src/lib/components/HintsPanel.svelte @@ -1,6 +1,6 @@ @@ -47,7 +56,7 @@ >

{$t.game.components.hints.origin}

{#if showHintOrigin} -

{dailyCharacter.origin || $t.game.components.hints.unknown}

+

{getDisplayOrigin(dailyCharacter) || $t.game.components.hints.unknown}

{:else if Math.max(0, 5 - selectedCharacters.length) > 0}

{Math.max(0, 5 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}

{:else} diff --git a/src/lib/components/WinPanel.svelte b/src/lib/components/WinPanel.svelte index 2ca4510..bfcfa73 100644 --- a/src/lib/components/WinPanel.svelte +++ b/src/lib/components/WinPanel.svelte @@ -1,11 +1,33 @@
@@ -11,7 +60,7 @@ {#if yesterdayCharacter.pictureUrl} {yesterdayCharacter.name} {:else} @@ -21,23 +70,32 @@ {/if}

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

-

{yesterdayCharacter.name}

- {#if yesterdayCharacter.epithets} +

{getDisplayName(yesterdayCharacter)}

+ {#if getDisplayEpithets(yesterdayCharacter).length > 0}

- {typeof yesterdayCharacter.epithets === 'string' - ? JSON.parse(yesterdayCharacter.epithets).join(', ') - : (yesterdayCharacter.epithets as string[]).join(', ')} + {getDisplayEpithets(yesterdayCharacter).join(', ')}

{/if}
- - {$t.game.components.yesterdayCharacter.openPage} - + {#if isFrench} + + {$t.game.components.yesterdayCharacter.openPage} + + {:else} + + {$t.game.components.yesterdayCharacter.openPage} + + {/if}
{:else}
diff --git a/src/lib/server/daily-character.ts b/src/lib/server/daily-character.ts index 55b93be..6001ceb 100644 --- a/src/lib/server/daily-character.ts +++ b/src/lib/server/daily-character.ts @@ -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) { diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 13d0b03..1abce0c 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -44,6 +44,7 @@ export const character = sqliteTable('character', { gender: text('gender'), age: integer('age'), affiliations: text('affiliations', { mode: 'json' }).$type(), + frAffiliations: text('fr_affiliations', { mode: 'json' }).$type(), 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(), + frAffiliations: text('fr_affiliations', { mode: 'json' }).$type(), 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(), + frEpithets: text('fr_epithets', { mode: 'json' }).$type(), status: text('status').$type(), 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(), + frAffiliations: text('fr_affiliations', { mode: 'json' }).$type(), 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), diff --git a/src/routes/(game)/+page.svelte b/src/routes/(game)/+page.svelte index d9b01d0..bfad3c7 100644 --- a/src/routes/(game)/+page.svelte +++ b/src/routes/(game)/+page.svelte @@ -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 || ''; + } + @@ -57,7 +106,7 @@ {#if yesterdayCharacter.pictureUrl} {yesterdayCharacter.name} {:else} @@ -67,23 +116,32 @@ {/if}

{$t.game.home.yesterdayCharacter}

-

{yesterdayCharacter.name}

- {#if yesterdayCharacter.epithets} +

{getDisplayName(yesterdayCharacter)}

+ {#if getDisplayEpithets(yesterdayCharacter).length > 0}

- {typeof yesterdayCharacter.epithets === 'string' - ? JSON.parse(yesterdayCharacter.epithets).join(', ') - : (yesterdayCharacter.epithets as string[]).join(', ')} + {getDisplayEpithets(yesterdayCharacter).join(', ')}

{/if}
- - {$t.game.home.openPage} - + {#if isFrench} + + {$t.game.home.openPage} + + {:else} + + {$t.game.home.openPage} + + {/if}
{:else}
diff --git a/src/routes/(game)/infinite/+page.svelte b/src/routes/(game)/infinite/+page.svelte index 0141cda..43204e0 100644 --- a/src/routes/(game)/infinite/+page.svelte +++ b/src/routes/(game)/infinite/+page.svelte @@ -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( 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));