diff --git a/package-lock.json b/package-lock.json
index 1d6a839..372346f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,13 +8,14 @@
"name": "qczl-website",
"version": "0.0.0",
"dependencies": {
- "@radix-ui/react-slot": "^1.2.4",
- "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"geist": "^1.7.0",
+ "i18next": "^26.0.8",
+ "i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.503.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-i18next": "^17.0.6",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {
@@ -261,6 +262,15 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz",
@@ -1487,39 +1497,6 @@
"node": ">= 10"
}
},
- "node_modules/@radix-ui/react-compose-refs": {
- "version": "1.1.2",
- "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
- "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-slot": {
- "version": "1.2.4",
- "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
- "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -2226,7 +2203,7 @@
"version": "19.2.14",
"resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -2329,18 +2306,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/class-variance-authority": {
- "version": "0.7.1",
- "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
- "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
- "license": "Apache-2.0",
- "dependencies": {
- "clsx": "^2.1.1"
- },
- "funding": {
- "url": "https://polar.sh/cva"
- }
- },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz",
@@ -2368,7 +2333,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/debug": {
@@ -2531,6 +2496,52 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/i18next": {
+ "version": "26.0.8",
+ "resolved": "https://registry.npmmirror.com/i18next/-/i18next-26.0.8.tgz",
+ "integrity": "sha512-BRzLom0mhDhV9v0QhgUUHWQJuwFmnr1194xEcNLYD6ym8y8s542n4jXUvRLnhNTbh9PmpU6kGZamyuGHQMsGjw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://www.locize.com/i18next"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.locize.com"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
+ "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
@@ -3048,6 +3059,33 @@
"react": "^19.2.5"
}
},
+ "node_modules/react-i18next": {
+ "version": "17.0.6",
+ "resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-17.0.6.tgz",
+ "integrity": "sha512-WzJ6SMKF+GTD7JZZqxSR1AKKmXjaSu39sClUrNlwxS4Tl7a99O+ltFy6yhPMO+wgZuxpQjJ2PZkfrQKmAqrLhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.29.2",
+ "html-parse-stringify": "^3.0.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "i18next": ">= 26.0.1",
+ "react": ">= 16.8.0",
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -3270,7 +3308,7 @@
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -3311,6 +3349,15 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/vite": {
"version": "6.4.2",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.2.tgz",
@@ -3386,6 +3433,15 @@
}
}
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index 4b1900e..8fd79c4 100644
--- a/package.json
+++ b/package.json
@@ -9,13 +9,14 @@
"preview": "vite preview"
},
"dependencies": {
- "@radix-ui/react-slot": "^1.2.4",
- "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"geist": "^1.7.0",
+ "i18next": "^26.0.8",
+ "i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.503.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-i18next": "^17.0.6",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {
diff --git a/src/App.tsx b/src/App.tsx
index 333396f..6db0bca 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,3 +1,5 @@
+import { useEffect } from "react"
+import { useTranslation } from "react-i18next"
import { Navbar } from "./components/sections/navbar"
import { Hero } from "./components/sections/hero"
import { About } from "./components/sections/about"
@@ -6,6 +8,15 @@ import { Team } from "./components/sections/team"
import { Footer } from "./components/sections/footer"
function App() {
+ const { t, i18n } = useTranslation()
+
+ useEffect(() => {
+ document.title = `${t("site.title")} - ${t("site.subtitle")}`
+ document
+ .querySelector('meta[name="description"]')
+ ?.setAttribute("content", `${t("site.title")} - ${t("site.subtitle")}`)
+ }, [t, i18n.language])
+
return (
diff --git a/src/components/sections/about.tsx b/src/components/sections/about.tsx
index 76803ba..d93dc7e 100644
--- a/src/components/sections/about.tsx
+++ b/src/components/sections/about.tsx
@@ -1,53 +1,42 @@
+import { useTranslation } from "react-i18next"
import { Zap, Shield, Code2, Rocket, Eye, Heart, Bot, Gauge, Layers, MessageSquare } from "lucide-react"
-const stats = [
- { label: "projects shipped", value: "100+", desc: "交付项目" },
- { label: "years building", value: "5+", desc: "持续开发" },
- { label: "client retention", value: "95%", desc: "客户留存" },
-]
-
-const advantages = [
- { icon: Bot, label: "AI 驱动", desc: "深度整合 AI 工具链,开发效率提升数倍" },
- { icon: Gauge, label: "快速交付", desc: "两周一个迭代,持续交付可运行的成果" },
- { icon: Layers, label: "全栈覆盖", desc: "前端、后端、移动端、基础设施一个团队搞定" },
- { icon: MessageSquare, label: "直接沟通", desc: "你直接和写代码的人对话,需求零损耗" },
-]
-
-const principles = [
- { icon: Code2, title: "Clean Code", desc: "代码即文档。我们写人类可读的代码。" },
- { icon: Zap, title: "Ship Fast", desc: "两周一个迭代,快速验证想法。" },
- { icon: Shield, title: "Own It", desc: "全栈负责到底。从需求到部署。" },
- { icon: Eye, title: "Transparency", desc: "进度完全透明,随时可查看代码和部署。" },
- { icon: Heart, title: "Craftsmanship", desc: "把每个项目当作自己的作品。" },
- { icon: Rocket, title: "Scale Ready", desc: "架构为增长设计,今天的小项目明天的大平台。" },
-]
+const principleIcons = [Code2, Zap, Shield, Eye, Heart, Rocket] as const
+const advantageIcons = [Bot, Gauge, Layers, MessageSquare] as const
export function About() {
+ const { t } = useTranslation()
+
+ type StatItem = { value: string; label: string; desc: string }
+ type AdvantageItem = { title: string; desc: string }
+ type PrincipleItem = { title: string; desc: string }
+
+ const stats = t("about.stats", { returnObjects: true }) as StatItem[]
+ const advantages = t("about.whyUs.items", { returnObjects: true }) as AdvantageItem[]
+ const principles = t("about.principles.items", { returnObjects: true }) as PrincipleItem[]
+
return (
-
+
- {/* Section header */}
-
about us
+
{t("about.label")}
- 不是外包公司
+ {t("about.title")}
- 我们是一个由全栈工程师组成的小团队,相信小团队能做出好产品。
- 没有项目经理层层转达,你直接和写代码的人对话。
+ {t("about.description")}
- {/* Stats row */}
- {stats.map((stat) => (
-
(
+
@@ -59,48 +48,52 @@ export function About() {
))}
- {/* Advantages */}
-
why us
+
{t("about.whyUs.label")}
- {advantages.map((item) => (
-
-
-
+ {Array.isArray(advantages) && advantages.map((item, index) => {
+ const Icon = advantageIcons[index]
+ return (
+
+
+
+
+
{item.title}
+
{item.desc}
-
{item.label}
-
{item.desc}
-
- ))}
+ )
+ })}
- {/* Principles */}
-
our principles
+
{t("about.principles.label")}
- {principles.map((item, index) => (
-
-
- {String(index + 1).padStart(2, "0")}
-
-
-
-
{item.title}
-
-
{item.desc}
+ {Array.isArray(principles) && principles.map((item, index) => {
+ const Icon = principleIcons[index]
+ return (
+
+
+ {String(index + 1).padStart(2, "0")}
+
+
+
+
{item.title}
+
+
{item.desc}
+
-
- ))}
+ )
+ })}
diff --git a/src/components/sections/cases.tsx b/src/components/sections/cases.tsx
index c1d613f..dfd4557 100644
--- a/src/components/sections/cases.tsx
+++ b/src/components/sections/cases.tsx
@@ -1,83 +1,60 @@
-import { Smartphone, Building2, Printer } from "lucide-react"
+import { useTranslation } from "react-i18next"
+import { Smartphone, Building2, Printer, LucideIcon } from "lucide-react"
-const cases = [
- {
- id: "01",
- title: "威友",
- subtitle: "Weiyou",
- category: "校园社交",
- description: "面向高校学生的校园交友 APP。支持兴趣匹配、活动发布、匿名树洞等功能,覆盖 iOS、Android 双端及 Web 管理后台。",
- platforms: ["iOS", "Android", "Web"],
- tags: ["React Native", "Go", "PostgreSQL"],
- icon: Smartphone,
- status: "live",
- },
- {
- id: "02",
- title: "CarrotERP",
- subtitle: "Enterprise Resource Planning",
- category: "企业管理",
- description: "内部使用的 ERP 管理软件,对标行业领军产品。涵盖采购、库存、财务、人事等核心模块,支撑企业日常运营全流程。",
- platforms: ["Web", "Desktop"],
- tags: ["React", "Go", "PostgreSQL"],
- icon: Building2,
- status: "live",
- },
- {
- id: "03",
- title: "CarrotPrint",
- subtitle: "Self-service Printing",
- category: "自助服务",
- description: "自助打印解决方案,覆盖微信小程序、Windows 客户端、Linux 服务端三端。用户扫码上传文件,就近打印取件。",
- platforms: ["小程序", "Windows", "Linux"],
- tags: ["Rust", "C#", "Taro", "Go"],
- icon: Printer,
- status: "live",
- },
-]
+const caseIcons: Record
= {
+ "威友": Smartphone,
+ "CarrotERP": Building2,
+ "CarrotPrint": Printer,
+}
export function Cases() {
+ const { t } = useTranslation()
+
+ type CaseItem = {
+ id: string
+ title: string
+ subtitle: string
+ category: string
+ description: string
+ platforms: string[]
+ tags: string[]
+ status: string
+ }
+
+ const items = t("cases.items", { returnObjects: true }) as CaseItem[]
+
return (
- {/* Section header */}
-
selected work
+
{t("cases.label")}
- 项目案例
+ {t("cases.title")}
- 以下是我们自主研发的核心产品,每个项目都经过真实场景验证。
+ {t("cases.description")}
- {/* Cases list - one column, alternating layout */}
- {cases.map((item, index) => {
+ {Array.isArray(items) && items.map((item, index) => {
const isEven = index % 2 === 0
- const Icon = item.icon
+ const Icon = caseIcons[item.title] || Smartphone
return (
-
+
- {/* Left side - Visual */}
- {/* Card mockup */}
- {/* Background glow */}
- {/* Icon & Title */}
@@ -88,9 +65,8 @@ export function Cases() {
- {/* Platform badges */}
- {item.platforms.map((platform) => (
+ {item.platforms.map((platform: string) => (
- {/* Status */}
{item.status}
@@ -108,36 +83,30 @@ export function Cases() {
- {/* Index number */}
{item.id}
- {/* Right side - Details */}
- {/* Category */}
{item.category}
- {/* Title */}
{item.title}
- {/* Description */}
{item.description}
- {/* Tech stack */}
-
tech stack
+
{t("cases.techStack")}
- {item.tags.map((tag) => (
+ {item.tags.map((tag: string) => (
{
- e.preventDefault()
- setSubmitted(true)
- setTimeout(() => {
- setSubmitted(false)
- setFormData({ name: "", email: "", company: "", message: "" })
- }, 3000)
- }
-
- return (
-
- )
-}
diff --git a/src/components/sections/footer.tsx b/src/components/sections/footer.tsx
index e7f3cdd..c70232f 100644
--- a/src/components/sections/footer.tsx
+++ b/src/components/sections/footer.tsx
@@ -1,68 +1,68 @@
+import { useTranslation } from "react-i18next"
import { Terminal, ArrowUpRight, Mail } from "lucide-react"
export function Footer() {
+ const { t } = useTranslation()
+
return (