From c58620e3390145acefc47ec7c3b441f97b9fedd9 Mon Sep 17 00:00:00 2001 From: jubnl Date: Fri, 1 May 2026 01:37:55 +0200 Subject: [PATCH] fix: translate mobile bottom-nav tab labels (issue #931) Replaced hardcoded English labels in BottomNav with t() lookups using the same translation keys as the desktop navbar (nav.myTrips, admin.addons.catalog.*.name). --- .../src/components/Layout/BottomNav.test.tsx | 41 ++++++++++++++++++- client/src/components/Layout/BottomNav.tsx | 24 +++++------ 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/client/src/components/Layout/BottomNav.test.tsx b/client/src/components/Layout/BottomNav.test.tsx index d603a6a9..9244efc9 100644 --- a/client/src/components/Layout/BottomNav.test.tsx +++ b/client/src/components/Layout/BottomNav.test.tsx @@ -19,8 +19,10 @@ vi.mock('react-router-dom', async () => { import { render, screen, fireEvent } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { useAuthStore } from '../../store/authStore'; +import { useSettingsStore } from '../../store/settingsStore'; +import { useAddonStore } from '../../store/addonStore'; import { resetAllStores, seedStore } from '../../../tests/helpers/store'; -import { buildUser } from '../../../tests/helpers/factories'; +import { buildUser, buildSettings } from '../../../tests/helpers/factories'; import BottomNav from './BottomNav'; const currentUser = buildUser({ id: 1, username: 'testuser', email: 'test@example.com' }); @@ -39,7 +41,7 @@ describe('BottomNav', () => { it('FE-COMP-BOTTOMNAV-002: shows Trips nav link', () => { render(); - expect(screen.getByText('Trips')).toBeInTheDocument(); + expect(screen.getByText('My Trips')).toBeInTheDocument(); }); it('FE-COMP-BOTTOMNAV-003: shows Profile button', () => { @@ -99,4 +101,39 @@ describe('BottomNav', () => { // Sheet should be closed — username no longer visible (only the nav Profile text remains) expect(screen.queryByText('testuser')).not.toBeInTheDocument(); }); + + it('FE-COMP-BOTTOMNAV-010: Trips label translates when language is fr', () => { + seedStore(useSettingsStore, { settings: buildSettings({ language: 'fr' }) }); + render(); + expect(screen.getByText('Mes voyages')).toBeInTheDocument(); + }); + + it('FE-COMP-BOTTOMNAV-011: Profile label translates when language is fr', () => { + seedStore(useSettingsStore, { settings: buildSettings({ language: 'fr' }) }); + render(); + expect(screen.getByText('Profil')).toBeInTheDocument(); + }); + + it('FE-COMP-BOTTOMNAV-012: addon labels translate when language is fr', () => { + seedStore(useSettingsStore, { settings: buildSettings({ language: 'fr' }) }); + seedStore(useAddonStore, { + addons: [ + { id: 'vacay', name: 'Vacay', type: 'global', icon: 'calendar', enabled: true }, + { id: 'atlas', name: 'Atlas', type: 'global', icon: 'globe', enabled: true }, + { id: 'journey', name: 'Journey', type: 'global', icon: 'compass', enabled: true }, + ], + }); + render(); + expect(screen.getByText('Vacances')).toBeInTheDocument(); + expect(screen.getByText('Atlas')).toBeInTheDocument(); + expect(screen.getByText('Journal de voyage')).toBeInTheDocument(); + }); + + it('FE-COMP-BOTTOMNAV-013: unknown addon id is not rendered', () => { + seedStore(useAddonStore, { + addons: [{ id: 'foo', name: 'Foo Addon', type: 'global', icon: 'star', enabled: true }], + }); + render(); + expect(screen.queryByText('Foo Addon')).not.toBeInTheDocument(); + }); }); diff --git a/client/src/components/Layout/BottomNav.tsx b/client/src/components/Layout/BottomNav.tsx index 16aa9ffd..59fd9854 100644 --- a/client/src/components/Layout/BottomNav.tsx +++ b/client/src/components/Layout/BottomNav.tsx @@ -7,14 +7,10 @@ import { useTranslation } from '../../i18n' import { Plane, CalendarDays, Globe, Compass, User, Settings, Shield, LogOut, X } from 'lucide-react' import type { LucideIcon } from 'lucide-react' -const BASE_ITEMS: { to: string; label: string; icon: LucideIcon; addonId?: string }[] = [ - { to: '/trips', label: 'Trips', icon: Plane }, -] - -const ADDON_NAV: Record = { - vacay: { to: '/vacay', label: 'Vacay', icon: CalendarDays }, - atlas: { to: '/atlas', label: 'Atlas', icon: Globe }, - journey: { to: '/journey', label: 'Journey', icon: Compass }, +const ADDON_NAV: Record = { + vacay: { icon: CalendarDays, labelKey: 'admin.addons.catalog.vacay.name' }, + atlas: { icon: Globe, labelKey: 'admin.addons.catalog.atlas.name' }, + journey: { icon: Compass, labelKey: 'admin.addons.catalog.journey.name' }, } export default function BottomNav() { @@ -25,11 +21,13 @@ export default function BottomNav() { const globalAddons = addons.filter(a => a.type === 'global' && a.enabled) const [showProfile, setShowProfile] = useState(false) - const items = [...BASE_ITEMS] - for (const addon of globalAddons) { - const nav = ADDON_NAV[addon.id] - if (nav) items.push(nav) - } + const items: { to: string; label: string; icon: LucideIcon }[] = [ + { to: '/trips', label: t('nav.myTrips'), icon: Plane }, + ...globalAddons.flatMap(addon => { + const nav = ADDON_NAV[addon.id] + return nav ? [{ to: `/${addon.id}`, label: t(nav.labelKey), icon: nav.icon }] : [] + }), + ] return ( <>