From bb2b421053aaa7649a40b2a3f15abc41582efdbb Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Wed, 24 Jun 2026 19:20:25 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20localize=20Pok=C3=A9dex=20to=20French?= =?UTF-8?q?=20(UI,=20names,=20types,=20abilities)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/locale-fr.ts: baked FR names for 151 Pokémon, 18 types, 114 abilities (generated once from PokeAPI — no extra runtime locale requests) - pokeapi.ts: map Pokémon + ability names to FR in the data layer - accent-insensitive search (evoli matches Évoli) - all UI strings, stat labels (PV/ATT/DÉF…) and html lang=fr --- index.html | 2 +- src/App.vue | 41 +++-- src/components/PokemonCard.vue | 4 +- src/components/PokemonModal.vue | 35 ++-- src/lib/locale-fr.ts | 295 ++++++++++++++++++++++++++++++++ src/lib/pokeapi.ts | 10 +- 6 files changed, 349 insertions(+), 38 deletions(-) create mode 100644 src/lib/locale-fr.ts diff --git a/index.html b/index.html index 42c27c3..671afc2 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/src/App.vue b/src/App.vue index 9c00208..cee8f30 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,6 +8,7 @@ import { type PokemonListItem, TYPE_COLORS, } from "@/lib/pokeapi" +import { TYPE_FR } from "@/lib/locale-fr" import { useFavorites } from "@/composables/useFavorites" import PokemonCard from "@/components/PokemonCard.vue" import PokemonModal from "@/components/PokemonModal.vue" @@ -34,6 +35,14 @@ const detailError = ref(null) const detailCache = new Map() const typeCache = new Map>() +// Lower-case and strip accents so "evoli" matches "Évoli". +function norm(s: string): string { + return s + .normalize("NFD") + .replace(/\p{Diacritic}/gu, "") + .toLowerCase() +} + const filtered = computed(() => { let items = list.value if (showFavoritesOnly.value) items = items.filter((p) => isFavorite(p.id)) @@ -41,10 +50,12 @@ const filtered = computed(() => { const ids = typeIds.value items = items.filter((p) => ids.has(p.id)) } - const q = search.value.trim().toLowerCase().replace(/^#/, "") - if (q) { + const raw = search.value.trim().toLowerCase().replace(/^#/, "") + if (raw) { + const q = norm(raw) items = items.filter( - (p) => p.name.includes(q) || String(p.id) === q || String(p.id).padStart(3, "0") === q, + (p) => + norm(p.name).includes(q) || String(p.id) === raw || String(p.id).padStart(3, "0") === raw, ) } return items @@ -58,7 +69,7 @@ async function load(): Promise { try { list.value = await fetchPokedex() } catch (e) { - loadError.value = e instanceof Error ? e.message : "Failed to load Pokédex" + loadError.value = e instanceof Error ? e.message : "Échec du chargement du Pokédex" } finally { loading.value = false } @@ -105,7 +116,7 @@ async function openDetail(id: number): Promise { detailCache.set(id, d) detail.value = d } catch (e) { - detailError.value = e instanceof Error ? e.message : "Failed to load this Pokémon" + detailError.value = e instanceof Error ? e.message : "Échec du chargement de ce Pokémon" } finally { detailLoading.value = false } @@ -154,18 +165,18 @@ onUnmounted(() => window.removeEventListener("keydown", onKeydown)) -

Filtering…

+

Filtrage…

- Booting Pokédex… + Démarrage du Pokédex…

{{ loadError }}

@@ -194,7 +205,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKeydown)) v-else-if="filtered.length === 0" class="text-center py-20 text-2xl text-base-content/60" > - No Pokémon found. + Aucun Pokémon trouvé.
diff --git a/src/components/PokemonCard.vue b/src/components/PokemonCard.vue index 71e8474..ad11267 100644 --- a/src/components/PokemonCard.vue +++ b/src/components/PokemonCard.vue @@ -23,7 +23,7 @@ const dexNo = `#${String(props.id).padStart(3, "0")}`
diff --git a/src/components/PokemonModal.vue b/src/components/PokemonModal.vue index e0e650d..69ae834 100644 --- a/src/components/PokemonModal.vue +++ b/src/components/PokemonModal.vue @@ -1,5 +1,6 @@