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

- 联系我们 -

-

- 无论您有任何技术需求或合作意向,欢迎随时与我们联系 -

-
- -
-
- {contactInfo.map((info) => ( - - -
- -
-
-

- {info.title} -

-

- {info.content} -

-
-
-
- ))} -
- -
- - - {submitted ? ( -
-
- -
-

- 提交成功 -

-

- 感谢您的留言,我们会尽快与您联系! -

-
- ) : ( -
-
-
- - - setFormData({ ...formData, name: e.target.value }) - } - required - /> -
-
- - - setFormData({ ...formData, email: e.target.value }) - } - required - /> -
-
-
- - - setFormData({ ...formData, company: e.target.value }) - } - /> -
-
- -