diff --git a/README.md b/README.md index ad4d64a..f1d00fc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- +

diff --git a/messages/README-LANGUAGES.md b/messages/README-LANGUAGES.md new file mode 100644 index 0000000..ec15c0b --- /dev/null +++ b/messages/README-LANGUAGES.md @@ -0,0 +1,18 @@ +# Contributing Translations + +To contribute to the translation of your language you must modify the json in `/messages` that is named corresponding to the ISO Language Code of your given language. + +If you do not see a JSON for your language then add the language. + +## Adding a language + +1. To add a language you must add the language to the `const SUPPORTED_LANGUAGES = ['en', 'de'];` variable in the `./src/i18n.ts` file. +2. You must create a new `.json` file in the `./messages` directory +3. Copy the contents of the `en.json` file, make your way down the key-value pairs and change **only the values** to the translated equivalent. + + + + + + + diff --git a/messages/de.json b/messages/de.json new file mode 100644 index 0000000..a862be3 --- /dev/null +++ b/messages/de.json @@ -0,0 +1,7 @@ +{ + "navigation": { + "getting-started": "Erste Schritte", + "donate": "Spenden", + "useful-links": "Nützliche Links" + } +} \ No newline at end of file diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 0000000..e0a7a18 --- /dev/null +++ b/messages/en.json @@ -0,0 +1,7 @@ +{ + "navigation": { + "getting-started": "Getting Started", + "donate": "Donate", + "useful-links": "Useful Links" + } +} \ No newline at end of file diff --git a/next.config.js b/next.config.js index 47cc272..7f48842 100644 --- a/next.config.js +++ b/next.config.js @@ -1,7 +1,10 @@ +const createNextIntlPlugin = require('next-intl/plugin'); const { PHASE_DEVELOPMENT_SERVER } = require('next/constants') - + +const withNextIntl = createNextIntlPlugin(); + /** @type {import('next').NextConfig} */ -module.exports = (phase, { defaultConfig }) => { +const nextConfig = (phase, { defaultConfig }) => { const defaultConfigWWW = { images: { remotePatterns: [ @@ -38,4 +41,5 @@ module.exports = (phase, { defaultConfig }) => { output: 'export', }; }; - + +module.exports = withNextIntl(nextConfig); diff --git a/package-lock.json b/package-lock.json index 00fa56f..b3f8d84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@hookform/resolvers": "^3.9.0", + "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", @@ -31,6 +32,7 @@ "framer-motion": "^11.3.24", "lucide-react": "^0.400.0", "next": "14.2.4", + "next-intl": "^3.18.1", "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -2455,6 +2457,55 @@ "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", "license": "MIT" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -2936,6 +2987,37 @@ "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", "license": "MIT" }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.0.tgz", + "integrity": "sha512-HJOzSX8dQqtsp/3jVxCU3CXEONF7/2jlGAB28oX8TTw1Dz8JYbEI1UcL8355PuLBE41/IRRMvCw7VkiK/jcUOQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collapsible": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", @@ -2989,6 +3071,36 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz", + "integrity": "sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -8604,6 +8716,18 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "tslib": "^2.4.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -11328,7 +11452,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -11390,6 +11513,27 @@ } } }, + "node_modules/next-intl": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.18.1.tgz", + "integrity": "sha512-ht8HyroJeiJIte9yhg1f0Nc2rlZmkvSYQ3nhqFVJLzhq7T1Xb8nfjilffrOJc3sA8kEjBOS4bdIrg4YX8REO0Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^0.6.3", + "use-intl": "^3.18.1" + }, + "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" + } + }, "node_modules/next-themes": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", @@ -15068,6 +15212,19 @@ } } }, + "node_modules/use-intl": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.18.1.tgz", + "integrity": "sha512-BFNhVnszG1AB04DbNvJ+TLLd1oBDGergAKI8t9xaE4vDJYZaVKQH4zmpdArbegzTu5U9XMen6w14d1P1hBwKOQ==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/package.json b/package.json index fa53fd6..ce02325 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.0", + "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", @@ -35,6 +36,7 @@ "framer-motion": "^11.3.24", "lucide-react": "^0.400.0", "next": "14.2.4", + "next-intl": "^3.18.1", "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/public/feature-item-1.png b/public/feature-item-1.png new file mode 100644 index 0000000..96dae41 Binary files /dev/null and b/public/feature-item-1.png differ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fd2a59b..e68eabb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import { Inter } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/components/theme-provider"; import StyledComponentsRegistry from "@/lib/styled-components-registry"; +import {NextIntlClientProvider} from 'next-intl'; +import {getLocale, getMessages} from 'next-intl/server'; const inter = Inter({ subsets: ["latin"] }); @@ -12,26 +14,32 @@ export const metadata: Metadata = { keywords: ["Zen", "Browser", "Zen Browser", "Web", "Internet", "Fast"], }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const locale = await getLocale(); + + const messages = await getMessages(); + return ( - + - - {children} - + + + {children} + + ); diff --git a/src/components/features.tsx b/src/components/features.tsx index aa4ea1f..5b35cf4 100644 --- a/src/components/features.tsx +++ b/src/components/features.tsx @@ -50,10 +50,12 @@ import React, { useState } from 'react'; import { ny } from '@/lib/utils'; import ThemeCard from './theme-card'; import { getAllThemes, ZenTheme } from '@/lib/themes'; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './ui/accordion'; +import Logo from './logo'; function Checkmark() { return ( - + ); } @@ -70,17 +72,11 @@ function Question() { } export default function Features() { - const [feature, setFeature] = useState(0); - React.useEffect(() => { - const interval = setInterval(() => { - setFeature((feature) => (feature + 1) % 3); - }, 3000); - return () => clearInterval(interval); - }, []); + const [feature, setFeature] = useState("item-1"); return (
-
+

