From ec6633a820c605f5d7ef88048853b462962ad489 Mon Sep 17 00:00:00 2001 From: tetiana zorii Date: Mon, 2 Feb 2026 14:00:12 -0500 Subject: [PATCH] feat(i18n): add translations for blog categories, and UI components - Translate blog category labels in mobile menu, cards, and filters - Add CTA hover variants translations - Add aria-label translations for theme toggle, cart, search, GitHub star - Update translation files for EN, UK, PL locales --- frontend/components/blog/BlogCard.tsx | 21 ++++++++++++++++- frontend/components/blog/BlogFilters.tsx | 2 +- frontend/components/blog/BlogHeaderSearch.tsx | 3 ++- frontend/components/header/AppMobileMenu.tsx | 21 ++++++++++++++++- frontend/components/header/DesktopActions.tsx | 5 ++-- .../components/home/InteractiveCTAButton.tsx | 15 ++++++------ frontend/components/quiz/QuizContainer.tsx | 2 +- .../components/shared/GitHubStarButton.tsx | 4 +++- .../components/shop/header/CartButton.tsx | 5 +++- frontend/components/theme/ThemeToggle.tsx | 12 ++++++---- frontend/messages/en.json | 23 ++++++++++++++++--- frontend/messages/pl.json | 23 ++++++++++++++++--- frontend/messages/uk.json | 23 ++++++++++++++++--- 13 files changed, 129 insertions(+), 30 deletions(-) diff --git a/frontend/components/blog/BlogCard.tsx b/frontend/components/blog/BlogCard.tsx index 646f282e..b7e09afe 100644 --- a/frontend/components/blog/BlogCard.tsx +++ b/frontend/components/blog/BlogCard.tsx @@ -22,6 +22,24 @@ export default function BlogCard({ disableHoverColor?: boolean; }) { const t = useTranslations('blog'); + + const getCategoryLabel = (categoryName: string): string => { + const key = categoryName.toLowerCase() as + | 'tech' + | 'career' + | 'insights' + | 'news' + | 'growth'; + const categoryTranslations: Record = { + tech: t('categories.tech'), + career: t('categories.career'), + insights: t('categories.insights'), + news: t('categories.news'), + growth: t('categories.growth'), + }; + return categoryTranslations[key] || categoryName; + }; + const excerpt = (post.body ?? []) .filter((b): b is PortableTextBlock => b._type === 'block') @@ -34,8 +52,9 @@ export default function BlogCard({ () => formatBlogDate(post.publishedAt), [post.publishedAt] ); - const categoryLabel = + const rawCategory = post.categories?.[0] === 'Growth' ? 'Career' : post.categories?.[0]; + const categoryLabel = rawCategory ? getCategoryLabel(rawCategory) : undefined; return (
{featuredPost.categories?.[0] && (
- {featuredPost.categories[0]} + {getCategoryLabel(featuredPost.categories[0] === 'Growth' ? 'Career' : featuredPost.categories[0])}
)}
diff --git a/frontend/components/blog/BlogHeaderSearch.tsx b/frontend/components/blog/BlogHeaderSearch.tsx index 42ac9d35..c934883f 100644 --- a/frontend/components/blog/BlogHeaderSearch.tsx +++ b/frontend/components/blog/BlogHeaderSearch.tsx @@ -42,6 +42,7 @@ function normalizeSearchText(value: string) { export function BlogHeaderSearch() { const t = useTranslations('blog'); + const tAria = useTranslations('aria'); const locale = useLocale(); const [open, setOpen] = useState(false); const [value, setValue] = useState(''); @@ -158,7 +159,7 @@ export function BlogHeaderSearch() { }) } className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground" - aria-label="Search blog" + aria-label={tAria('searchBlog')} >
(null); const githubUrl = 'https://github.com/DevLoversTeam/devlovers.net'; @@ -74,7 +76,7 @@ export function GitHubStarButton({ className = '' }: GitHubStarButtonProps) { href={githubUrl} target="_blank" rel="noopener noreferrer" - aria-label={`Star on GitHub - ${displayCount} stars`} + aria-label={t('starOnGithub', { count: displayCount })} className={` hidden lg:inline-flex group relative diff --git a/frontend/components/shop/header/CartButton.tsx b/frontend/components/shop/header/CartButton.tsx index 5077846e..fdb15f31 100644 --- a/frontend/components/shop/header/CartButton.tsx +++ b/frontend/components/shop/header/CartButton.tsx @@ -1,6 +1,7 @@ 'use client'; import { ShoppingBag } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { useMounted } from '@/hooks/use-mounted'; import { HeaderButton } from '@/components/shared/HeaderButton'; @@ -10,18 +11,20 @@ import { useCart } from '../CartProvider'; export function CartButton() { const { cart } = useCart(); const mounted = useMounted(); + const t = useTranslations('aria'); const itemCount = mounted ? cart.summary.itemCount : 0; const showCount = itemCount > 0; const badgeText = itemCount > 99 ? '99+' : itemCount; + const label = showCount ? t('cartWithItems', { count: itemCount }) : t('cart'); return ( diff --git a/frontend/components/theme/ThemeToggle.tsx b/frontend/components/theme/ThemeToggle.tsx index 515f96d4..770586b0 100644 --- a/frontend/components/theme/ThemeToggle.tsx +++ b/frontend/components/theme/ThemeToggle.tsx @@ -1,18 +1,20 @@ 'use client'; import { useTheme } from 'next-themes'; +import { useTranslations } from 'next-intl'; import { Monitor, Sun, Moon } from 'lucide-react'; import { motion } from 'framer-motion'; import { useEffect, useState } from 'react'; const themes = [ - { value: 'system', icon: Monitor, label: 'System theme' }, - { value: 'light', icon: Sun, label: 'Light theme' }, - { value: 'dark', icon: Moon, label: 'Dark theme' }, + { value: 'system', icon: Monitor, labelKey: 'themeSystem' }, + { value: 'light', icon: Sun, labelKey: 'themeLight' }, + { value: 'dark', icon: Moon, labelKey: 'themeDark' }, ] as const; export function ThemeToggle() { const { theme, setTheme } = useTheme(); + const t = useTranslations('aria'); const [mounted, setMounted] = useState(false); useEffect(() => { @@ -36,11 +38,11 @@ export function ThemeToggle() { return (
- {themes.map(({ value, icon: Icon, label }) => ( + {themes.map(({ value, icon: Icon, labelKey }) => (