feat: implement internationalization support with next-intl and routing configuration
This commit is contained in:
parent
90e99c0c51
commit
31dbaf0261
@ -1,14 +1,18 @@
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
|
||||
export default withNextIntl(nextConfig);
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
"input-otp": "1.4.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "15.2.4",
|
||||
"next-intl": "^4.1.0",
|
||||
"next-themes": "latest",
|
||||
"react": "^19",
|
||||
"react-day-picker": "8.10.1",
|
||||
|
||||
109
pnpm-lock.yaml
generated
109
pnpm-lock.yaml
generated
@ -131,6 +131,9 @@ importers:
|
||||
next:
|
||||
specifier: 15.2.4
|
||||
version: 15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
next-intl:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(next@15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
|
||||
next-themes:
|
||||
specifier: latest
|
||||
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@ -221,6 +224,24 @@ packages:
|
||||
'@floating-ui/utils@0.2.9':
|
||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||
|
||||
'@formatjs/ecma402-abstract@2.3.4':
|
||||
resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==}
|
||||
|
||||
'@formatjs/fast-memoize@2.2.7':
|
||||
resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.11.2':
|
||||
resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==}
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.14':
|
||||
resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==}
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.10':
|
||||
resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
|
||||
|
||||
'@formatjs/intl-localematcher@0.6.1':
|
||||
resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==}
|
||||
|
||||
'@hookform/resolvers@3.10.0':
|
||||
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
|
||||
peerDependencies:
|
||||
@ -1082,6 +1103,9 @@ packages:
|
||||
'@radix-ui/rect@1.1.0':
|
||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||
|
||||
'@schummar/icu-type-parser@1.21.5':
|
||||
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
|
||||
|
||||
'@swc/counter@0.1.3':
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
|
||||
@ -1293,6 +1317,9 @@ packages:
|
||||
decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
|
||||
decimal.js@10.5.0:
|
||||
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
|
||||
|
||||
detect-libc@2.0.4:
|
||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -1421,6 +1448,9 @@ packages:
|
||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
intl-messageformat@10.7.16:
|
||||
resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==}
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||
|
||||
@ -1513,6 +1543,20 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
negotiator@1.0.0:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
next-intl@4.1.0:
|
||||
resolution: {integrity: sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw==}
|
||||
peerDependencies:
|
||||
next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
||||
typescript: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
next-themes@0.4.6:
|
||||
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||
peerDependencies:
|
||||
@ -1879,6 +1923,11 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-intl@4.1.0:
|
||||
resolution: {integrity: sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q==}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
||||
|
||||
use-sidecar@1.1.3:
|
||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -1961,6 +2010,36 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.9': {}
|
||||
|
||||
'@formatjs/ecma402-abstract@2.3.4':
|
||||
dependencies:
|
||||
'@formatjs/fast-memoize': 2.2.7
|
||||
'@formatjs/intl-localematcher': 0.6.1
|
||||
decimal.js: 10.5.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@formatjs/fast-memoize@2.2.7':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.11.2':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.3.4
|
||||
'@formatjs/icu-skeleton-parser': 1.8.14
|
||||
tslib: 2.8.1
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.14':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.3.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.10':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@formatjs/intl-localematcher@0.6.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@hookform/resolvers@3.10.0(react-hook-form@7.56.2(react@19.1.0))':
|
||||
dependencies:
|
||||
react-hook-form: 7.56.2(react@19.1.0)
|
||||
@ -2807,6 +2886,8 @@ snapshots:
|
||||
|
||||
'@radix-ui/rect@1.1.0': {}
|
||||
|
||||
'@schummar/icu-type-parser@1.21.5': {}
|
||||
|
||||
'@swc/counter@0.1.3': {}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
@ -3013,6 +3094,8 @@ snapshots:
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
|
||||
decimal.js@10.5.0: {}
|
||||
|
||||
detect-libc@2.0.4:
|
||||
optional: true
|
||||
|
||||
@ -3125,6 +3208,13 @@ snapshots:
|
||||
|
||||
internmap@2.0.3: {}
|
||||
|
||||
intl-messageformat@10.7.16:
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.3.4
|
||||
'@formatjs/fast-memoize': 2.2.7
|
||||
'@formatjs/icu-messageformat-parser': 2.11.2
|
||||
tslib: 2.8.1
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
optional: true
|
||||
|
||||
@ -3201,6 +3291,18 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
next-intl@4.1.0(next@15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.5.10
|
||||
negotiator: 1.0.0
|
||||
next: 15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react: 19.1.0
|
||||
use-intl: 4.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@ -3576,6 +3678,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.2
|
||||
|
||||
use-intl@4.1.0(react@19.1.0):
|
||||
dependencies:
|
||||
'@formatjs/fast-memoize': 2.2.7
|
||||
'@schummar/icu-type-parser': 1.21.5
|
||||
intl-messageformat: 10.7.16
|
||||
react: 19.1.0
|
||||
|
||||
use-sidecar@1.1.3(@types/react@19.1.2)(react@19.1.0):
|
||||
dependencies:
|
||||
detect-node-es: 1.1.0
|
||||
|
||||
44
src/app/[locale]/layout.tsx
Normal file
44
src/app/[locale]/layout.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import type React from "react"
|
||||
import "@/app/globals.css"
|
||||
import {ThemeProvider} from "@/components/theme-provider"
|
||||
import {NextIntlClientProvider} from 'next-intl';
|
||||
import {setRequestLocale} from 'next-intl/server';
|
||||
import {hasLocale} from 'next-intl';
|
||||
import {notFound} from 'next/navigation';
|
||||
import {getMessages} from 'next-intl/server';
|
||||
import {routing} from "@/i18n/routing";
|
||||
|
||||
export const metadata = {
|
||||
title: "grtsinry43 - 全栈开发者",
|
||||
description: "grtsinry43的个人网站,全栈开发者,专注于Java/JavaScript,并正在转向Kotlin/TypeScript全栈。",
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: { locale: string };
|
||||
}) {
|
||||
const messages = await getMessages();
|
||||
const {locale} = await params;
|
||||
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Enable static rendering
|
||||
setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<html lang={locale} suppressHydrationWarning>
|
||||
<body>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@ -24,8 +24,10 @@ import GsapPersonalIntro from "@/components/sections/personal-intro";
|
||||
import GsapPhotographySection from "@/components/sections/photography-section";
|
||||
import GsapRhythmGamesSection from "@/components/sections/rhythm-games-section";
|
||||
import FinalSection from "@/components/sections/final-section";
|
||||
import {useTranslations} from 'next-intl';
|
||||
|
||||
export default function HomePage() {
|
||||
const t = useTranslations('HomePage');
|
||||
const {theme} = useTheme()
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
const {scrollYProgress} = useScroll()
|
||||
@ -82,7 +84,7 @@ export default function HomePage() {
|
||||
>
|
||||
<img className="w-12 h-12 mr-4 rounded-full"
|
||||
src={"https://dogeoss.grtsinry43.com/img/author.jpeg"}/>
|
||||
<div>grtsinry43</div>
|
||||
<div>{t('greeting')}</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
@ -96,8 +98,8 @@ export default function HomePage() {
|
||||
<div className="container flex items-center justify-between h-16 px-4 mx-auto">
|
||||
<motion.div initial={{opacity: 0, y: -20}} animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5}}>
|
||||
<Link href="/" className="text-xl font-bold tracking-tight">
|
||||
grtsinry43
|
||||
<Link href="/public" className="text-xl font-bold tracking-tight">
|
||||
{t('title')}
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
@ -130,10 +132,10 @@ export default function HomePage() {
|
||||
{/* Horizontal scrolling text */}
|
||||
<section className="py-20 overflow-hidden opacity-55">
|
||||
<ParallaxText baseVelocity={-3}>
|
||||
Full Stack • Java • JavaScript • Kotlin • TypeScript • React • Next.js • Spring Boot
|
||||
{t('parallaxText1')}
|
||||
</ParallaxText>
|
||||
<ParallaxText baseVelocity={3}>
|
||||
Vue.js • Android • Jetpack Compose • WeChat Miniprogram • Arch Linux • 创新 • 探索 • 开发
|
||||
{t('parallaxText2')}
|
||||
</ParallaxText>
|
||||
</section>
|
||||
|
||||
@ -164,9 +166,9 @@ export default function HomePage() {
|
||||
className="mt-8 bg-gradient-to-br from-slate-300 to-slate-500 py-4
|
||||
font-bold bg-clip-text text-center text-3xl tracking-tight text-transparent md:text-5xl"
|
||||
>
|
||||
要看更多?
|
||||
{t('moreToSee')}
|
||||
<p className="mt-4">
|
||||
下面,继续看看我的个性和兴趣吧~
|
||||
{t('personalityAndInterests')}
|
||||
</p>
|
||||
</motion.h1>
|
||||
</LampContainer>
|
||||
@ -198,8 +200,7 @@ export default function HomePage() {
|
||||
whileInView={{opacity: 1}}
|
||||
transition={{duration: 0.8}}
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">© {new Date().getFullYear()} grtsinry43.
|
||||
保留所有权利。</p>
|
||||
<p className="text-sm text-muted-foreground">{t('footerRights', {year: new Date().getFullYear()})}</p>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="flex items-center space-x-6"
|
||||
@ -214,14 +215,14 @@ export default function HomePage() {
|
||||
className="text-muted-foreground hover:text-foreground transition-all duration-300 hover:scale-110"
|
||||
>
|
||||
<Github className="h-5 w-5"/>
|
||||
<span className="sr-only">GitHub</span>
|
||||
<span className="sr-only">{t('srGitHub')}</span>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:grtsinry43@outlook.com"
|
||||
className="text-muted-foreground hover:text-foreground transition-all duration-300 hover:scale-110"
|
||||
>
|
||||
<Mail className="h-5 w-5"/>
|
||||
<span className="sr-only">Email</span>
|
||||
<span className="sr-only">{t('srEmail')}</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://blog.grtsinry43.com"
|
||||
@ -230,7 +231,7 @@ export default function HomePage() {
|
||||
className="text-muted-foreground hover:text-foreground transition-all duration-300 hover:scale-110"
|
||||
>
|
||||
<ExternalLink className="h-5 w-5"/>
|
||||
<span className="sr-only">Blog</span>
|
||||
<span className="sr-only">{t('srBlog')}</span>
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
@ -1,24 +0,0 @@
|
||||
import type React from "react"
|
||||
import "@/app/globals.css"
|
||||
import {ThemeProvider} from "@/components/theme-provider"
|
||||
|
||||
export const metadata = {
|
||||
title: "grtsinry43 - 全栈开发者",
|
||||
description: "grtsinry43的个人网站,全栈开发者,专注于Java/JavaScript,并正在转向Kotlin/TypeScript全栈。",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="zh-CN" suppressHydrationWarning>
|
||||
<body>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
7
src/i18n/navigation.ts
Normal file
7
src/i18n/navigation.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import {createNavigation} from 'next-intl/navigation';
|
||||
import {routing} from './routing';
|
||||
|
||||
// Lightweight wrappers around Next.js' navigation
|
||||
// APIs that consider the routing configuration
|
||||
export const {Link, redirect, usePathname, useRouter, getPathname} =
|
||||
createNavigation(routing);
|
||||
16
src/i18n/request.ts
Normal file
16
src/i18n/request.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {getRequestConfig} from 'next-intl/server';
|
||||
import {hasLocale} from 'next-intl';
|
||||
import {routing} from './routing';
|
||||
|
||||
export default getRequestConfig(async ({requestLocale}) => {
|
||||
// Typically corresponds to the `[locale]` segment
|
||||
const requested = await requestLocale;
|
||||
const locale = hasLocale(routing.locales, requested)
|
||||
? requested
|
||||
: routing.defaultLocale;
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../../messages/${locale}.json`)).default
|
||||
};
|
||||
});
|
||||
7
src/i18n/routing.ts
Normal file
7
src/i18n/routing.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import {defineRouting} from 'next-intl/routing';
|
||||
|
||||
export const routing = defineRouting({
|
||||
locales: ['en', 'zh'],
|
||||
defaultLocale: 'zh',
|
||||
localePrefix: 'as-needed'
|
||||
});
|
||||
11
src/middleware.ts
Normal file
11
src/middleware.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import createMiddleware from 'next-intl/middleware';
|
||||
import {routing} from './i18n/routing';
|
||||
|
||||
export default createMiddleware(routing);
|
||||
|
||||
export const config = {
|
||||
// Match all pathnames except for
|
||||
// - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
|
||||
// - … the ones containing a dot (e.g. `favicon.ico`)
|
||||
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user