Your Browser, your way

With Zen's Theme Store, you can customize your browsing experience to reflect your unique style and preferences. Choose from a wide array of themes, colors, and layouts to make Zen truly your own, transforming your browser into a personalized digital space.

@@ -91,7 +87,7 @@ export default function Features() {
-
+

Community driven and Open Source

Zen thrives on the contributions of its vibrant community. As an open-source project, Zen encourages collaboration and innovation, allowing users and developers alike to shape the future of the browser.

@@ -109,11 +105,11 @@ export default function Features() {
-

Automated Releases, to prove security

+

Automated Releases to ensure security

-

Comunity driven

+

Community driven

@@ -144,7 +140,7 @@ export default function Features() {
Zen Browser
-
+
Zen Browser

Split Views

@@ -173,18 +169,18 @@ export default function Features() {
-

Tab Groups (Comming Soon)

+

Tab Groups (Coming Soon)

-

Security And Privacy is important to us

+

Security and Privacy is important to us

Zen is based on Firefox, ensuring that your browsing experience prioritizes security and privacy. With advanced tracking protection and minimal data collection, Zen keeps your online activity safe and secure, giving you peace of mind as you explore the web.

- +
@@ -220,6 +216,38 @@ export default function Features() {
+ {/*
+
+ Zen Browser + {feature == "item-1" && ( +
+ +
+ )} +
+
+ + + is it firefox based? + + Yes, Zen Browser is focused on being always at the latest version of Firefox, ensuring that you have the latest security updates and features. + + + + Does it track me? + + No! Zen Browser is built with privacy in mind. We don't track you, we don't collect your data, and we don't sell your data to third parties. + + + + How secure is Zen Browser? + + Zen Browser is built on top of Firefox, which is known for its security features. We also have additional security features like https only built into Zen Browser to help keep you safe online. + + + +
+
*/}

Convinced?

diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 92ba5c1..e99420f 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -18,6 +18,7 @@ import { ModeToggle } from "./mode-toggle" import { MobileNav } from "./mobile-nav" import { HeartIcon } from "lucide-react" import { HeartFilledIcon } from "@radix-ui/react-icons" +import { useTranslations } from "next-intl"; export const components: { title: string; href: string; description: string }[] = [ { @@ -48,6 +49,9 @@ export const components: { title: string; href: string; description: string }[] ] export function Navigation() { + + const t = useTranslations('navigation'); + return (
@@ -59,7 +63,7 @@ export function Navigation() { - Getting started + {t('getting-started')}
  • @@ -94,7 +98,7 @@ export function Navigation() { - Donate + {t('donate')}
      @@ -114,7 +118,7 @@ export function Navigation() { - Useful Links + {t('useful-links')}
        {components.map((component) => ( diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 0000000..ab33aa9 --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,57 @@ +'use client' + +import * as React from 'react' +import * as AccordionPrimitive from '@radix-ui/react-accordion' +import { ChevronDownIcon } from '@radix-ui/react-icons' + +import { ny } from '@/lib/utils' + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = 'AccordionItem' + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
        {children}
        +
        +)) +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..a2fcfed --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,35 @@ +import { getRequestConfig } from "next-intl/server"; +import { headers } from "next/headers"; + +const SUPPORTED_LANGUAGES = ['en', 'de']; + +export default getRequestConfig(async () => { + const headersList = headers(); + + const acceptLanguage = headersList.get("accept-language") || "en"; + + const [primaryLanguage] = acceptLanguage + .split(",") + .map((lang) => lang.split(";")[0]) + .map((lang) => lang.toLowerCase()); + + const locale = SUPPORTED_LANGUAGES.includes(primaryLanguage) ? primaryLanguage : 'en'; + + try { + const messages = (await import(`../messages/${locale}.json`)).default; + + return { + locale, + messages, + }; + } catch (error) { + console.error(`Failed to load messages for locale: ${locale}`, error); + + const fallbackMessages = (await import(`../messages/en.json`)).default; + + return { + locale: "en", + messages: fallbackMessages, + }; + } +}); diff --git a/src/lib/release-notes.ts b/src/lib/release-notes.ts index 5fce99a..f8b5f30 100644 --- a/src/lib/release-notes.ts +++ b/src/lib/release-notes.ts @@ -668,7 +668,7 @@ export const releaseNotes: ReleaseNote[] = [ features: [ "Fixed policies for updates", "Enforce HTTPS-Only Mode", - "Url Bsar improvements", + "URL bar improvements", "Fixed issue with opening links from external apps", "Compact mode now takes element separation into account", "Added labels to buttons during expand-on-hover"