From 9c36f30bb0e0a5a5255d6d489e65f5bfc99aa5b2 Mon Sep 17 00:00:00 2001 From: JiPai Date: Thu, 5 Sep 2024 19:59:05 +0800 Subject: [PATCH 01/27] feat: add i18n dependency to website workspace --- apps/website/i18n/request.tsx | 12 ++++++ apps/website/i18n/routing.ts | 16 ++++++++ apps/website/middleware.ts | 9 +++++ apps/website/next.config.js | 20 ++++++---- apps/website/package.json | 1 + pnpm-lock.yaml | 70 +++++++++++++++++++++++++++++++++++ 6 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 apps/website/i18n/request.tsx create mode 100644 apps/website/i18n/routing.ts create mode 100644 apps/website/middleware.ts diff --git a/apps/website/i18n/request.tsx b/apps/website/i18n/request.tsx new file mode 100644 index 00000000..714e7404 --- /dev/null +++ b/apps/website/i18n/request.tsx @@ -0,0 +1,12 @@ +import { notFound } from 'next/navigation' +import { getRequestConfig } from 'next-intl/server' +import { routing } from './routing' + +export default getRequestConfig(async ({ locale }) => { + // Validate that the incoming `locale` parameter is valid + if (!routing.locales.includes(locale as any)) notFound() + + return { + messages: (await import(`../locales/${locale}.json`)).default, + } +}) diff --git a/apps/website/i18n/routing.ts b/apps/website/i18n/routing.ts new file mode 100644 index 00000000..b647e4fe --- /dev/null +++ b/apps/website/i18n/routing.ts @@ -0,0 +1,16 @@ +import { defineRouting } from 'next-intl/routing' +import { createSharedPathnamesNavigation } from 'next-intl/navigation' + +export const routing = defineRouting({ + // A list of all locales that are supported + locales: ['en', 'zh-Hans'], + + // Used when no locale matches + defaultLocale: 'en', + localePrefix: 'as-needed', +}) + +// Lightweight wrappers around Next.js' navigation APIs +// that will consider the routing configuration +export const { Link, redirect, usePathname, useRouter } = + createSharedPathnamesNavigation(routing) diff --git a/apps/website/middleware.ts b/apps/website/middleware.ts new file mode 100644 index 00000000..5eb5b604 --- /dev/null +++ b/apps/website/middleware.ts @@ -0,0 +1,9 @@ +import createMiddleware from 'next-intl/middleware'; +import {routing} from './i18n/routing'; + +export default createMiddleware(routing); + +export const config = { + // Match only internationalized pathnames + matcher: ['/', '/(zh-Hans|en)/:path*'] +}; \ No newline at end of file diff --git a/apps/website/next.config.js b/apps/website/next.config.js index 5ddbf3c9..5792410a 100644 --- a/apps/website/next.config.js +++ b/apps/website/next.config.js @@ -1,11 +1,15 @@ +const createNextIntlPlugin = require('next-intl/plugin') + +const withNextIntl = createNextIntlPlugin() + /** @type {import('next').NextConfig} */ const nextConfig = { - eslint: { - ignoreDuringBuilds: true, - }, - typescript: { - ignoreBuildErrors: true, - }, -}; + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, +} -module.exports = nextConfig; +module.exports = withNextIntl(nextConfig) diff --git a/apps/website/package.json b/apps/website/package.json index 73f74245..3f9e15fb 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -24,6 +24,7 @@ "framer-motion": "^11.0.24", "lucide-react": "0.364.0", "next": "14.2.2", + "next-intl": "^3.19.0", "react": "18.2.0", "react-dom": "18.2.0", "react-ga4": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a8e9e30..69d7f53c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -554,6 +554,9 @@ importers: next: specifier: 14.2.2 version: 14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + next-intl: + specifier: ^3.19.0 + version: 3.19.0(next@14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -1662,6 +1665,18 @@ packages: '@floating-ui/utils@0.2.5': resolution: {integrity: sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==} + '@formatjs/ecma402-abstract@2.0.0': + resolution: {integrity: sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==} + + '@formatjs/fast-memoize@2.2.0': + resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==} + + '@formatjs/icu-messageformat-parser@2.7.8': + resolution: {integrity: sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==} + + '@formatjs/icu-skeleton-parser@1.8.2': + resolution: {integrity: sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==} + '@formatjs/intl-localematcher@0.5.4': resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} @@ -5798,6 +5813,9 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + intl-messageformat@10.5.14: + resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -6634,6 +6652,12 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next-intl@3.19.0: + resolution: {integrity: sha512-ciiHYBwR3ztoMdJZgFmt0LII7GYTsLA/MFt3y681q4Lw4fI5EYNCZSYb9XA/BIt3ZX5S1TLUP1uOERy1dIQvMg==} + peerDependencies: + next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} peerDependencies: @@ -8196,6 +8220,11 @@ packages: '@types/react': optional: true + use-intl@3.19.0: + resolution: {integrity: sha512-JOA73+YdtArxkvFKrneLAhH55m+x+sA9Wj8w8rYkHkHvcW/76w5T3szSmBG063vH3UqLoIKlsDVBBUfpkB7GMg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + use-resize-observer@9.1.0: resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==} peerDependencies: @@ -9680,6 +9709,26 @@ snapshots: '@floating-ui/utils@0.2.5': {} + '@formatjs/ecma402-abstract@2.0.0': + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + tslib: 2.6.3 + + '@formatjs/fast-memoize@2.2.0': + dependencies: + tslib: 2.6.3 + + '@formatjs/icu-messageformat-parser@2.7.8': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/icu-skeleton-parser': 1.8.2 + tslib: 2.6.3 + + '@formatjs/icu-skeleton-parser@1.8.2': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + tslib: 2.6.3 + '@formatjs/intl-localematcher@0.5.4': dependencies: tslib: 2.6.3 @@ -14933,6 +14982,13 @@ snapshots: internmap@2.0.3: {} + intl-messageformat@10.5.14: + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/fast-memoize': 2.2.0 + '@formatjs/icu-messageformat-parser': 2.7.8 + tslib: 2.6.3 + invariant@2.2.4: dependencies: loose-envify: 1.4.0 @@ -16017,6 +16073,14 @@ snapshots: neo-async@2.6.2: {} + next-intl@3.19.0(next@14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0): + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + negotiator: 0.6.3 + next: 14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + use-intl: 3.19.0(react@18.2.0) + next-themes@0.2.1(next@14.2.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: next: 14.2.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -17797,6 +17861,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + use-intl@3.19.0(react@18.2.0): + dependencies: + '@formatjs/fast-memoize': 2.2.0 + intl-messageformat: 10.5.14 + react: 18.2.0 + use-resize-observer@9.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@juggle/resize-observer': 3.4.0 From 458ddc6e0a505df315c0754a001a9c957069e183 Mon Sep 17 00:00:00 2001 From: JiPai Date: Thu, 5 Sep 2024 20:00:08 +0800 Subject: [PATCH 02/27] chore(config): add tabWidth to prettier config for avoid differences among contributors --- apps/website/prettier.config.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/website/prettier.config.js b/apps/website/prettier.config.js index 16943fa9..c72d0299 100644 --- a/apps/website/prettier.config.js +++ b/apps/website/prettier.config.js @@ -2,5 +2,7 @@ module.exports = { singleQuote: true, semi: false, - plugins: ["prettier-plugin-tailwindcss"], -}; + tabWidth: 4, + useTabs: true, + plugins: ['prettier-plugin-tailwindcss'], +} From d3108ebf65bd514c63dd05a8ebcdbb931521ee49 Mon Sep 17 00:00:00 2001 From: JiPai Date: Thu, 5 Sep 2024 20:19:10 +0800 Subject: [PATCH 03/27] feat: let not-found page work with locale --- apps/website/app/[locale]/[...rest]/page.tsx | 5 + apps/website/app/[locale]/layout.tsx | 101 +++++++++++++++++++ apps/website/app/[locale]/not-found.tsx | 27 +++++ apps/website/app/layout.tsx | 92 ++--------------- apps/website/app/not-found.tsx | 65 ++++++++---- apps/website/i18n/routing.ts | 12 +-- apps/website/middleware.ts | 16 +-- 7 files changed, 199 insertions(+), 119 deletions(-) create mode 100644 apps/website/app/[locale]/[...rest]/page.tsx create mode 100644 apps/website/app/[locale]/layout.tsx create mode 100644 apps/website/app/[locale]/not-found.tsx diff --git a/apps/website/app/[locale]/[...rest]/page.tsx b/apps/website/app/[locale]/[...rest]/page.tsx new file mode 100644 index 00000000..0ff96242 --- /dev/null +++ b/apps/website/app/[locale]/[...rest]/page.tsx @@ -0,0 +1,5 @@ +import { notFound } from 'next/navigation' + +export default function CatchAll() { + notFound() +} diff --git a/apps/website/app/[locale]/layout.tsx b/apps/website/app/[locale]/layout.tsx new file mode 100644 index 00000000..e1edcf5b --- /dev/null +++ b/apps/website/app/[locale]/layout.tsx @@ -0,0 +1,101 @@ +import clsx from 'clsx' +import { Inter, Lexend } from 'next/font/google' +import '@/styles/tailwind.css' +import GoogleAnalytics from '@/components/analitycs/google' + +import { NextIntlClientProvider } from 'next-intl' +import { getMessages } from 'next-intl/server' + +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: { + default: 'Dokploy - Effortless Deployment Solutions', + template: '%s | Simplify Your DevOps', + }, + alternates: { + canonical: 'https://dokploy.com', + languages: { + en: 'https://dokploy.com', + }, + }, + description: + 'Streamline your deployment process with Dokploy. Effortlessly manage applications and databases on any VPS using Docker and Traefik for improved performance and security.', + applicationName: 'Dokploy', + keywords: [ + 'Dokploy', + 'Docker', + 'Traefik', + 'deployment', + 'VPS', + 'application management', + 'database management', + 'DevOps', + 'cloud infrastructure', + 'UI Self hosted', + ], + referrer: 'origin', + robots: 'index, follow', + openGraph: { + type: 'website', + url: 'https://dokploy.com', + title: 'Dokploy - Effortless Deployment Solutions', + description: + 'Simplify your DevOps with Dokploy. Deploy applications and manage databases efficiently on any VPS.', + siteName: 'Dokploy', + images: [ + { + url: 'http://dokploy.com/og.png', + }, + ], + }, + twitter: { + card: 'summary_large_image', + site: '@Dokploy', + creator: '@Dokploy', + title: 'Dokploy - Simplify Your DevOps', + description: + 'Deploy applications and manage databases with ease using Dokploy. Learn how our platform can elevate your infrastructure management.', + images: 'https://dokploy.com/og.png', + }, +} + +const inter = Inter({ + subsets: ['latin'], + display: 'swap', + variable: '--font-inter', +}) + +const lexend = Lexend({ + subsets: ['latin'], + display: 'swap', + variable: '--font-lexend', +}) + +export default async function RootLayout({ + children, + params, +}: { + children: React.ReactNode + params: { locale: string } +}) { + const { locale } = params + const messages = await getMessages() + return ( + + + + + {children} + + + + ) +} diff --git a/apps/website/app/[locale]/not-found.tsx b/apps/website/app/[locale]/not-found.tsx new file mode 100644 index 00000000..1eee5946 --- /dev/null +++ b/apps/website/app/[locale]/not-found.tsx @@ -0,0 +1,27 @@ +import Link from 'next/link' + +import { SlimLayout } from '../../components/SlimLayout' +// import { Button } from "../components/Button"; +import { Logo } from '../../components/shared/Logo' + +export default function NotFound() { + return ( + +
+ + + +
+

404

+

+ Page not found +

+

+ Sorry, we couldn’t find the page you’re looking for. +

+ {/* */} +
+ ) +} diff --git a/apps/website/app/layout.tsx b/apps/website/app/layout.tsx index fc43d46d..b7bc5467 100644 --- a/apps/website/app/layout.tsx +++ b/apps/website/app/layout.tsx @@ -1,85 +1,11 @@ -import clsx from "clsx"; -import { Inter, Lexend } from "next/font/google"; -import "@/styles/tailwind.css"; -import GoogleAnalytics from "@/components/analitycs/google"; -import type { Metadata } from "next"; +import { ReactNode } from 'react' -export const metadata: Metadata = { - title: { - default: "Dokploy - Effortless Deployment Solutions", - template: "%s | Simplify Your DevOps", - }, - alternates: { - canonical: "https://dokploy.com", - languages: { - en: "https://dokploy.com", - }, - }, - description: - "Streamline your deployment process with Dokploy. Effortlessly manage applications and databases on any VPS using Docker and Traefik for improved performance and security.", - applicationName: "Dokploy", - keywords: [ - "Dokploy", - "Docker", - "Traefik", - "deployment", - "VPS", - "application management", - "database management", - "DevOps", - "cloud infrastructure", - "UI Self hosted", - ], - referrer: "origin", - robots: "index, follow", - openGraph: { - type: "website", - url: "https://dokploy.com", - title: "Dokploy - Effortless Deployment Solutions", - description: - "Simplify your DevOps with Dokploy. Deploy applications and manage databases efficiently on any VPS.", - siteName: "Dokploy", - images: [ - { - url: "http://dokploy.com/og.png", - }, - ], - }, - twitter: { - card: "summary_large_image", - site: "@Dokploy", - creator: "@Dokploy", - title: "Dokploy - Simplify Your DevOps", - description: - "Deploy applications and manage databases with ease using Dokploy. Learn how our platform can elevate your infrastructure management.", - images: "https://dokploy.com/og.png", - }, -}; - -const inter = Inter({ - subsets: ["latin"], - display: "swap", - variable: "--font-inter", -}); - -const lexend = Lexend({ - subsets: ["latin"], - display: "swap", - variable: "--font-lexend", -}); - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - - {children} - - ); +type Props = { + children: ReactNode +} + +// Since we have a `not-found.tsx` page on the root, a layout file +// is required, even if it's just passing children through. +export default function RootLayout({ children }: Props) { + return children } diff --git a/apps/website/app/not-found.tsx b/apps/website/app/not-found.tsx index 5225642b..69915615 100644 --- a/apps/website/app/not-found.tsx +++ b/apps/website/app/not-found.tsx @@ -1,27 +1,48 @@ -import Link from "next/link"; +'use client' -import { SlimLayout } from "../components/SlimLayout"; -// import { Button } from "../components/Button"; -import { Logo } from "../components/shared/Logo"; +import { Logo } from '@/components/shared/Logo' +import { SlimLayout } from '@/components/SlimLayout' +import Error from 'next/error' +import Link from 'next/link' + +// Render the default Next.js 404 page when a route +// is requested that doesn't match the middleware and +// therefore doesn't have a locale associated with it. + +// export default function NotFound() { +// return ( +// +// +// +//
+// +// +// +//
+//

