| 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 |