feat(i18n): add multilingual support for all sections

Implement internationalization across all components by adding lang prop and translation keys
Update data structure to use translation keys instead of hardcoded text
Add comprehensive English and Chinese translations in ui.ts
This commit is contained in:
joyzhao
2025-06-15 09:34:17 +08:00
parent 21c337a040
commit 720686751a
10 changed files with 415 additions and 145 deletions

View File

@@ -1,11 +1,13 @@
import React from "react";
import { awards } from "@/lib/data";
import { useTranslations } from "@/i18n/utils";
import { Trophy } from "lucide-react";
import MotionWrapper from "./MotionWrapper";
import { GlassCard } from "./ui/glass-card";
import { motion } from "framer-motion";
export default function AwardsSection() {
export default function AwardsSection({ lang }: { lang: "en" | "zh" }) {
const t = useTranslations(lang);
return (
<section
id="awards"
@@ -14,7 +16,7 @@ export default function AwardsSection() {
<div className="container max-w-4xl mx-auto px-6 md:px-4">
<MotionWrapper>
<h2 className="text-2xl font-bold mb-8 text-center md:text-left">
🏆 Awards
🏆 {t('awards.title')}
</h2>
</MotionWrapper>
@@ -30,29 +32,29 @@ export default function AwardsSection() {
>
<Trophy className="h-4 w-4 text-white" />
</motion.div>
<h3 className="font-medium">{award.name}</h3>
<h3 className="font-medium">{t(award.name as any)}</h3>
</div>
<p className="text-xs text-muted-foreground mb-1 pl-8">
🏢 {award.issuer}
🏢 {t(award.issuer as any)}
</p>
<div className="flex flex-col space-y-2 mt-auto">
<div className="flex justify-between items-center">
<span className="text-xs text-muted-foreground bg-background/50 px-2 py-1 rounded-md">
📅 {award.date}
📅 {t(award.date as any)}
</span>
<motion.span
className="text-xs px-2 py-1 bg-purple-500/10 rounded-full"
whileHover={{ scale: 1.05 }}
>
{award.position}
{t(award.position as any)}
</motion.span>
</div>
<motion.span
className="text-xs text-muted-foreground/80 bg-background/50 px-2 py-1 rounded-md w-fit"
whileHover={{ scale: 1.05 }}
>
{award.type === "International" ? "🌎 " : "🇮🇳 "}
{award.type}
{award.type === "awards.type.international" ? "🌎 " : "🇮🇳 "}
{t(award.type as any)}
</motion.span>
</div>
</GlassCard>

View File

@@ -1,10 +1,12 @@
import { education } from "@/lib/data";
import TimelineItem from "./TimelineItem";
import { useTranslations } from "@/i18n/utils";
import { Award } from "lucide-react";
import MotionWrapper from "./MotionWrapper";
import { motion } from "framer-motion";
export default function EducationSection() {
export default function EducationSection({ lang }: { lang: "en" | "zh" }) {
const t = useTranslations(lang);
return (
<section
id="education"
@@ -13,22 +15,24 @@ export default function EducationSection() {
<div className="container max-w-4xl mx-auto px-6 md:px-4">
<MotionWrapper>
<h2 className="text-2xl font-bold mb-8 text-center md:text-left">
🎓 Education
🎓 {t('education.title')}
</h2>
</MotionWrapper>
<div className="mb-8">
{education.map((edu, index) => (
{education.map((edu, index) => {
const institutionKey = edu.institution.toLowerCase().replace(/[^a-z0-9]/g, '');
return (
<TimelineItem
key={edu.institution}
title={`🎓 ${edu.degree}`}
subtitle={`🏛️ ${edu.institution}`}
date={`📅 ${edu.period}`}
title={`🎓 ${t(`education.${institutionKey}.degree` as any)}`}
subtitle={`🏛️ ${t(`education.${institutionKey}.institution` as any)}`}
date={`📅 ${t(`education.${institutionKey}.period` as any)}`}
isLast={index === education.length - 1}
index={index}
>
<p className="text-sm text-muted-foreground mb-3">
📍 {edu.location}
📍 {t(`education.${institutionKey}.location` as any)}
</p>
{edu.achievements && edu.achievements.length > 0 && (
@@ -44,7 +48,7 @@ export default function EducationSection() {
<Award className="h-4 w-4 text-purple-500" />
</div>
<h4 className="text-sm font-medium">
Achievements & Activities
{t('education.achievementsAndActivities')}
</h4>
</div>
<ul className="list-none ml-4 space-y-2 text-sm">
@@ -57,14 +61,14 @@ export default function EducationSection() {
transition={{ duration: 0.3, delay: 0.1 * i }}
viewport={{ once: true }}
>
{achievement}
{t(`education.${institutionKey}.achievements.${i}` as any)}
</motion.li>
))}
</ul>
</motion.div>
)}
</TimelineItem>
))}
)})}
</div>
</div>
</section>

View File

@@ -1,10 +1,12 @@
import { workExperience } from "@/lib/data";
import TimelineItem from "./TimelineItem";
import { useTranslations } from "@/i18n/utils";
import { Briefcase } from "lucide-react";
import { motion } from "framer-motion";
import MotionWrapper from "./MotionWrapper";
export default function ExperienceSection() {
export default function ExperienceSection({ lang }: { lang: "en" | "zh" }) {
const t = useTranslations(lang);
return (
<section
id="experience"
@@ -21,17 +23,19 @@ export default function ExperienceSection() {
viewport={{ once: true }}
>
💼
</motion.span>{" "}
Work Experience
</motion.span>
{t('experience.title')}
</h2>
</MotionWrapper>
<div className="mb-8">
{workExperience.map((job, index) => (
{workExperience.map((job, index) => {
const companyKey = job.company.toLowerCase().replace(/ /g, '');
return (
<TimelineItem
key={job.company + job.period}
title={`👨‍💻 ${job.position} | ${job.company}`}
subtitle={`🌍 ${job.location}`}
date={`📅 ${job.period}`}
title={`👨‍💻 ${t(`work.${companyKey}.position` as any)} | ${t(`work.${companyKey}.company` as any)}`}
subtitle={`🌍 ${t(`work.${companyKey}.location` as any)}`}
date={`📅 ${t(`work.${companyKey}.period` as any)}`}
isLast={index === workExperience.length - 1}
index={index}
>
@@ -46,7 +50,7 @@ export default function ExperienceSection() {
<div className="h-6 w-6 flex items-center justify-center rounded-full bg-purple-500/10 mr-2">
<Briefcase className="h-4 w-4 text-purple-500" />
</div>
<h4 className="text-sm font-medium">Key Achievements</h4>
<h4 className="text-sm font-medium">{t('experience.keyAchievements')}</h4>
</div>
<ul className="list-none ml-4 space-y-2 text-sm">
{job.achievements.map((achievement, i) => (
@@ -58,13 +62,13 @@ export default function ExperienceSection() {
transition={{ duration: 0.3, delay: 0.1 * i }}
viewport={{ once: true }}
>
{achievement}
{t(`work.${companyKey}.achievements.${i}` as any)}
</motion.li>
))}
</ul>
</motion.div>
</TimelineItem>
))}
)})}
</div>
</div>
</section>

View File

@@ -1,9 +1,11 @@
import { personalInfo } from "@/lib/data";
import { Mail, Github, MapPin, Linkedin } from "lucide-react";
import { useTranslations } from "@/i18n/utils";
import { motion } from "framer-motion";
import MotionWrapper from "./MotionWrapper";
export default function HeroSection() {
export default function HeroSection({ lang }: { lang: "en" | "zh" }) {
const t = useTranslations(lang);
const containerVariants = {
hidden: { opacity: 0 },
visible: {
@@ -40,7 +42,7 @@ export default function HeroSection() {
className="text-4xl font-bold mb-2"
variants={childVariants}
>
{personalInfo.name}{" "}
{t('personal.name')}{" "}
<span className="inline-block animate-pulse"></span>
</motion.h1>
@@ -48,7 +50,7 @@ export default function HeroSection() {
className="text-xl text-muted-foreground mb-6"
variants={childVariants}
>
Software Engineer 👨💻
{t('personal.title')}
</motion.p>
<motion.div
@@ -61,17 +63,17 @@ export default function HeroSection() {
whileHover={{ scale: 1.05, color: "#4b5563" }}
>
<MapPin className="h-4 w-4 mr-2" />
📍 {personalInfo.location}
📍 {t('personal.location')}
</motion.div>
<motion.a
href={`mailto:${personalInfo.email}`}
href={`mailto:${t('personal.email')}`}
className="flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors"
variants={childVariants}
whileHover={{ scale: 1.05, color: "#4b5563" }}
>
<Mail className="h-4 w-4 mr-2" />
{personalInfo.email}
{t('personal.email')}
</motion.a>
<motion.a
@@ -83,7 +85,7 @@ export default function HeroSection() {
whileHover={{ scale: 1.05, color: "#4b5563" }}
>
<Github className="h-4 w-4 mr-2" />
🌟 GitHub
🌟 {t('hero.githubLink')}
</motion.a>
<motion.a
@@ -95,7 +97,7 @@ export default function HeroSection() {
whileHover={{ scale: 1.05, color: "#4b5563" }}
>
<Linkedin className="h-4 w-4 mr-2" />
🔗 LinkedIn
🔗 {t('hero.linkedinLink')}
</motion.a>
</motion.div>
</div>
@@ -122,15 +124,7 @@ export default function HeroSection() {
<div className="bg-gradient-to-r from-purple-500/10 to-pink-500/10 backdrop-blur-sm backdrop-filter p-4 rounded-lg border border-purple-500/20 dark:border-purple-500/10 shadow-sm">
<p className="text-muted-foreground pl-4 py-2 mb-4 relative">
<span className="absolute left-0 top-0 h-full w-1 bg-gradient-to-b from-purple-500 to-pink-500 rounded-full"></span>
🚀 Passionate software engineer with a versatile skill set
spanning multiple domains. I thrive on solving complex challenges
across different platforms and environments, adapting quickly to
new technologies and methodologies. My holistic approach combines
technical expertise with creative problem-solving, allowing me to
develop solutions that are both innovative and practical. I'm
driven by continuous learning and a commitment to excellence,
whether working independently or collaborating with diverse teams
to create impactful, scalable solutions.
{t('hero.description')}
</p>
</div>
</MotionWrapper>

View File

@@ -1,5 +1,6 @@
import React from "react";
import { projects } from "@/lib/data";
import { useTranslations } from "@/i18n/utils";
import {
CardContent,
CardDescription,
@@ -12,13 +13,14 @@ import { GlassCard } from "./ui/glass-card";
import MotionWrapper from "./MotionWrapper";
import { motion } from "framer-motion";
export default function ProjectsSection() {
export default function ProjectsSection({ lang }: { lang: "en" | "zh" }) {
const t = useTranslations(lang);
return (
<section id="projects" className="py-12 relative">
<div className="container max-w-4xl mx-auto px-6 md:px-4">
<MotionWrapper>
<h2 className="text-2xl font-bold mb-8 text-center md:text-left">
🚀 Projects
🚀 {t('projects.title')}
</h2>
</MotionWrapper>
@@ -42,7 +44,7 @@ export default function ProjectsSection() {
transition={{ delay: i * 0.1 }}
viewport={{ once: true }}
>
{desc}
{t(desc as any)}
</motion.li>
))}
</ul>
@@ -57,7 +59,7 @@ export default function ProjectsSection() {
whileTap={{ scale: 0.95 }}
>
<Github className="h-4 w-4 mr-2 group-hover/link:rotate-12 transition-transform duration-300" />
View on GitHub 🔗
{t('projects.viewOnGithub')} 🔗
</motion.a>
</CardFooter>
</GlassCard>

View File

@@ -1,5 +1,6 @@
import React from "react";
import { skills } from "@/lib/data";
import { useTranslations } from "@/i18n/utils";
import { motion } from "framer-motion";
import MotionWrapper from "./MotionWrapper";
import { GlassCard } from "./ui/glass-card";
@@ -44,7 +45,8 @@ const skillCategoryVariants = {
},
};
export default function SkillsSection() {
export default function SkillsSection({ lang }: { lang: "en" | "zh" }) {
const t = useTranslations(lang);
return (
<section
id="skills"
@@ -53,7 +55,7 @@ export default function SkillsSection() {
<div className="container max-w-4xl mx-auto px-6 md:px-4">
<MotionWrapper>
<h2 className="text-2xl font-bold mb-8 text-center md:text-left">
🛠 Skills
🛠 {t('skills.title')}
</h2>
</MotionWrapper>
@@ -67,7 +69,7 @@ export default function SkillsSection() {
<motion.div variants={skillCategoryVariants}>
<GlassCard className="p-4">
<h3 className="text-lg font-medium mb-3 text-center md:text-left flex items-center">
<span className="mr-2 text-xl">💻</span> Programming Languages
<span className="mr-2 text-xl">💻</span> {t('skills.programmingLanguages')}
</h3>
<div className="flex flex-wrap gap-2 justify-center md:justify-start">
{skills.programmingLanguages.map((skill, index) => (
@@ -80,7 +82,7 @@ export default function SkillsSection() {
<motion.div variants={skillCategoryVariants}>
<GlassCard className="p-4">
<h3 className="text-lg font-medium mb-3 text-center md:text-left flex items-center">
<span className="mr-2 text-xl">🎨</span> Frontend Development
<span className="mr-2 text-xl">🎨</span> {t('skills.frontendDevelopment')}
</h3>
<div className="flex flex-wrap gap-2 justify-center md:justify-start">
{skills.frontendDevelopment.map((skill, index) => (
@@ -93,7 +95,7 @@ export default function SkillsSection() {
<motion.div variants={skillCategoryVariants}>
<GlassCard className="p-4">
<h3 className="text-lg font-medium mb-3 text-center md:text-left flex items-center">
<span className="mr-2 text-xl"></span> Backend Development
<span className="mr-2 text-xl"></span> {t('skills.backendDevelopment')}
</h3>
<div className="flex flex-wrap gap-2 justify-center md:justify-start">
{skills.backendDevelopment.map((skill, index) => (
@@ -106,7 +108,7 @@ export default function SkillsSection() {
<motion.div variants={skillCategoryVariants}>
<GlassCard className="p-4">
<h3 className="text-lg font-medium mb-3 text-center md:text-left flex items-center">
<span className="mr-2 text-xl">🗄</span> Database & Storage
<span className="mr-2 text-xl">🗄</span> {t('skills.databaseAndStorage')}
</h3>
<div className="flex flex-wrap gap-2 justify-center md:justify-start">
{skills.databaseAndStorage.map((skill, index) => (
@@ -119,7 +121,7 @@ export default function SkillsSection() {
<motion.div variants={skillCategoryVariants}>
<GlassCard className="p-4">
<h3 className="text-lg font-medium mb-3 text-center md:text-left flex items-center">
<span className="mr-2 text-xl"></span> Cloud & DevOps
<span className="mr-2 text-xl"></span> {t('skills.cloudAndDevOps')}
</h3>
<div className="flex flex-wrap gap-2 justify-center md:justify-start">
{skills.cloudAndDevOps.map((skill, index) => (
@@ -132,7 +134,7 @@ export default function SkillsSection() {
<motion.div variants={skillCategoryVariants}>
<GlassCard className="p-4">
<h3 className="text-lg font-medium mb-3 text-center md:text-left flex items-center">
<span className="mr-2 text-xl">🧰</span> Tools & Services
<span className="mr-2 text-xl">🧰</span> {t('skills.toolsAndServices')}
</h3>
<div className="flex flex-wrap gap-2 justify-center md:justify-start">
{skills.toolsAndServices.map((skill, index) => (