refactor(i18n): simplify language handling across components

use Astro.currentLocale as single source of truth for language
remove manual lang prop passing to components
components now read language from document lang attribute
This commit is contained in:
joyzhao
2025-06-19 10:24:48 +08:00
parent 4621223d26
commit c1f240d007
10 changed files with 78 additions and 22 deletions

View File

@@ -1,12 +1,25 @@
import { useTranslations, type Lang } from "@/i18n/utils";
import { personalInfo } from "@/lib/data";
import { motion } from "framer-motion";
import { useState, useEffect } from "react";
import { defaultLang } from "@/i18n/ui";
interface FooterProps {
lang: Lang;
lang?: Lang;
}
export default function Footer({ lang }: FooterProps) {
export default function Footer({ lang: propLang }: FooterProps) {
// 优先使用props传入的语言如果没有则尝试从HTML lang属性获取
const [lang, setLang] = useState<Lang>(propLang || defaultLang);
useEffect(() => {
// 在客户端运行时从HTML lang属性获取当前语言
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== lang)) {
setLang(htmlLang);
}
}, [propLang, lang]);
const t = useTranslations(lang);
return (
<footer className="border-t border-purple-500/10 py-6 bg-gradient-to-b from-background to-muted/20 backdrop-blur-sm">

View File

@@ -4,12 +4,25 @@ import ThemeToggle from "./ui/theme-toggle";
import { useTranslations, getLocalizedPath, type Lang } from "@/i18n/utils";
import { useState, useEffect } from "react";
import { Menu, X } from "lucide-react";
import { defaultLang } from "@/i18n/ui";
// 从window.document.documentElement.lang获取当前语言
interface GlassHeaderProps {
lang: Lang;
lang?: Lang;
}
export default function GlassHeader({ lang }: GlassHeaderProps) {
export default function GlassHeader({ lang: propLang }: GlassHeaderProps) {
// 优先使用props传入的语言如果没有则尝试从HTML lang属性获取
const [lang, setLang] = useState<Lang>(propLang || defaultLang);
useEffect(() => {
// 在客户端运行时从HTML lang属性获取当前语言
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== lang)) {
setLang(htmlLang);
}
}, [propLang, lang]);
const t = useTranslations(lang);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { getLocalizedPath, type Lang } from "@/i18n/utils"; // getLangFromUrl is removed as Astro.currentLocale is used now.
import { getLocalizedPath, type Lang } from "@/i18n/utils";
import { languages as i18nLanguages, defaultLang } from "@/i18n/ui";
import { Languages, Check, ChevronDown } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
@@ -7,26 +7,41 @@ import { motion, AnimatePresence } from "framer-motion";
const availableLanguages = Object.entries(i18nLanguages).map(([code, name]) => ({
code: code as Lang,
name,
// You can add icons here if you have a mapping or a more complex structure in ui.ts
// 语言图标映射
icon: code === 'en' ? '🇬🇧' : code === 'zh' ? '🇨🇳' : '🌐'
}));
interface LanguageSwitcherProps {
lang: Lang;
lang?: Lang;
}
export default function LanguageSwitcher({ lang: initialLang }: LanguageSwitcherProps) {
export default function LanguageSwitcher({ lang: propLang }: LanguageSwitcherProps) {
const [isOpen, setIsOpen] = useState(false);
// 获取当前语言优先使用props传入的语言
const [currentLang, setCurrentLang] = useState<Lang>(propLang || defaultLang);
// 在客户端运行时从HTML lang属性获取当前语言
useEffect(() => {
if (typeof document !== 'undefined') {
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== currentLang)) {
setCurrentLang(htmlLang);
}
}
}, [propLang, currentLang]);
const [selectedLanguage, setSelectedLanguage] = useState(() => {
return availableLanguages.find(l => l.code === initialLang) || availableLanguages.find(l => l.code === defaultLang) || availableLanguages[0];
return availableLanguages.find(l => l.code === currentLang) ||
availableLanguages.find(l => l.code === defaultLang) ||
availableLanguages[0];
});
useEffect(() => {
const currentLangObject = availableLanguages.find(l => l.code === initialLang);
const currentLangObject = availableLanguages.find(l => l.code === currentLang);
if (currentLangObject && currentLangObject.code !== selectedLanguage.code) {
setSelectedLanguage(currentLangObject);
}
}, [initialLang, selectedLanguage.code]);
}, [currentLang, selectedLanguage.code]);
const toggleOpen = () => setIsOpen(!isOpen);

