Skip to content

Commit 1c8bd27

Browse files
ericyangpanclaude
andcommitted
refactor(i18n): restructure component translation namespaces
- Migrate from per-component namespaces to unified component namespaces - Change from 'components.controls.searchDialog' to 'components.controls' - Change from 'components.common.header' to 'components.common' - Apply consistent namespace pattern across all component types: - components.common - root-level components (Header, Footer, etc.) - components.controls - all controls/* components - components.navigation - all navigation/* components - components.product - all product/* components - components.sidebar - all sidebar/* components - Update all component files to use new namespace structure This simplifies translation key references and improves maintainability. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 55c867c commit 1c8bd27

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+151
-178
lines changed

src/components/Footer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function FooterLinkList({ title, links }: FooterLinkListProps) {
3535
}
3636

3737
export default function Footer() {
38-
const tComponent = useTranslations('components.common.footer')
38+
const tComponent = useTranslations('components.common')
3939
const tShared = useTranslations('shared')
4040

4141
// Define link arrays (static hrefs, only labels depend on translations)
@@ -81,8 +81,8 @@ export default function Footer() {
8181
{tShared('terms.aiCodingStack')}
8282
</span>
8383
<p className="text-sm pb-[var(--spacing-sm)] leading-[1.8] text-[var(--color-text-secondary)] font-light">
84-
{tComponent('tagline')}
85-
<span className="block mt-[var(--spacing-sm)]">{tComponent('openSource')}</span>
84+
{tComponent('footer.tagline')}
85+
<span className="block mt-[var(--spacing-sm)]">{tComponent('footer.openSource')}</span>
8686
</p>
8787
<div className="flex gap-[var(--spacing-xs)]">
8888
<ThemeSwitcher />
@@ -96,7 +96,7 @@ export default function Footer() {
9696
</div>
9797

9898
<div className="py-[var(--spacing-md)] text-center text-xs text-[var(--color-text-muted)]">
99-
{tComponent('copyright')}
99+
{tComponent('footer.copyright')}
100100
</div>
101101
</footer>
102102
)

src/components/Header.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function Header() {
3232
const [isMenuOpen, setIsMenuOpen] = useState(false)
3333
const [activeMegaMenu, setActiveMegaMenu] = useState<'aiCodingStack' | 'ranking' | null>(null)
3434
const [isSearchDialogOpen, setIsSearchDialogOpen] = useState(false)
35-
const tComponent = useTranslations('components.common.header')
35+
const tComponent = useTranslations('components.common')
3636
const tShared = useTranslations('shared')
3737

3838
// Menu items configuration - memoized to avoid recreation on each render
@@ -46,10 +46,10 @@ function Header() {
4646
hasMegaMenu: true,
4747
megaMenuType: 'aiCodingStack',
4848
},
49-
{ href: '/ai-coding-landscape', translationKey: 'landscape', namespace: 'header' },
49+
{ href: '/ai-coding-landscape', translationKey: 'header.landscape', namespace: 'header' },
5050
{
5151
href: '#',
52-
translationKey: 'ranking',
52+
translationKey: 'header.ranking',
5353
namespace: 'header',
5454
hasMegaMenu: true,
5555
megaMenuType: 'ranking',
@@ -172,7 +172,7 @@ function Header() {
172172

173173
// Memoized menu button label
174174
const menuButtonLabel = useMemo(
175-
() => (isMenuOpen ? tComponent('closeMenu') : tComponent('openMenu')),
175+
() => (isMenuOpen ? tComponent('header.closeMenu') : tComponent('header.openMenu')),
176176
[isMenuOpen, tComponent]
177177
)
178178

@@ -222,7 +222,7 @@ function Header() {
222222
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
223223
/>
224224
</svg>
225-
<span className="flex-1 text-left">{tComponent('searchPlaceholder')}</span>
225+
<span className="flex-1 text-left">{tShared('actions.searchPlaceholder')}</span>
226226
<kbd className="flex items-center gap-1 px-1.5 py-0.5 text-xs border border-[var(--color-border)]">
227227
<Command className="w-3 h-3" />
228228
<span>K</span>
@@ -260,7 +260,7 @@ function Header() {
260260
type="button"
261261
onClick={handleMenuToggle}
262262
className="p-[var(--spacing-xs)] hover:bg-[var(--color-hover)] transition-colors"
263-
aria-label={tComponent('toggleMenu')}
263+
aria-label={tComponent('header.toggleMenu')}
264264
>
265265
<svg
266266
className="w-6 h-6"

src/components/controls/CopyButton.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useTranslations } from 'next-intl'
44
import { useState } from 'react'
55

66
export default function CopyButton({ text }: { text: string }) {
7-
const tComponent = useTranslations('components.controls.copyButton')
7+
const tComponent = useTranslations('components.controls')
88
const [copied, setCopied] = useState(false)
99

1010
const handleCopy = async () => {
@@ -26,8 +26,10 @@ export default function CopyButton({ text }: { text: string }) {
2626
? 'text-green-600'
2727
: 'text-[var(--color-text-muted)] hover:text-[var(--color-text)]'
2828
}`}
29-
title={copied ? tComponent('copied') : tComponent('copyToClipboard')}
30-
aria-label={copied ? tComponent('copied') : tComponent('copyToClipboard')}
29+
title={copied ? tComponent('copyButton.copied') : tComponent('copyButton.copyToClipboard')}
30+
aria-label={
31+
copied ? tComponent('copyButton.copied') : tComponent('copyButton.copyToClipboard')
32+
}
3133
>
3234
{copied ? (
3335
<svg
@@ -41,7 +43,7 @@ export default function CopyButton({ text }: { text: string }) {
4143
strokeLinejoin="round"
4244
role="img"
4345
>
44-
<title>{tComponent('copied')}</title>
46+
<title>{tComponent('copyButton.copied')}</title>
4547
<polyline points="20 6 9 17 4 12"></polyline>
4648
</svg>
4749
) : (
@@ -56,7 +58,7 @@ export default function CopyButton({ text }: { text: string }) {
5658
strokeLinejoin="round"
5759
role="img"
5860
>
59-
<title>{tComponent('copyToClipboard')}</title>
61+
<title>{tComponent('copyButton.copyToClipboard')}</title>
6062
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
6163
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
6264
</svg>

src/components/controls/FilterSortBar.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,20 @@ export default function FilterSortBar({
2828
searchQuery = '',
2929
onSearchChange,
3030
}: FilterSortBarProps) {
31-
const tComponent = useTranslations('components.controls.filterSortBar')
31+
const tComponent = useTranslations('components.controls')
3232
const tShared = useTranslations('shared')
3333
const [isSortOpen, setIsSortOpen] = useState(false)
3434
const sortRef = useRef<HTMLDivElement>(null)
3535

3636
const sortOptions = [
37-
{ value: 'default', label: tComponent('sortDefault') },
38-
{ value: 'name-asc', label: tComponent('sortNameAsc') },
39-
{ value: 'name-desc', label: tComponent('sortNameDesc') },
37+
{ value: 'default', label: tComponent('filterSortBar.sortDefault') },
38+
{ value: 'name-asc', label: tComponent('filterSortBar.sortNameAsc') },
39+
{ value: 'name-desc', label: tComponent('filterSortBar.sortNameDesc') },
4040
]
4141

4242
const currentSortLabel =
43-
sortOptions.find(opt => opt.value === sortOrder)?.label || tComponent('sortDefault')
43+
sortOptions.find(opt => opt.value === sortOrder)?.label ||
44+
tComponent('filterSortBar.sortDefault')
4445

4546
// Close dropdown when clicking outside
4647
useEffect(() => {
@@ -99,7 +100,9 @@ export default function FilterSortBar({
99100

100101
{/* Sort Custom Dropdown */}
101102
<div className="flex items-center gap-[var(--spacing-xs)]">
102-
<span className="text-xs text-[var(--color-text-muted)]">{tComponent('sort')}</span>
103+
<span className="text-xs text-[var(--color-text-muted)]">
104+
{tComponent('filterSortBar.sort')}
105+
</span>
103106
<div className="relative" ref={sortRef}>
104107
<button
105108
type="button"
@@ -136,7 +139,9 @@ export default function FilterSortBar({
136139

137140
{/* License Filter Buttons */}
138141
<div className="flex items-center gap-[var(--spacing-xs)]">
139-
<span className="text-xs text-[var(--color-text-muted)]">{tComponent('license')}</span>
142+
<span className="text-xs text-[var(--color-text-muted)]">
143+
{tComponent('filterSortBar.license')}
144+
</span>
140145
<button
141146
type="button"
142147
onClick={() => toggleLicense('open-source')}
@@ -164,7 +169,7 @@ export default function FilterSortBar({
164169
{/* Platform Filter Buttons */}
165170
<div className="flex items-center gap-[var(--spacing-xs)]">
166171
<span className="text-xs text-[var(--color-text-muted)]">
167-
{platformLabel || tComponent('platform')}
172+
{platformLabel || tComponent('filterSortBar.platform')}
168173
</span>
169174
{availablePlatforms.map(platform => (
170175
<button
@@ -189,7 +194,7 @@ export default function FilterSortBar({
189194
onClick={clearFilters}
190195
className="ml-auto px-[var(--spacing-sm)] py-1 text-sm text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition-colors"
191196
>
192-
{tComponent('clearFilters')}
197+
{tComponent('filterSortBar.clearFilters')}
193198
</button>
194199
)}
195200
</div>

src/components/controls/LanguageSwitcher.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export default function LanguageSwitcher() {
5858
const locale = useLocale() as Locale
5959
const router = useRouter()
6060
const pathname = usePathname()
61-
const tComponent = useTranslations('components.common.footer')
61+
const tComponent = useTranslations('components.common')
6262
const [isOpen, setIsOpen] = useState(false)
6363
const dropdownRef = useRef<HTMLDivElement>(null)
6464

@@ -120,8 +120,8 @@ export default function LanguageSwitcher() {
120120
type="button"
121121
onClick={() => setIsOpen(!isOpen)}
122122
className="footer-control-button"
123-
title={tComponent('selectLanguage')}
124-
aria-label={tComponent('selectLanguage')}
123+
title={tComponent('footer.selectLanguage')}
124+
aria-label={tComponent('footer.selectLanguage')}
125125
aria-expanded={isOpen}
126126
>
127127
<Languages className="footer-control-icon" />

src/components/controls/SearchDialog.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface SearchDialogProps {
1717

1818
export default function SearchDialog({ isOpen, onClose, locale }: SearchDialogProps) {
1919
const tShared = useTranslations('shared')
20-
const tComponent = useTranslations('components.controls.searchDialog')
20+
const tComponent = useTranslations('components.controls')
2121
const router = useRouter()
2222
const [query, setQuery] = useState('')
2323
const [suggestions, setSuggestions] = useState<SearchResult[]>([])
@@ -104,7 +104,7 @@ export default function SearchDialog({ isOpen, onClose, locale }: SearchDialogPr
104104
<Command.Input
105105
value={query}
106106
onValueChange={setQuery}
107-
placeholder={tComponent('placeholder')}
107+
placeholder={tComponent('searchDialog.placeholder')}
108108
className="flex-1 bg-transparent text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:outline-none text-base"
109109
autoFocus
110110
/>
@@ -118,7 +118,9 @@ export default function SearchDialog({ isOpen, onClose, locale }: SearchDialogPr
118118
{/* Empty State */}
119119
<Command.Empty className="py-12 text-center">
120120
{query.trim() === '' ? (
121-
<div className="text-[var(--color-text-muted)] text-sm">{tComponent('empty')}</div>
121+
<div className="text-[var(--color-text-muted)] text-sm">
122+
{tComponent('searchDialog.empty')}
123+
</div>
122124
) : (
123125
<div className="text-center">
124126
<svg
@@ -136,7 +138,7 @@ export default function SearchDialog({ isOpen, onClose, locale }: SearchDialogPr
136138
/>
137139
</svg>
138140
<p className="text-sm text-[var(--color-text-muted)]">
139-
{tComponent('noResultsFor', { query })}
141+
{tComponent('searchDialog.noResultsFor', { query })}
140142
</p>
141143
</div>
142144
)}
@@ -180,7 +182,7 @@ export default function SearchDialog({ isOpen, onClose, locale }: SearchDialogPr
180182
className="px-4 py-3 cursor-pointer transition-colors border-t border-[var(--color-border)] data-[selected=true]:bg-[var(--color-hover)] aria-selected:bg-[var(--color-hover)] text-[var(--color-text-secondary)]"
181183
>
182184
<div className="flex items-center gap-2 text-sm">
183-
<span>{tComponent('viewAllResults', { query })}</span>
185+
<span>{tComponent('searchDialog.viewAllResults', { query })}</span>
184186
<span className="ml-auto text-[var(--color-text-muted)]"></span>
185187
</div>
186188
</Command.Item>
@@ -199,13 +201,13 @@ export default function SearchDialog({ isOpen, onClose, locale }: SearchDialogPr
199201
<kbd className="px-1.5 py-0.5 border border-[var(--color-border)] bg-[var(--color-bg)]">
200202
201203
</kbd>
202-
<span>{tComponent('navigate')}</span>
204+
<span>{tComponent('searchDialog.navigate')}</span>
203205
</div>
204206
<div className="flex items-center gap-1.5">
205207
<kbd className="px-1.5 py-0.5 border border-[var(--color-border)] bg-[var(--color-bg)]">
206208
207209
</kbd>
208-
<span>{tComponent('select')}</span>
210+
<span>{tComponent('searchDialog.select')}</span>
209211
</div>
210212
</div>
211213
)}

src/components/controls/SearchInput.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export default function SearchInput({
1818
onSearch,
1919
}: SearchInputProps) {
2020
const tShared = useTranslations('shared')
21-
const tComponent = useTranslations('components.controls.searchInput')
2221
const router = useRouter()
2322
const [query, setQuery] = useState(initialQuery)
2423
const [suggestions, setSuggestions] = useState<SearchResult[]>([])
@@ -29,7 +28,7 @@ export default function SearchInput({
2928
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)
3029
const hasUserInteracted = useRef(false)
3130

32-
const placeholderText = placeholder || tComponent('placeholder')
31+
const placeholderText = placeholder || tShared('actions.searchPlaceholder')
3332

3433
// Debounce search suggestions
3534
useEffect(() => {

src/components/controls/ThemeSwitcher.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import { useTheme } from '../ThemeProvider'
1010
*/
1111
export default function ThemeSwitcher() {
1212
const { theme, toggleTheme } = useTheme()
13-
const tComponent = useTranslations('components.common.footer')
13+
const tComponent = useTranslations('components.common')
1414

1515
return (
1616
<button
1717
type="button"
1818
onClick={toggleTheme}
1919
className="footer-control-button"
20-
title={tComponent('toggleTheme')}
21-
aria-label={tComponent('toggleTheme')}
20+
title={tComponent('footer.toggleTheme')}
21+
aria-label={tComponent('footer.toggleTheme')}
2222
>
2323
{theme === 'light' ? (
2424
<Moon className="footer-control-icon" />

src/components/navigation/Breadcrumb.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export interface BreadcrumbItem {
1818
* - Sticky behavior is enabled when scrolling using CSS position: sticky.
1919
*/
2020
export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
21-
const tComponent = useTranslations('components.navigation.breadcrumb')
21+
const tComponent = useTranslations('components.navigation')
2222
const sectionRef = useRef<HTMLElement>(null)
2323

2424
// Dynamically adjust sticky position based on header height
@@ -50,7 +50,7 @@ export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
5050
{
5151
'@type': 'ListItem',
5252
position: 1,
53-
name: tComponent('home'),
53+
name: tComponent('breadcrumb.home'),
5454
item: SITE_CONFIG.url,
5555
},
5656
...items.map((item, index) => {

src/components/navigation/RankingMegaMenu.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const RankingMegaMenu = memo(function RankingMegaMenu({
1717
isOpen,
1818
onClose,
1919
}: RankingMegaMenuProps) {
20-
const tComponent = useTranslations('components.common.header')
20+
const tComponent = useTranslations('components.common')
2121

2222
if (!isOpen) return null
2323

@@ -30,9 +30,11 @@ export const RankingMegaMenu = memo(function RankingMegaMenu({
3030
<div className="p-[var(--spacing-md)]">
3131
{/* Open Source Ranking Link */}
3232
<Link href="/open-source-rank" onClick={onClose} className={featuredLinkClass}>
33-
<div className="font-medium mb-[var(--spacing-xs)]">{tComponent('openSourceRank')}</div>
33+
<div className="font-medium mb-[var(--spacing-xs)]">
34+
{tComponent('header.openSourceRank')}
35+
</div>
3436
<div className="text-xs text-[var(--color-text-secondary)]">
35-
{tComponent('openSourceRankDesc')}
37+
{tComponent('header.openSourceRankDesc')}
3638
</div>
3739
</Link>
3840
</div>

0 commit comments

Comments
 (0)