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:
@@ -1,12 +1,25 @@
|
|||||||
import { useTranslations, type Lang } from "@/i18n/utils";
|
import { useTranslations, type Lang } from "@/i18n/utils";
|
||||||
import { personalInfo } from "@/lib/data";
|
import { personalInfo } from "@/lib/data";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { defaultLang } from "@/i18n/ui";
|
||||||
|
|
||||||
interface FooterProps {
|
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);
|
const t = useTranslations(lang);
|
||||||
return (
|
return (
|
||||||
<footer className="border-t border-purple-500/10 py-6 bg-gradient-to-b from-background to-muted/20 backdrop-blur-sm">
|
<footer className="border-t border-purple-500/10 py-6 bg-gradient-to-b from-background to-muted/20 backdrop-blur-sm">
|
||||||
|
|||||||
@@ -4,12 +4,25 @@ import ThemeToggle from "./ui/theme-toggle";
|
|||||||
import { useTranslations, getLocalizedPath, type Lang } from "@/i18n/utils";
|
import { useTranslations, getLocalizedPath, type Lang } from "@/i18n/utils";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Menu, X } from "lucide-react";
|
import { Menu, X } from "lucide-react";
|
||||||
|
import { defaultLang } from "@/i18n/ui";
|
||||||
|
|
||||||
|
// 从window.document.documentElement.lang获取当前语言
|
||||||
interface GlassHeaderProps {
|
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 t = useTranslations(lang);
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
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 as i18nLanguages, defaultLang } from "@/i18n/ui";
|
||||||
import { Languages, Check, ChevronDown } from "lucide-react";
|
import { Languages, Check, ChevronDown } from "lucide-react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
@@ -7,26 +7,41 @@ import { motion, AnimatePresence } from "framer-motion";
|
|||||||
const availableLanguages = Object.entries(i18nLanguages).map(([code, name]) => ({
|
const availableLanguages = Object.entries(i18nLanguages).map(([code, name]) => ({
|
||||||
code: code as Lang,
|
code: code as Lang,
|
||||||
name,
|
name,
|
||||||
// You can add icons here if you have a mapping or a more complex structure in ui.ts
|
// 语言图标映射
|
||||||
icon: code === 'en' ? '🇬🇧' : code === 'zh' ? '🇨🇳' : '🌐'
|
icon: code === 'en' ? '🇬🇧' : code === 'zh' ? '🇨🇳' : '🌐'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface LanguageSwitcherProps {
|
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);
|
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(() => {
|
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(() => {
|
useEffect(() => {
|
||||||
const currentLangObject = availableLanguages.find(l => l.code === initialLang);
|
const currentLangObject = availableLanguages.find(l => l.code === currentLang);
|
||||||
if (currentLangObject && currentLangObject.code !== selectedLanguage.code) {
|
if (currentLangObject && currentLangObject.code !== selectedLanguage.code) {
|
||||||
setSelectedLanguage(currentLangObject);
|
setSelectedLanguage(currentLangObject);
|
||||||
}
|
}
|
||||||
}, [initialLang, selectedLanguage.code]);
|
}, [currentLang, selectedLanguage.code]);
|
||||||
|
|
||||||
const toggleOpen = () => setIsOpen(!isOpen);
|
const toggleOpen = () => setIsOpen(!isOpen);
|
||||||
|
|
||||||
|
|||||||
@@ -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 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>
|
</div>
|
||||||
<!-- Glass Header with navigation -->
|
<!-- Glass Header with navigation -->
|
||||||
<GlassHeader lang={lang} client:load />
|
<GlassHeader client:load />
|
||||||
|
|
||||||
<!-- Main content with proper spacing for fixed header -->
|
<!-- Main content with proper spacing for fixed header -->
|
||||||
<div class="pt-16">
|
<div class="pt-16">
|
||||||
@@ -47,7 +47,7 @@ const lang = Astro.currentLocale as Lang || defaultLang;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<Footer lang={lang} client:load />
|
<Footer client:load />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BlogLayout from '../../layouts/BlogLayout.astro';
|
import BlogLayout from '../../layouts/BlogLayout.astro';
|
||||||
import BlogList from '../../components/BlogList.tsx';
|
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
|
// Sample blog data - inline for now
|
||||||
const blogPosts = [
|
const blogPosts = [
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import { useTranslations, type Lang } from "@/i18n/utils";
|
|||||||
import { defaultLang } from "@/i18n/ui";
|
import { defaultLang } from "@/i18n/ui";
|
||||||
import { personalInfo } from "@/lib/data";
|
import { personalInfo } from "@/lib/data";
|
||||||
|
|
||||||
const lang: Lang = defaultLang;
|
// 使用Astro.currentLocale获取当前语言环境
|
||||||
|
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||||
const t = useTranslations(lang);
|
const t = useTranslations(lang);
|
||||||
const pageTitle = t('site.title');
|
const pageTitle = t('site.title');
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={pageTitle}>
|
<Layout title={pageTitle}>
|
||||||
<GlassHeader lang={lang} client:only="react" />
|
<GlassHeader client:only="react" />
|
||||||
<main class="min-h-screen">
|
<main class="min-h-screen">
|
||||||
<!-- Hero Section - Inlined Content -->
|
<!-- Hero Section - Inlined Content -->
|
||||||
<section class="py-32 relative overflow-hidden min-h-screen flex items-center">
|
<section class="py-32 relative overflow-hidden min-h-screen flex items-center">
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import Footer from "@/components/Footer";
|
|||||||
import { useTranslations, type Lang } from "@/i18n/utils";
|
import { useTranslations, type Lang } from "@/i18n/utils";
|
||||||
import { defaultLang } from "@/i18n/ui";
|
import { defaultLang } from "@/i18n/ui";
|
||||||
|
|
||||||
const lang: Lang = defaultLang;
|
// 使用Astro.currentLocale获取当前语言环境
|
||||||
|
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||||
const t = useTranslations(lang);
|
const t = useTranslations(lang);
|
||||||
const pageTitle = t('projects.title');
|
const pageTitle = t('projects.title');
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={pageTitle}>
|
<Layout title={pageTitle}>
|
||||||
<GlassHeader lang={lang} client:only="react" />
|
<GlassHeader client:only="react" />
|
||||||
<main class="min-h-screen">
|
<main class="min-h-screen">
|
||||||
<!-- Projects Hero Section -->
|
<!-- Projects Hero Section -->
|
||||||
<section class="py-24 relative overflow-hidden">
|
<section class="py-24 relative overflow-hidden">
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BlogLayout from '../../../layouts/BlogLayout.astro';
|
import BlogLayout from '../../../layouts/BlogLayout.astro';
|
||||||
import BlogList from '../../../components/BlogList.tsx';
|
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 = [
|
const blogPosts = [
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import Layout from "@/layouts/Layout.astro";
|
|||||||
import GlassHeader from "@/components/GlassHeader.tsx";
|
import GlassHeader from "@/components/GlassHeader.tsx";
|
||||||
import SkillsMarquee from "@/components/SkillsMarquee.tsx";
|
import SkillsMarquee from "@/components/SkillsMarquee.tsx";
|
||||||
import Footer from "@/components/Footer.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";
|
import { personalInfo } from "@/lib/data";
|
||||||
|
|
||||||
const lang: "en" | "zh" = 'zh';
|
// 使用Astro.currentLocale获取当前语言环境
|
||||||
|
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||||
const t = useTranslations(lang);
|
const t = useTranslations(lang);
|
||||||
const pageTitle = t('site.title');
|
const pageTitle = t('site.title');
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={pageTitle}>
|
<Layout title={pageTitle}>
|
||||||
<GlassHeader lang={lang} client:only="react" />
|
<GlassHeader client:only="react" />
|
||||||
<main class="min-h-screen">
|
<main class="min-h-screen">
|
||||||
<!-- Hero Section - Inlined Content -->
|
<!-- Hero Section - Inlined Content -->
|
||||||
<section class="py-32 relative overflow-hidden min-h-screen flex items-center">
|
<section class="py-32 relative overflow-hidden min-h-screen flex items-center">
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import Footer from "@/components/Footer";
|
|||||||
import { useTranslations, type Lang } from "@/i18n/utils";
|
import { useTranslations, type Lang } from "@/i18n/utils";
|
||||||
import { defaultLang } from "@/i18n/ui";
|
import { defaultLang } from "@/i18n/ui";
|
||||||
|
|
||||||
const lang: Lang = 'zh';
|
// 使用Astro.currentLocale获取当前语言环境
|
||||||
|
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||||
const t = useTranslations(lang);
|
const t = useTranslations(lang);
|
||||||
const pageTitle = t('projects.title');
|
const pageTitle = t('projects.title');
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={pageTitle}>
|
<Layout title={pageTitle}>
|
||||||
<GlassHeader lang={lang} client:only="react" />
|
<GlassHeader client:only="react" />
|
||||||
<main class="min-h-screen">
|
<main class="min-h-screen">
|
||||||
<!-- Projects Hero Section -->
|
<!-- Projects Hero Section -->
|
||||||
<section class="py-24 relative overflow-hidden">
|
<section class="py-24 relative overflow-hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user