+// 404 +//

+//

+// Page not found +//

+//

+// Sorry, we couldn’t find the page you’re looking for. +//

+// {/* */} +//
+// +// +// ) +// } export default function NotFound() { return ( - -
- - - -
-

404

-

- Page not found -

-

- Sorry, we couldn’t find the page you’re looking for. -

- {/* */} -
- ); + + + + + + ) } diff --git a/apps/website/i18n/routing.ts b/apps/website/i18n/routing.ts index b647e4fe..a60e3d1a 100644 --- a/apps/website/i18n/routing.ts +++ b/apps/website/i18n/routing.ts @@ -2,15 +2,15 @@ import { defineRouting } from 'next-intl/routing' import { createSharedPathnamesNavigation } from 'next-intl/navigation' export const routing = defineRouting({ - // A list of all locales that are supported - locales: ['en', 'zh-Hans'], + // A list of all locales that are supported + locales: ['en', 'zh-Hans'], - // Used when no locale matches - defaultLocale: 'en', - localePrefix: 'as-needed', + // Used when no locale matches + defaultLocale: 'en', + localePrefix: 'as-needed', }) // Lightweight wrappers around Next.js' navigation APIs // that will consider the routing configuration export const { Link, redirect, usePathname, useRouter } = - createSharedPathnamesNavigation(routing) + createSharedPathnamesNavigation(routing) diff --git a/apps/website/middleware.ts b/apps/website/middleware.ts index 5eb5b604..ada4177a 100644 --- a/apps/website/middleware.ts +++ b/apps/website/middleware.ts @@ -1,9 +1,9 @@ -import createMiddleware from 'next-intl/middleware'; -import {routing} from './i18n/routing'; - -export default createMiddleware(routing); - +import createMiddleware from 'next-intl/middleware' +import { routing } from './i18n/routing' + +export default createMiddleware(routing, { localeDetection: false }) + export const config = { - // Match only internationalized pathnames - matcher: ['/', '/(zh-Hans|en)/:path*'] -}; \ No newline at end of file + // Match only internationalized pathnames + matcher: ['/((?!_next|.*\\..*).*)'] +} From fe032d3d0ffc1d7f1449d3417ae934c5f6ad5715 Mon Sep 17 00:00:00 2001 From: JiPai Date: Thu, 5 Sep 2024 20:19:28 +0800 Subject: [PATCH 04/27] feat: add translate for zh-Hans --- apps/website/app/[locale]/page.tsx | 25 +++ apps/website/app/page.tsx | 25 --- apps/website/components/CallToAction.tsx | 33 ++-- apps/website/components/Faqs.tsx | 64 ++++---- apps/website/components/Footer.tsx | 31 ++-- apps/website/components/Header.tsx | 95 ++++++----- apps/website/components/Hero.tsx | 82 +++++---- apps/website/components/PrimaryFeatures.tsx | 126 +++++++------- apps/website/components/SecondaryFeatures.tsx | 155 ++++++++++-------- apps/website/components/SlimLayout.tsx | 16 +- apps/website/locales/en.json | 82 +++++++++ apps/website/locales/zh-Hans.json | 82 +++++++++ 12 files changed, 521 insertions(+), 295 deletions(-) create mode 100644 apps/website/app/[locale]/page.tsx delete mode 100644 apps/website/app/page.tsx create mode 100644 apps/website/locales/en.json create mode 100644 apps/website/locales/zh-Hans.json diff --git a/apps/website/app/[locale]/page.tsx b/apps/website/app/[locale]/page.tsx new file mode 100644 index 00000000..b459b2da --- /dev/null +++ b/apps/website/app/[locale]/page.tsx @@ -0,0 +1,25 @@ +import { CallToAction } from '@/components/CallToAction' +import { Faqs } from '@/components/Faqs' +import { Footer } from '@/components/Footer' +import { Header } from '@/components/Header' +import { Hero } from '@/components/Hero' +import { PrimaryFeatures } from '@/components/PrimaryFeatures' +import { SecondaryFeatures } from '@/components/SecondaryFeatures' +import { Testimonials } from '../../components/Testimonials' + +export default function Home() { + return ( +
+
+
+ + + + + {/* */} + +
+
+
+ ) +} diff --git a/apps/website/app/page.tsx b/apps/website/app/page.tsx deleted file mode 100644 index 2847ee10..00000000 --- a/apps/website/app/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { CallToAction } from "../components/CallToAction"; -import { Faqs } from "../components/Faqs"; -import { Footer } from "../components/Footer"; -import { Header } from "../components/Header"; -import { Hero } from "../components/Hero"; -import { PrimaryFeatures } from "../components/PrimaryFeatures"; -import { SecondaryFeatures } from "../components/SecondaryFeatures"; -import { Testimonials } from "../components/Testimonials"; - -export default function Home() { - return ( -
-
-
- - - - - {/* */} - -
-
-
- ); -} diff --git a/apps/website/components/CallToAction.tsx b/apps/website/components/CallToAction.tsx index 6c7b5a1f..ac225284 100644 --- a/apps/website/components/CallToAction.tsx +++ b/apps/website/components/CallToAction.tsx @@ -1,12 +1,14 @@ -import { Container } from "@/components/Container"; -import Link from "next/link"; -import { Button } from "./ui/button"; +import { Container } from '@/components/Container' +import Link from 'next/link' +import { Button } from './ui/button' +import { useTranslations } from 'next-intl' export function CallToAction() { + const t = useTranslations('HomePage') return (
- + @@ -27,30 +34,28 @@ export function CallToAction() { - +

- Unlock Your Deployment Potential + {t('callToAction.title')}

- Streamline your deployments with our PaaS. Effortlessly manage - Docker containers and traffic with Traefik. Boost your - infrastructure's efficiency and security today + {t('callToAction.des')}

{/* @ts-expect-error */} -
- ); + ) } diff --git a/apps/website/components/Faqs.tsx b/apps/website/components/Faqs.tsx index 117b4d00..f84fe5c4 100644 --- a/apps/website/components/Faqs.tsx +++ b/apps/website/components/Faqs.tsx @@ -1,63 +1,58 @@ -import { Container } from "./Container"; +import { useTranslations } from 'next-intl' +import { Container } from './Container' const faqs = [ [ { - question: "What is dokploy?", - answer: - "Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as a free alternative self-hostable solution to platforms like Heroku, Vercel, and Netlify.", + question: 'faq.q1', + answer: 'faq.a2', }, { - question: "Why Choose Dokploy?", - answer: "Simplicity, Flexibility, and Fast", + question: 'faq.q2', + answer: 'faq.a2', }, { - question: "Is free?", - answer: - "Yes, dokploy is totally free. You can use it for personal projects, small teams, or even for large-scale applications.", + question: 'faq.q3', + answer: 'faq.a3', }, { - question: "Is it open source?", - answer: "Yes, dokploy is open source and free to use.", + question: 'faq.q4', + answer: 'faq.a4', }, ], [ { - question: "What type of applications can i deploy with dokploy?", - answer: - "Dokploy is a great choice for any type of application. You can deploy your code to dokploy and manage it from the dashboard. We support a wide range of languages and frameworks, so you can choose the one that best fits your needs.", + question: 'faq.q5', + answer: 'faq.a5', }, { - question: "How do I request a feature or report a bug?", - answer: - "Currently we are working on fixing bug fixes, but we will be releasing new features soon. You can also request features or report bugs.", + question: 'faq.q6', + answer: 'faq.a6', }, { - question: "Do you track the usage of Dokploy?", - answer: "No, we don't track any usage data.", + question: 'faq.q7', + answer: "faq.a7", }, ], [ { question: - "Are there any user forums or communities where I can interact with other users?", - answer: - "Yes, we have active github discussions where you can share ideas, ask for help, and connect with other users.", + 'faq.q8', + answer: 'faq.a8', }, { - question: "What types of applications can I deploy with Dokploy?", - answer: - "Dokploy supports a variety of applications, including those built with Docker, as well as applications from any Git repository, offering custom builds with Nixpacks, Dockerfiles, or Buildpacks like Heroku and Paketo.", + question: 'faq.q9', + answer: 'faq.a9', }, { - question: "How does Dokploy handle database management?", - answer: - "Dokploy supports multiple database systems including Postgres, MySQL, MariaDB, MongoDB, and Redis, providing tools for easy deployment and management directly from the dashboard.", + question: 'faq.q10', + answer: 'faq.a10', }, ], -]; +] export function Faqs() { + const t = useTranslations('HomePage') return (
- Frequently asked questions + {t('faq.title')}

- If you can’t find what you’re looking for, email our support team - and if you’re lucky someone will get back to you. + {t('faq.des')}

    @@ -84,10 +78,10 @@ export function Faqs() { {column.map((faq, faqIndex) => (
  • - {faq.question} + {t(faq.question)}

    - {faq.answer} + {t(faq.answer)}

  • ))} @@ -97,5 +91,5 @@ export function Faqs() {
- ); + ) } diff --git a/apps/website/components/Footer.tsx b/apps/website/components/Footer.tsx index 3c188e55..0c2bd1c4 100644 --- a/apps/website/components/Footer.tsx +++ b/apps/website/components/Footer.tsx @@ -1,15 +1,17 @@ -import Link from "next/link"; +import Link from 'next/link' -import { Container } from "./Container"; -import { NavLink } from "./NavLink"; -import { Logo } from "./shared/Logo"; +import { Container } from './Container' +import { NavLink } from './NavLink' +import { Logo } from './shared/Logo' +import { useTranslations } from 'next-intl' export function Footer() { + const t = useTranslations('HomePage') return (
-
+
Dokploy @@ -17,14 +19,18 @@ export function Footer() {
@@ -57,11 +63,12 @@ export function Footer() {

- Copyright © {new Date().getFullYear()} Dokploy. All rights - reserved. + {t('footer.copyright', { + year: new Date().getFullYear(), + })}

- ); + ) } diff --git a/apps/website/components/Header.tsx b/apps/website/components/Header.tsx index 5cd3151c..5f0ca76c 100644 --- a/apps/website/components/Header.tsx +++ b/apps/website/components/Header.tsx @@ -1,33 +1,34 @@ -"use client"; +'use client' -import { cn } from "@/lib/utils"; -import { Popover, Transition } from "@headlessui/react"; -import { HeartIcon } from "lucide-react"; -import Link from "next/link"; -import { Fragment } from "react"; -import { Container } from "./Container"; -import { NavLink } from "./NavLink"; -import { trackGAEvent } from "./analitycs"; -import { Logo } from "./shared/Logo"; -import { Button, buttonVariants } from "./ui/button"; +import { cn } from '@/lib/utils' +import { Popover, Transition } from '@headlessui/react' +import { HeartIcon } from 'lucide-react' +import Link from 'next/link' +import { Fragment } from 'react' +import { Container } from './Container' +import { NavLink } from './NavLink' +import { trackGAEvent } from './analitycs' +import { Logo } from './shared/Logo' +import { Button, buttonVariants } from './ui/button' +import { useTranslations } from 'next-intl' function MobileNavLink({ href, children, target, }: { - href: string; - children: React.ReactNode; - target?: string; + href: string + children: React.ReactNode + target?: string }) { return ( { trackGAEvent({ - action: "Nav Link Clicked", - category: "Navigation", + action: 'Nav Link Clicked', + category: 'Navigation', label: href, - }); + }) }} as={Link} href={href} @@ -36,7 +37,7 @@ function MobileNavLink({ > {children} - ); + ) } function MobileNavIcon({ open }: { open: boolean }) { @@ -50,20 +51,24 @@ function MobileNavIcon({ open }: { open: boolean }) { > - ); + ) } function MobileNavigation() { + const t = useTranslations('HomePage') return ( - Features - Testimonials - Faqs + + {t('navigation.features')} + + {/* Testimonials */} + + {t('navigation.faqs')} + - Docs + {t('navigation.docs')} - ); + ) } export function Header() { + const t = useTranslations('HomePage') return ( -
+
- ); + ) } diff --git a/apps/website/components/Hero.tsx b/apps/website/components/Hero.tsx index 3d07e9ca..018962c2 100644 --- a/apps/website/components/Hero.tsx +++ b/apps/website/components/Hero.tsx @@ -1,9 +1,10 @@ -"use client"; -import { Check, Copy } from "lucide-react"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { Container } from "./Container"; -import { Button } from "./ui/button"; +'use client' +import { Check, Copy } from 'lucide-react' +import Link from 'next/link' +import { useEffect, useState } from 'react' +import { Container } from './Container' +import { Button } from './ui/button' +import { useTranslations } from 'next-intl' const ProductHunt = () => { return ( @@ -34,32 +35,42 @@ const ProductHunt = () => { - ); -}; + ) +} export function Hero() { - const [isCopied, setIsCopied] = useState(false); + const t = useTranslations('HomePage') + const [isCopied, setIsCopied] = useState(false) useEffect(() => { const timer = setTimeout(() => { - setIsCopied(false); - }, 2000); - return () => clearTimeout(timer); - }, [isCopied]); + setIsCopied(false) + }, 2000) + return () => clearTimeout(timer) + }, [isCopied]) return ( - +
- + - + @@ -68,7 +79,7 @@ export function Hero() {

- Deploy{" "} + {t('hero.deploy')}{' '} - Anywhere - {" "} - with Total Freedom and Ease. + {t('hero.anywhere')} + {' '} + {t('hero.with')}

- Streamline your operations with our all-in-one platform—perfect for - managing projects, data, and system health with simplicity and - efficiency. + {t('hero.des')}

-
- +
+ curl -sSL https://dokploy.com/install.sh | sh
-
+