feat: implement internationalization support with next-intl and routing configuration
This commit is contained in:
parent
90e99c0c51
commit
31dbaf0261
@ -1,3 +1,5 @@
|
|||||||
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
eslint: {
|
eslint: {
|
||||||
@ -11,4 +13,6 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default nextConfig
|
const withNextIntl = createNextIntlPlugin();
|
||||||
|
|
||||||
|
export default withNextIntl(nextConfig);
|
||||||
|
|||||||
@ -50,6 +50,7 @@
|
|||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "15.2.4",
|
"next": "15.2.4",
|
||||||
|
"next-intl": "^4.1.0",
|
||||||
"next-themes": "latest",
|
"next-themes": "latest",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-day-picker": "8.10.1",
|
"react-day-picker": "8.10.1",
|
||||||
|
|||||||
109
pnpm-lock.yaml
generated
109
pnpm-lock.yaml
generated
@ -131,6 +131,9 @@ importers:
|
|||||||
next:
|
next:
|
||||||
specifier: 15.2.4
|
specifier: 15.2.4
|
||||||
version: 15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
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:
|
next-themes:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
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':
|
'@floating-ui/utils@0.2.9':
|
||||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
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':
|
'@hookform/resolvers@3.10.0':
|
||||||
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
|
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1082,6 +1103,9 @@ packages:
|
|||||||
'@radix-ui/rect@1.1.0':
|
'@radix-ui/rect@1.1.0':
|
||||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||||
|
|
||||||
|
'@schummar/icu-type-parser@1.21.5':
|
||||||
|
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
|
||||||
|
|
||||||
'@swc/counter@0.1.3':
|
'@swc/counter@0.1.3':
|
||||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||||
|
|
||||||
@ -1293,6 +1317,9 @@ packages:
|
|||||||
decimal.js-light@2.5.1:
|
decimal.js-light@2.5.1:
|
||||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||||
|
|
||||||
|
decimal.js@10.5.0:
|
||||||
|
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
|
||||||
|
|
||||||
detect-libc@2.0.4:
|
detect-libc@2.0.4:
|
||||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -1421,6 +1448,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
intl-messageformat@10.7.16:
|
||||||
|
resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==}
|
||||||
|
|
||||||
is-arrayish@0.3.2:
|
is-arrayish@0.3.2:
|
||||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||||
|
|
||||||
@ -1513,6 +1543,20 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
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:
|
next-themes@0.4.6:
|
||||||
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1879,6 +1923,11 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
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:
|
use-sidecar@1.1.3:
|
||||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -1961,6 +2010,36 @@ snapshots:
|
|||||||
|
|
||||||
'@floating-ui/utils@0.2.9': {}
|
'@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))':
|
'@hookform/resolvers@3.10.0(react-hook-form@7.56.2(react@19.1.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
react-hook-form: 7.56.2(react@19.1.0)
|
react-hook-form: 7.56.2(react@19.1.0)
|
||||||
@ -2807,6 +2886,8 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/rect@1.1.0': {}
|
'@radix-ui/rect@1.1.0': {}
|
||||||
|
|
||||||
|
'@schummar/icu-type-parser@1.21.5': {}
|
||||||
|
|
||||||
'@swc/counter@0.1.3': {}
|
'@swc/counter@0.1.3': {}
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
@ -3013,6 +3094,8 @@ snapshots:
|
|||||||
|
|
||||||
decimal.js-light@2.5.1: {}
|
decimal.js-light@2.5.1: {}
|
||||||
|
|
||||||
|
decimal.js@10.5.0: {}
|
||||||
|
|
||||||
detect-libc@2.0.4:
|
detect-libc@2.0.4:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -3125,6 +3208,13 @@ snapshots:
|
|||||||
|
|
||||||
internmap@2.0.3: {}
|
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:
|
is-arrayish@0.3.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -3201,6 +3291,18 @@ snapshots:
|
|||||||
|
|
||||||
nanoid@3.3.11: {}
|
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):
|
next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
@ -3576,6 +3678,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@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):
|
use-sidecar@1.1.3(@types/react@19.1.2)(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-node-es: 1.1.0
|
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 GsapPhotographySection from "@/components/sections/photography-section";
|
||||||
import GsapRhythmGamesSection from "@/components/sections/rhythm-games-section";
|
import GsapRhythmGamesSection from "@/components/sections/rhythm-games-section";
|
||||||
import FinalSection from "@/components/sections/final-section";
|
import FinalSection from "@/components/sections/final-section";
|
||||||
|
import {useTranslations} from 'next-intl';
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
const t = useTranslations('HomePage');
|
||||||
const {theme} = useTheme()
|
const {theme} = useTheme()
|
||||||
const [scrolled, setScrolled] = useState(false)
|
const [scrolled, setScrolled] = useState(false)
|
||||||
const {scrollYProgress} = useScroll()
|
const {scrollYProgress} = useScroll()
|
||||||
@ -82,7 +84,7 @@ export default function HomePage() {
|
|||||||
>
|
>
|
||||||
<img className="w-12 h-12 mr-4 rounded-full"
|
<img className="w-12 h-12 mr-4 rounded-full"
|
||||||
src={"https://dogeoss.grtsinry43.com/img/author.jpeg"}/>
|
src={"https://dogeoss.grtsinry43.com/img/author.jpeg"}/>
|
||||||
<div>grtsinry43</div>
|
<div>{t('greeting')}</div>
|
||||||
</motion.div>
|
</motion.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">
|
<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}}
|
<motion.div initial={{opacity: 0, y: -20}} animate={{opacity: 1, y: 0}}
|
||||||
transition={{duration: 0.5}}>
|
transition={{duration: 0.5}}>
|
||||||
<Link href="/" className="text-xl font-bold tracking-tight">
|
<Link href="/public" className="text-xl font-bold tracking-tight">
|
||||||
grtsinry43
|
{t('title')}
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -130,10 +132,10 @@ export default function HomePage() {
|
|||||||
{/* Horizontal scrolling text */}
|
{/* Horizontal scrolling text */}
|
||||||
<section className="py-20 overflow-hidden opacity-55">
|
<section className="py-20 overflow-hidden opacity-55">
|
||||||
<ParallaxText baseVelocity={-3}>
|
<ParallaxText baseVelocity={-3}>
|
||||||
Full Stack • Java • JavaScript • Kotlin • TypeScript • React • Next.js • Spring Boot
|
{t('parallaxText1')}
|
||||||
</ParallaxText>
|
</ParallaxText>
|
||||||
<ParallaxText baseVelocity={3}>
|
<ParallaxText baseVelocity={3}>
|
||||||
Vue.js • Android • Jetpack Compose • WeChat Miniprogram • Arch Linux • 创新 • 探索 • 开发
|
{t('parallaxText2')}
|
||||||
</ParallaxText>
|
</ParallaxText>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -164,9 +166,9 @@ export default function HomePage() {
|
|||||||
className="mt-8 bg-gradient-to-br from-slate-300 to-slate-500 py-4
|
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"
|
font-bold bg-clip-text text-center text-3xl tracking-tight text-transparent md:text-5xl"
|
||||||
>
|
>
|
||||||
要看更多?
|
{t('moreToSee')}
|
||||||
<p className="mt-4">
|
<p className="mt-4">
|
||||||
下面,继续看看我的个性和兴趣吧~
|
{t('personalityAndInterests')}
|
||||||
</p>
|
</p>
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
</LampContainer>
|
</LampContainer>
|
||||||
@ -198,8 +200,7 @@ export default function HomePage() {
|
|||||||
whileInView={{opacity: 1}}
|
whileInView={{opacity: 1}}
|
||||||
transition={{duration: 0.8}}
|
transition={{duration: 0.8}}
|
||||||
>
|
>
|
||||||
<p className="text-sm text-muted-foreground">© {new Date().getFullYear()} grtsinry43.
|
<p className="text-sm text-muted-foreground">{t('footerRights', {year: new Date().getFullYear()})}</p>
|
||||||
保留所有权利。</p>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex items-center space-x-6"
|
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"
|
className="text-muted-foreground hover:text-foreground transition-all duration-300 hover:scale-110"
|
||||||
>
|
>
|
||||||
<Github className="h-5 w-5"/>
|
<Github className="h-5 w-5"/>
|
||||||
<span className="sr-only">GitHub</span>
|
<span className="sr-only">{t('srGitHub')}</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="mailto:grtsinry43@outlook.com"
|
href="mailto:grtsinry43@outlook.com"
|
||||||
className="text-muted-foreground hover:text-foreground transition-all duration-300 hover:scale-110"
|
className="text-muted-foreground hover:text-foreground transition-all duration-300 hover:scale-110"
|
||||||
>
|
>
|
||||||
<Mail className="h-5 w-5"/>
|
<Mail className="h-5 w-5"/>
|
||||||
<span className="sr-only">Email</span>
|
<span className="sr-only">{t('srEmail')}</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://blog.grtsinry43.com"
|
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"
|
className="text-muted-foreground hover:text-foreground transition-all duration-300 hover:scale-110"
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-5 w-5"/>
|
<ExternalLink className="h-5 w-5"/>
|
||||||
<span className="sr-only">Blog</span>
|
<span className="sr-only">{t('srBlog')}</span>
|
||||||
</a>
|
</a>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</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