fix: i18n for project section strings, unique SVG pattern IDs, remove hardcoded text

- Projects.tsx: use t() for title, subtitle, viewAll, noProjects
- ProjectsPageClient.tsx: use tList('title') instead of hardcoded 'Archive'
- ProjectThumbnail.tsx: useId() for unique SVG pattern IDs to avoid collisions
- Remove unused sizeClasses variable
- en.json: update project subtitle and add noProjects key
- de.json: update German translations for project section
This commit is contained in:
2026-04-16 14:39:17 +02:00
parent c442aa447b
commit dd46bcddc7
5 changed files with 37 additions and 40 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useMemo } from "react";
import { useMemo, useId } from "react";
import {
Terminal,
Smartphone,
@@ -103,42 +103,42 @@ const slugIcons: Record<string, LucideIcon> = {
"task-management-dashboard": LayoutDashboard,
};
function PatternOverlay({ pattern }: { pattern: string }) {
const patternMap: Record<string, React.ReactNode> = {
function PatternOverlay({ pattern, id }: { pattern: string; id: string }) {
const patterns: Record<string, React.ReactNode> = {
dots: (
<svg className="absolute inset-0 w-full h-full opacity-[0.07] dark:opacity-[0.05]" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="dots" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<pattern id={`pat-dots-${id}`} x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="2" cy="2" r="1.5" fill="currentColor" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#dots)" />
<rect width="100%" height="100%" fill={`url(#pat-dots-${id})`} />
</svg>
),
grid: (
<svg className="absolute inset-0 w-full h-full opacity-[0.06] dark:opacity-[0.04]" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
<pattern id={`pat-grid-${id}`} x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeWidth="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<rect width="100%" height="100%" fill={`url(#pat-grid-${id})`} />
</svg>
),
diagonal: (
<svg className="absolute inset-0 w-full h-full opacity-[0.06] dark:opacity-[0.04]" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="diagonal" x="0" y="0" width="24" height="24" patternUnits="userSpaceOnUse">
<pattern id={`pat-diag-${id}`} x="0" y="0" width="24" height="24" patternUnits="userSpaceOnUse">
<line x1="0" y1="24" x2="24" y2="0" stroke="currentColor" strokeWidth="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal)" />
<rect width="100%" height="100%" fill={`url(#pat-diag-${id})`} />
</svg>
),
circuit: (
<svg className="absolute inset-0 w-full h-full opacity-[0.07] dark:opacity-[0.05]" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="circuit" x="0" y="0" width="60" height="60" patternUnits="userSpaceOnUse">
<pattern id={`pat-circ-${id}`} x="0" y="0" width="60" height="60" patternUnits="userSpaceOnUse">
<path d="M0 30h20m20 0h20M30 0v20m0 20v20" stroke="currentColor" strokeWidth="0.8" fill="none" />
<circle cx="30" cy="30" r="3" fill="currentColor" />
<circle cx="10" cy="30" r="2" fill="currentColor" />
@@ -147,36 +147,36 @@ function PatternOverlay({ pattern }: { pattern: string }) {
<circle cx="30" cy="50" r="2" fill="currentColor" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#circuit)" />
<rect width="100%" height="100%" fill={`url(#pat-circ-${id})`} />
</svg>
),
waves: (
<svg className="absolute inset-0 w-full h-full opacity-[0.06] dark:opacity-[0.04]" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="waves" x="0" y="0" width="100" height="20" patternUnits="userSpaceOnUse">
<pattern id={`pat-wave-${id}`} x="0" y="0" width="100" height="20" patternUnits="userSpaceOnUse">
<path d="M0 10 Q25 0 50 10 T100 10" fill="none" stroke="currentColor" strokeWidth="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#waves)" />
<rect width="100%" height="100%" fill={`url(#pat-wave-${id})`} />
</svg>
),
terminal: (
<svg className="absolute inset-0 w-full h-full opacity-[0.08] dark:opacity-[0.06]" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="terminal" x="0" y="0" width="200" height="30" patternUnits="userSpaceOnUse">
<pattern id={`pat-term-${id}`} x="0" y="0" width="200" height="30" patternUnits="userSpaceOnUse">
<text x="4" y="18" fontFamily="monospace" fontSize="10" fill="currentColor">$_</text>
<text x="50" y="18" fontFamily="monospace" fontSize="10" fill="currentColor" opacity="0.4"></text>
<text x="50" y="18" fontFamily="monospace" fontSize="10" fill="currentColor" opacity="0.4">&#x2502;</text>
<text x="70" y="18" fontFamily="monospace" fontSize="10" fill="currentColor" opacity="0.6">404</text>
<text x="110" y="18" fontFamily="monospace" fontSize="10" fill="currentColor" opacity="0.4"></text>
<text x="110" y="18" fontFamily="monospace" fontSize="10" fill="currentColor" opacity="0.4">&#x2502;</text>
<text x="130" y="18" fontFamily="monospace" fontSize="10" fill="currentColor" opacity="0.3">ERR</text>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#terminal)" />
<rect width="100%" height="100%" fill={`url(#pat-term-${id})`} />
</svg>
),
};
return patternMap[pattern] || patternMap.dots;
return patterns[pattern] || patterns.dots;
}
export default function ProjectThumbnail({
@@ -186,6 +186,7 @@ export default function ProjectThumbnail({
slug,
size = "card",
}: ProjectThumbnailProps) {
const uniqueId = useId();
const theme = useMemo(() => {
if (slug && slugIcons[slug]) {
const matchedTheme = categoryThemes[category || ""] || categoryThemes.default;
@@ -195,20 +196,14 @@ export default function ProjectThumbnail({
}, [category, slug]);
const Icon = theme.icon;
const isHero = size === "hero";
const displayTags = tags?.slice(0, 3) ?? [];
const sizeClasses = isHero
? ""
: "";
return (
<div
className={`absolute inset-0 flex items-center justify-center bg-gradient-to-br ${theme.gradient} ${theme.darkGradient} ${sizeClasses}`}
className={`absolute inset-0 flex items-center justify-center bg-gradient-to-br ${theme.gradient} ${theme.darkGradient}`}
>
<PatternOverlay pattern={theme.pattern} />
<PatternOverlay pattern={theme.pattern} id={uniqueId} />
<div className="relative z-10 flex flex-col items-center gap-3 sm:gap-4">
<div