diff --git a/app/about/page.tsx b/app/about/page.tsx index 561f060..bf6fa4d 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -1,6 +1,11 @@ +"use client"; + import Link from "next/link"; +import { useLocale } from "~/components/locale-provider"; export default function AboutPage() { + const { t } = useLocale(); + return (
-

关于 Nomadro

+

{t.about.title}

- Nomadro 是中文数字游民操作系统,提供游民导航、地理套利、直播工具箱等模块, - 帮助更多人从固定工位走向自由生活与自由收入。 + {t.about.body}

- ← 返回首页 + {t.about.backHome}
diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 84cc2e4..68b6a30 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -1,6 +1,11 @@ +"use client"; + import Link from "next/link"; +import { useLocale } from "~/components/locale-provider"; export default function ContactPage() { + const { t } = useLocale(); + return (
-

联系我们

+

{t.contact.title}

- 加入数字游民社群,获取最新动态与交流机会。 + {t.contact.body}

- 🎬 直播工具箱 + {t.contact.liveCta} - 👑 会员社群 + {t.contact.vipCta}
- ← 返回首页 + {t.contact.backHome}
diff --git a/app/layout.tsx b/app/layout.tsx index 33d858c..bbb5491 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import "./globals.css"; import { type Metadata, type Viewport } from "next"; import { Geist, Geist_Mono } from "next/font/google"; +import { LocaleProvider } from "~/components/locale-provider"; export const viewport: Viewport = { width: "device-width", @@ -52,7 +53,7 @@ export default function RootLayout({ return ( - {children} + {children} ); diff --git a/app/page.tsx b/app/page.tsx index 4b05990..de96e07 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,12 @@ +"use client"; + import { Hero } from "~/components/sections/hero"; +import { useLocale } from "~/components/locale-provider"; export default function HomePage() { + const { t } = useLocale(); + const year = new Date().getFullYear(); + return (
- © {new Date().getFullYear()} Nomadro + {t.copyright.replace("{year}", String(year))}
); diff --git a/components/locale-provider.tsx b/components/locale-provider.tsx new file mode 100644 index 0000000..15347e0 --- /dev/null +++ b/components/locale-provider.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { + createContext, + useContext, + useEffect, + useMemo, + useState, + type ReactNode, +} from "react"; +import { isChinaUser } from "is-china-user"; +import { dictionaries, type Dictionary, type Locale } from "~/lib/i18n"; + +type LocaleContextValue = { + locale: Locale; + isChina: boolean; + ready: boolean; + t: Dictionary; +}; + +const LocaleContext = createContext(null); + +export function LocaleProvider({ children }: { children: ReactNode }) { + // Default zh to match primary audience; switch after client detection. + const [locale, setLocale] = useState("zh"); + const [ready, setReady] = useState(false); + + useEffect(() => { + let cancelled = false; + + try { + const china = Boolean(isChinaUser()); + if (!cancelled) { + setLocale(china ? "zh" : "en"); + setReady(true); + } + } catch { + if (!cancelled) { + setLocale("zh"); + setReady(true); + } + } + + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + if (!ready) return; + document.documentElement.lang = dictionaries[locale].htmlLang; + }, [locale, ready]); + + const value = useMemo( + () => ({ + locale, + isChina: locale === "zh", + ready, + t: dictionaries[locale], + }), + [locale, ready], + ); + + return ( + {children} + ); +} + +export function useLocale() { + const ctx = useContext(LocaleContext); + if (!ctx) { + throw new Error("useLocale must be used within LocaleProvider"); + } + return ctx; +} diff --git a/components/sections/hero.tsx b/components/sections/hero.tsx index f475ce5..4d51440 100644 --- a/components/sections/hero.tsx +++ b/components/sections/hero.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { ChevronRight } from "lucide-react"; import { ENTRY_CARDS } from "~/lib/data"; +import { useLocale } from "~/components/locale-provider"; const CARD_STYLE = { blue: { @@ -23,6 +24,8 @@ const CARD_STYLE = { } as const; export function Hero() { + const { t } = useLocale(); + return (
- {/* 标题 */}

- 数字游民 + {t.brandSubtitle}

nomadro

- 游民导航 · 地理套利 · 直播工具箱 + {t.heroTagline}
- 一站开启自由生活 + {t.heroTaglineLine2}

- {/* 入口卡片 */}
{ENTRY_CARDS.map((card) => { const style = CARD_STYLE[card.accent]; + const copy = t.entryCards[card.id] ?? { + label: card.label, + title: card.title, + description: card.description, + }; + return (

- {card.label} + {copy.label}

{card.id === "live" && ( - LIVE + {t.liveBadge} )}

- {card.title} + {copy.title}

- {card.description} + {copy.description}

diff --git a/lib/i18n.ts b/lib/i18n.ts new file mode 100644 index 0000000..f857cfc --- /dev/null +++ b/lib/i18n.ts @@ -0,0 +1,106 @@ +export type Locale = "zh" | "en"; + +export type EntryCardCopy = { + label: string; + title: string; + description: string; +}; + +export type Dictionary = { + htmlLang: string; + brandSubtitle: string; + heroTagline: string; + heroTaglineLine2: string; + copyright: string; + liveBadge: string; + entryCards: Record; + about: { + title: string; + body: string; + backHome: string; + }; + contact: { + title: string; + body: string; + liveCta: string; + vipCta: string; + backHome: string; + }; +}; + +export const dictionaries: Record = { + zh: { + htmlLang: "zh-CN", + brandSubtitle: "数字游民", + heroTagline: "游民导航 · 地理套利 · 直播工具箱", + heroTaglineLine2: "一站开启自由生活", + copyright: "© {year} Nomadro", + liveBadge: "LIVE", + entryCards: { + nav: { + label: "游民导航", + title: "出海赚美金", + description: "资源导航、工具推荐、出海指南", + }, + arbitrage: { + label: "地理套利", + title: "AI 自动化杠杆", + description: "低成本工作室、自建云手机、自动化系统", + }, + live: { + label: "直播工具箱", + title: "随时随地开播", + description: "推流工具、场景模板、直播变现一站搞定", + }, + }, + about: { + title: "关于 Nomadro", + body: "Nomadro 是中文数字游民操作系统,提供游民导航、地理套利、直播工具箱等模块,帮助更多人从固定工位走向自由生活与自由收入。", + backHome: "← 返回首页", + }, + contact: { + title: "联系我们", + body: "加入数字游民社群,获取最新动态与交流机会。", + liveCta: "🎬 直播工具箱", + vipCta: "👑 会员社群", + backHome: "← 返回首页", + }, + }, + en: { + htmlLang: "en", + brandSubtitle: "Digital Nomad", + heroTagline: "Nomad Nav · Geo Arbitrage · Live Toolkit", + heroTaglineLine2: "Your freedom lifestyle, in one place", + copyright: "© {year} Nomadro", + liveBadge: "LIVE", + entryCards: { + nav: { + label: "Nomad Nav", + title: "Earn in USD overseas", + description: "Resource maps, tool picks, and go-abroad guides", + }, + arbitrage: { + label: "Geo Arbitrage", + title: "AI automation leverage", + description: "Low-cost studios, self-hosted cloud phones, automation systems", + }, + live: { + label: "Live Toolkit", + title: "Go live anywhere", + description: "Streaming tools, scene templates, and monetization in one place", + }, + }, + about: { + title: "About Nomadro", + body: "Nomadro is a digital nomad operating system for Chinese creators—covering nomad navigation, geo-arbitrage, and a live streaming toolkit—to help more people move from a fixed desk to freedom of life and income.", + backHome: "← Back to home", + }, + contact: { + title: "Contact us", + body: "Join the digital nomad community for updates and connections.", + liveCta: "🎬 Live Toolkit", + vipCta: "👑 Members community", + backHome: "← Back to home", + }, + }, +}; diff --git a/package.json b/package.json index 5b7000b..bc07480 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "framer-motion": "^12.36.0", + "is-china-user": "^1.7.0", "lucide-react": "^0.577.0", "next": "16.1.6", "react": "19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9818417..12b5c80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: framer-motion: specifier: ^12.36.0 version: 12.36.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + is-china-user: + specifier: ^1.7.0 + version: 1.7.0 lucide-react: specifier: ^0.577.0 version: 0.577.0(react@19.2.3) @@ -1254,6 +1257,9 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} + is-china-user@1.7.0: + resolution: {integrity: sha512-qVhmz+TwK8+WWtEILYZd9tgt7oNsVj0T6VaMqiUlGOltZ64/HTJ4iszNu2KBRM5marxN9BNiMcJjRENsJau6Aw==} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -3284,6 +3290,8 @@ snapshots: is-callable@1.2.7: {} + is-china-user@1.7.0: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2