11'use client'
22
3+ import { Command } from 'lucide-react'
34import Image from 'next/image'
45import { useParams } from 'next/navigation'
56import { useTranslations } from 'next-intl'
67import { memo , useCallback , useEffect , useMemo , useState } from 'react'
8+ import { RankingMegaMenu } from '@/components/controls/RankingMegaMenu'
9+ import SearchDialog from '@/components/controls/SearchDialog'
10+ import { StackMegaMenu } from '@/components/controls/StackMegaMenu'
711import { Link } from '@/i18n/navigation'
8- import SearchDialog from './controls/SearchDialog'
9- import { RankingMegaMenu } from './RankingMegaMenu'
10- import { StackMegaMenu } from './StackMegaMenu'
1112
1213// Menu item configuration type
1314interface MenuItem {
1415 href : string
1516 translationKey : string
16- namespace ?: 'header' | 'community '
17+ namespace ?: 'header' | 'common '
1718 isExternal ?: boolean
1819 hasMegaMenu ?: boolean
1920 megaMenuType ?: 'aiCodingStack' | 'ranking'
@@ -31,8 +32,7 @@ function Header() {
3132 const [ isMenuOpen , setIsMenuOpen ] = useState ( false )
3233 const [ activeMegaMenu , setActiveMegaMenu ] = useState < 'aiCodingStack' | 'ranking' | null > ( null )
3334 const [ isSearchDialogOpen , setIsSearchDialogOpen ] = useState ( false )
34- const tHeader = useTranslations ( 'header' )
35- const tCommunity = useTranslations ( 'community' )
35+ const t = useTranslations ( 'components.header' )
3636
3737 // Menu items configuration - memoized to avoid recreation on each render
3838 const menuItems = useMemo < MenuItem [ ] > (
@@ -89,16 +89,6 @@ function Header() {
8989 return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown )
9090 } , [ ] )
9191
92- // Get translation text helper - memoized
93- const getMenuText = useCallback (
94- ( item : MenuItem ) => {
95- const t = item . namespace === 'community' ? tCommunity : tHeader
96- const text = t ( item . translationKey as never )
97- return item . isExternal ? `→ ${ text } ` : text
98- } ,
99- [ tHeader , tCommunity ]
100- )
101-
10292 // Render desktop menu item
10393 const renderDesktopMenuItem = useCallback (
10494 ( item : MenuItem ) => {
@@ -117,7 +107,7 @@ function Header() {
117107 aria-expanded = { isActive }
118108 aria-haspopup = "true"
119109 >
120- { getMenuText ( item ) }
110+ { t ( item . translationKey as never ) }
121111 </ Link >
122112 { item . megaMenuType === 'aiCodingStack' && (
123113 < StackMegaMenu isOpen = { isActive } onClose = { handleMegaMenuClose } />
@@ -133,17 +123,17 @@ function Header() {
133123 < li key = { item . href } >
134124 { item . isExternal ? (
135125 < a href = { item . href } target = "_blank" rel = "noopener" className = { DESKTOP_LINK_CLASSES } >
136- { getMenuText ( item ) }
126+ → { t ( item . translationKey as never ) }
137127 </ a >
138128 ) : (
139129 < Link href = { item . href } className = { DESKTOP_LINK_CLASSES } >
140- { getMenuText ( item ) }
130+ { t ( item . translationKey as never ) }
141131 </ Link >
142132 ) }
143133 </ li >
144134 )
145135 } ,
146- [ activeMegaMenu , handleMegaMenuOpen , handleMegaMenuClose , getMenuText ]
136+ [ activeMegaMenu , handleMegaMenuOpen , handleMegaMenuClose , t ]
147137 )
148138
149139 // Render mobile menu item
@@ -152,22 +142,22 @@ function Header() {
152142 < li key = { item . href } >
153143 { item . isExternal ? (
154144 < a href = { item . href } target = "_blank" rel = "noopener" className = { MOBILE_LINK_CLASSES } >
155- { getMenuText ( item ) }
145+ → { t ( item . translationKey as never ) }
156146 </ a >
157147 ) : (
158148 < Link href = { item . href } className = { MOBILE_LINK_CLASSES } onClick = { handleMenuClose } >
159- { getMenuText ( item ) }
149+ { t ( item . translationKey as never ) }
160150 </ Link >
161151 ) }
162152 </ li >
163153 ) ,
164- [ handleMenuClose , getMenuText ]
154+ [ handleMenuClose , t ]
165155 )
166156
167157 // Memoized menu button label
168158 const menuButtonLabel = useMemo (
169- ( ) => ( isMenuOpen ? tHeader ( 'closeMenu' ) : tHeader ( 'openMenu' ) ) ,
170- [ isMenuOpen , tHeader ]
159+ ( ) => ( isMenuOpen ? t ( 'closeMenu' ) : t ( 'openMenu' ) ) ,
160+ [ isMenuOpen , t ]
171161 )
172162
173163 return (
@@ -216,8 +206,11 @@ function Header() {
216206 d = "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
217207 />
218208 </ svg >
219- < span className = "flex-1 text-left" > { tHeader ( 'searchPlaceholder' ) } </ span >
220- < kbd className = "px-1.5 py-0.5 text-xs border border-[var(--color-border)]" > ⌘K</ kbd >
209+ < span className = "flex-1 text-left" > { t ( 'searchPlaceholder' ) } </ span >
210+ < kbd className = "flex items-center gap-1 px-1.5 py-0.5 text-xs border border-[var(--color-border)]" >
211+ < Command className = "w-3 h-3" />
212+ < span > K</ span >
213+ </ kbd >
221214 </ button >
222215 </ div >
223216
@@ -228,7 +221,7 @@ function Header() {
228221 type = "button"
229222 onClick = { ( ) => setIsSearchDialogOpen ( true ) }
230223 className = "p-[var(--spacing-xs)] hover:bg-[var(--color-hover)] transition-colors"
231- aria-label = { tHeader ( 'search' ) }
224+ aria-label = { t ( 'search' ) }
232225 >
233226 < svg
234227 className = "w-5 h-5"
@@ -251,7 +244,7 @@ function Header() {
251244 type = "button"
252245 onClick = { handleMenuToggle }
253246 className = "p-[var(--spacing-xs)] hover:bg-[var(--color-hover)] transition-colors"
254- aria-label = { tHeader ( 'toggleMenu' ) }
247+ aria-label = { t ( 'toggleMenu' ) }
255248 >
256249 < svg
257250 className = "w-6 h-6"
0 commit comments