返回 AiToEarn
layout.tsx
根目录 / project / aitoearn-web / src / app / [lng] / layout.tsx
1 import { dir } from 'i18next'
2 import { headers } from 'next/headers'
3 import Script from 'next/script'
4 import { useTranslation } from '@/app/i18n'
5 import { getHreflang } from '@/app/i18n/languageConfig'
6 import { fallbackLng, languages } from '@/app/i18n/settings'
7 import LayoutSidebar from '@/app/layout/LayoutSidebar'
8 import { MainContent } from '@/app/layout/MainContent'
9 import MobileNav from '@/app/layout/MobileNav'
10 import { ChannelManager } from '@/components/ChannelManager'
11 import { Providers } from '../layout/Providers'
12 import '../globals.css'
13
14 export const dynamic = 'force-dynamic'
15
16 export async function generateMetadata({ params }: { params: Promise<{ lng: string }> }) {
17 let { lng } = await params
18 if (!languages.includes(lng))
19 lng = fallbackLng
20 const { t } = await useTranslation(lng)
21
22 const headersList = await headers()
23 const host = headersList.get('host') || 'localhost:3000'
24 const proto = headersList.get('x-forwarded-proto') || 'https'
25 const baseUrl = `${proto}://${host}`
26
27 // 生成hreflang链接
28 const alternateRefs = languages.map(lang => ({
29 href: `${baseUrl}/${lang}`,
30 hreflang: getHreflang(lang),
31 }))
32
33 // 添加x-default
34 alternateRefs.push({
35 href: `${baseUrl}/en`,
36 hreflang: 'x-default',
37 })
38
39 return {
40 title: t('title'),
41 description: t('content'),
42 keywords: `aitoearn, AiToEarn, ai, earn, ${new URL(baseUrl).hostname}`,
43 alternates: {
44 languages: Object.fromEntries(alternateRefs.map(({ href, hreflang }) => [hreflang, href])),
45 },
46 }
47 }
48
49 export default async function RootLayout({
50 children,
51 params,
52 }: Readonly<{
53 children: React.ReactNode
54 params: Promise<{ lng: string }>
55 }>) {
56 const { lng } = await params
57 const autoLoginToken = process.env.AUTO_LOGIN_TOKEN?.trim() || undefined
58
59 return (
60 <html lang={lng} dir={dir(lng)} suppressHydrationWarning>
61 <head>
62 <meta name="msvalidate.01" content="FD5353C7C4A19D33CB0E8C7F0240B1F1" />
63 <meta
64 name="google-site-verification"
65 content="tc0EuxFIXvEW3lgie3jjqopDfYHc-Cw5MyZ93F91Wrg"
66 />
67 <meta
68 name="google-site-verification"
69 content="sPyHcKp5GBej4O1pTNzZsGRGRxBLZq1_3ZV-UrgTX6U"
70 />
71 </head>
72 <body suppressHydrationWarning>
73 {/* Rewardful 脚本 */}
74 <Script
75 id="rewardful-init"
76 strategy="afterInteractive"
77 dangerouslySetInnerHTML={{
78 __html: `(function(w,r){w._rwq=r;w[r]=w[r]||function(){(w[r].q=w[r].q||[]).push(arguments)}})(window,'rewardful');`,
79 }}
80 />
81 <Script src="https://r.wdfl.co/rw.js" data-rewardful="ded70f" strategy="afterInteractive" />
82 <Providers lng={lng} autoLoginToken={autoLoginToken}>
83 {/* 全局频道管理弹框 */}
84 <ChannelManager />
85 <p className="hidden">Impact-Site-Verification: f9836212-462a-482f-9232-8a877970eacf</p>
86 {/* 移动端顶部导航 - fixed 定位,独立于 flex 布局 */}
87 <MobileNav />
88 <div className="flex h-screen w-full">
89 {/* 桌面端侧边栏 */}
90 <LayoutSidebar />
91 {/* 主内容区域 - 根据页面类型动态控制 pt-14 */}
92 <MainContent>{children}</MainContent>
93 </div>
94 </Providers>
95 </body>
96 </html>
97 )
98 }
99
99 lines Plain Text