feat(i18n): implement astro i18n integration and refactor locale handling

- Add i18n configuration to astro.config.mjs with default locale and routing
- Refactor language handling to use Astro.currentLocale instead of URL parsing
- Update tsconfig to include only necessary files for better type checking
- Improve LanguageSwitcher to handle routing based on astro i18n config
- Add new translation keys and update components to use dynamic titles
- Simplify MotionWrapper component by removing unused default animations
This commit is contained in:
joyzhao
2025-06-15 17:20:29 +08:00
parent 22799c9d8a
commit 1476f4eeec
9 changed files with 110 additions and 53 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { getLangFromUrl, getLocalizedPath, type Lang } from "@/i18n/utils";
import { getLocalizedPath, type Lang } from "@/i18n/utils"; // getLangFromUrl is removed as Astro.currentLocale is used now.
import { languages as i18nLanguages, defaultLang } from "@/i18n/ui";
import { Languages, Check } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
@@ -30,12 +30,41 @@ export default function LanguageSwitcher({ lang: initialLang }: LanguageSwitcher
const toggleOpen = () => setIsOpen(!isOpen);
const handleSelectLanguage = (lang: typeof availableLanguages[0]) => {
setSelectedLanguage(lang);
const handleSelectLanguage = (languageOpt: typeof availableLanguages[0]) => {
setSelectedLanguage(languageOpt);
setIsOpen(false);
if (typeof window !== 'undefined') {
const currentPath = window.location.pathname.replace(/\/en|\/zh/, ''); // Remove lang prefix
const newPath = getLocalizedPath(currentPath || '/', lang.code);
const currentPathname = window.location.pathname;
const currentPathParts = currentPathname.split('/').filter(p => p);
let basePath = '';
// Check if the first part of the path is a known language code
if (currentPathParts.length > 0 && Object.keys(i18nLanguages).includes(currentPathParts[0])) {
basePath = '/' + currentPathParts.slice(1).join('/');
} else {
basePath = currentPathname;
}
// Ensure basePath always starts with a slash, or is just a slash for the root
if (!basePath.startsWith('/')) {
basePath = '/' + basePath;
}
if (basePath === '//') basePath = '/';
let newPath;
// If the target language is the default language and prefixDefaultLocale is false (as per our astro.config.mjs)
// then no language prefix is needed.
if (languageOpt.code === defaultLang) {
newPath = basePath;
} else {
// For non-default languages, prefix with the language code.
newPath = `/${languageOpt.code}${basePath}`;
}
// Clean up double slashes, just in case
newPath = newPath.replace(/\/\/+/g, '/');
if (newPath === '') newPath = '/'; // Handle case where basePath might be empty resulting in just /zh or /en
window.location.href = newPath;
}
};

View File

@@ -2,25 +2,11 @@ import React from "react";
import { motion } from "framer-motion";
import type { MotionProps } from "framer-motion";
interface MotionWrapperProps extends MotionProps {
interface MotionWrapperProps extends Omit<MotionProps, 'custom'> {
children: React.ReactNode;
delay?: number;
}
// Default animations for sections
const defaultAnimations = {
hidden: { opacity: 0, y: 20 },
visible: (delay: number = 0) => ({
opacity: 1,
y: 0,
transition: {
duration: 0.6,
delay: delay,
ease: "easeOut",
},
}),
};
export default function MotionWrapper({
children,
delay = 0,
@@ -31,7 +17,18 @@ export default function MotionWrapper({
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={defaultAnimations}
variants={{
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
delay,
ease: [0.43, 0.13, 0.23, 0.96]
}
}
}}
custom={delay}
{...props}
>