View File

@@ -39,7 +39,7 @@ const lang = Astro.currentLocale as Lang || defaultLang;
<div class="absolute inset-0 bg-gradient-to-br from-purple-50/30 via-transparent to-blue-50/20 dark:from-purple-950/20 dark:via-transparent dark:to-blue-950/10"></div>
</div>
<!-- Glass Header with navigation -->
<GlassHeader lang={lang} client:load />
<GlassHeader client:load />
<!-- Main content with proper spacing for fixed header -->
<div class="pt-16">
@@ -47,7 +47,7 @@ const lang = Astro.currentLocale as Lang || defaultLang;
</div>
<!-- Footer -->
<Footer lang={lang} client:load />
<Footer client:load />
</body>
</html>

View File

@@ -1,6 +1,11 @@
---
import BlogLayout from '../../layouts/BlogLayout.astro';
import BlogList from '../../components/BlogList.tsx';
import { type Lang } from '@/i18n/utils';
import { defaultLang } from '@/i18n/ui';
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
// Sample blog data - inline for now
const blogPosts = [

View File

@@ -7,13 +7,14 @@ import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui";
import { personalInfo } from "@/lib/data";
const lang: Lang = defaultLang;
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = t('site.title');
---
<Layout title={pageTitle}>
<GlassHeader lang={lang} client:only="react" />
<GlassHeader client:only="react" />
<main class="min-h-screen">
<!-- Hero Section - Inlined Content -->
<section class="py-32 relative overflow-hidden min-h-screen flex items-center">

View File

@@ -5,13 +5,14 @@ import Footer from "@/components/Footer";
import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui";
const lang: Lang = defaultLang;
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = t('projects.title');
---
<Layout title={pageTitle}>
<GlassHeader lang={lang} client:only="react" />
<GlassHeader client:only="react" />
<main class="min-h-screen">
<!-- Projects Hero Section -->
<section class="py-24 relative overflow-hidden">

View File

@@ -1,6 +1,11 @@
---
import BlogLayout from '../../../layouts/BlogLayout.astro';
import BlogList from '../../../components/BlogList.tsx';
import { type Lang } from '@/i18n/utils';
import { defaultLang } from '@/i18n/ui';
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
// 示例博客数据 - 暂时内联
const blogPosts = [

View File

@@ -3,16 +3,18 @@ import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader.tsx";
import SkillsMarquee from "@/components/SkillsMarquee.tsx";
import Footer from "@/components/Footer.tsx";
import { useTranslations } from "@/i18n/utils";
import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui";
import { personalInfo } from "@/lib/data";
const lang: "en" | "zh" = 'zh';
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = t('site.title');
---
<Layout title={pageTitle}>
<GlassHeader lang={lang} client:only="react" />
<GlassHeader client:only="react" />
<main class="min-h-screen">
<!-- Hero Section - Inlined Content -->
<section class="py-32 relative overflow-hidden min-h-screen flex items-center">

View File

@@ -5,13 +5,14 @@ import Footer from "@/components/Footer";
import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui";
const lang: Lang = 'zh';
// 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang);
const pageTitle = t('projects.title');
---
<Layout title={pageTitle}>
<GlassHeader lang={lang} client:only="react" />
<GlassHeader client:only="react" />
<main class="min-h-screen">
<!-- Projects Hero Section -->
<section class="py-24 relative overflow-hidden">