Skip to content

Commit

Permalink
Merge pull request #12 from Redot-Engine/feature/landing-page
Browse files Browse the repository at this point in the history
Implement Localization and Internationalization Features
  • Loading branch information
charlottewiltshire0 authored Dec 22, 2024
2 parents 9abd18d + 7348103 commit fd7fee7
Show file tree
Hide file tree
Showing 51 changed files with 3,970 additions and 308 deletions.
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ junit.xml
docs
public
bun.lockb
locales
13 changes: 13 additions & 0 deletions actions/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use server";

import { cookies } from "next/headers";

export async function setLanguage(value: string) {
const cookieStore = await cookies();
cookieStore.set("locale", value);
}

export async function getLanguage() {
const cookieStore = await cookies();
return cookieStore.get("locale")?.value || "en";
}
14 changes: 11 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "next-themes";
import { GoogleTagManager } from "@next/third-parties/google";
import { NextIntlClientProvider } from "next-intl";
import { getLocale, getMessages } from "next-intl/server";

const inter = Inter({
subsets: ["latin"],
Expand All @@ -14,20 +16,26 @@ export const metadata: Metadata = {
description: "Redot Engine: Open source game engine for everyone.",
};

export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const locale = await getLocale();

const messages = await getMessages();

return (
<html lang="en" suppressHydrationWarning>
<html lang={locale} suppressHydrationWarning>
<head>
<link rel="preconnect" href="https://image.redotengine.org" />
</head>
<GoogleTagManager gtmId="G-PLVV7BPX1T" />
<body className={`${inter.className} bg-background antialiased`}>
<ThemeProvider attribute="class" defaultTheme="light">
{children}
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</ThemeProvider>
</body>
</html>
Expand Down
42 changes: 42 additions & 0 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import Image from "next/image";

export const runtime = "edge";

export default function NotFound() {
const t = useTranslations("notFound");

return (
<section className="flex min-h-screen flex-col">
<Header />
<div className="flex-grow overflow-x-clip bg-gradient-to-b from-[#ffffff] to-[#FFD2D2] py-32">
<div className="mx-auto max-w-[540px]">
<div className="flex flex-col items-center justify-center gap-4">
<Image
src="https://image.redotengine.org/redotchan.png"
alt="Redotchan"
width={160}
height={160}
/>
<div className="flex flex-col">
<h2 className="mt-5 text-center text-4xl font-bold tracking-tighter md:text-[54px] md:leading-[60px]">
{t("title")}
</h2>
<p className="mt-5 text-center text-xl tracking-tighter text-black/60 md:text-[22px] md:leading-[30px]">
{t("description")}
</p>
</div>
<Button asChild>
<Link href="/">{t("goBack")}</Link>
</Button>
</div>
</div>
</div>
<Footer />
</section>
);
}
Binary file modified bun.lockb
Binary file not shown.
101 changes: 10 additions & 91 deletions components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,102 +7,21 @@ import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { socials } from "@/constants/socials";
import { language } from "@/constants/language";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { IconChevronDown } from "@tabler/icons-react";
import { links } from "@/constants/links";
import { footer } from "@/constants/footer";
import { useTranslations } from "next-intl";
import LanguageSwitcher from "@/components/language-switcher";

export const Footer = () => {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState("en");
const t = useTranslations("footer");

return (
<footer className="relative bottom-0 z-[50] w-full bg-black text-white">
<div className="px-10 py-16 lg:px-40">
<div className="flex flex-col gap-10">
<div className="mb-2 flex flex-col justify-between gap-6 md:flex-row">
<div className="flex flex-col gap-8">
<div className="dark">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
role="combobox"
aria-expanded={open}
className="flex items-center justify-between gap-2 text-white/80"
disabled={!value}
>
<Image
src={`/flags/${language.find((lang) => lang.value === value)?.code}.svg`}
alt={
language.find((lang) => lang.value === value)
?.label as string
}
width="20"
height="15"
className="mr-2 rounded"
/>
{value
? language.find((lang) => lang.value === value)?.label
: "Select language..."}
<IconChevronDown className="h-4 w-4 opacity-80" />
</button>
</PopoverTrigger>
<PopoverContent className="w-[220px] p-0">
<Command>
<CommandInput placeholder="Search language..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{language.map((lang) => (
<CommandItem
key={lang.value}
value={lang.value}
onSelect={(currentValue) => {
if (currentValue !== value) {
setValue(currentValue);
setOpen(false);
}
}}
>
<Image
src={`/flags/${lang.code}.svg`}
alt={lang.label}
width="20"
height="15"
className="rounded"
/>
{lang.label}
<Check
className={cn(
"ml-auto",
value === lang.value
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<LanguageSwitcher />
<div className="flex flex-row gap-4">
{socials.map((social, index) => (
<Link
Expand All @@ -126,7 +45,7 @@ export const Footer = () => {
{footer.map((category, index) => (
<div key={index} className="text-sm">
<h3 className="font-medium text-white/80">
{category.title}
{t(category.title)}
</h3>
<ul className="mt-4 space-y-2">
{category.children?.map((item, idx) => (
Expand All @@ -135,7 +54,7 @@ export const Footer = () => {
href={item.href || "#"}
className="text-white/60 transition duration-300 hover:text-white"
>
{item.title}
{t(item.title)}
</Link>
</li>
))}
Expand All @@ -157,22 +76,22 @@ export const Footer = () => {
/>

<span className="order-first w-full text-center text-sm text-white/60 md:order-none md:w-auto">
© 2024-present by the Redot community.
{t("copyright.text")}&nbsp;
<span className="block lg:inline">
Website&nbsp;
{t("copyright.website")}&nbsp;
<Link
href={links.websiteGithub}
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-4 transition-all duration-300 hover:text-white/70"
>
source code on Github
{t("copyright.sourceCode")}
</Link>
</span>
</span>

<Button variant="secondary" asChild>
<Link href="/settings">Settings</Link>
<Link href="/settings">{t("buttons.settings")}</Link>
</Button>
</div>
</div>
Expand Down
50 changes: 50 additions & 0 deletions components/header-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useTranslations } from "next-intl";
import { motion } from "motion/react";
import { useInView } from "react-intersection-observer";

interface HeaderSelectionProps {
section: string;
}

export default function HeaderSection({ section }: HeaderSelectionProps) {
const { ref, inView } = useInView({
threshold: 0.1,
triggerOnce: true,
});

const t = useTranslations(section);

return (
<>
<div className="mx-auto max-w-[540px]" ref={ref}>
<div className="flex justify-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: 0.2 }}
className="flex h-9 items-center rounded-md border border-input bg-background px-3 text-center text-sm font-medium hover:bg-accent"
>
{t("badge")}
</motion.div>
</div>

<motion.h2
initial={{ opacity: 0, y: 20 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: 0.4 }}
className="mt-5 text-center text-4xl font-bold tracking-tighter md:text-[54px] md:leading-[60px]"
dangerouslySetInnerHTML={{ __html: t("title") }}
/>

<motion.p
initial={{ opacity: 0, y: 20 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: 0.6 }}
className="mt-5 text-center text-xl tracking-tighter text-black/60 md:text-[22px] md:leading-[30px]"
>
{t("description")}
</motion.p>
</div>
</>
);
}
11 changes: 7 additions & 4 deletions components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import Image from "next/image";
import { header } from "@/constants/header";
import { Button } from "@/components/ui/button";
import { MobileSidebar } from "@/components/mobile-navbar";
import { useTranslations } from "next-intl";

export const Header = () => {
const t = useTranslations("header");

return (
<header className="sticky top-0 z-[50] w-full backdrop-blur-sm">
<Banner
subMessage="Upgrade to the latest version."
mainMessage="Redot Engine is now stable!"
subMessage={t("banner.subMessage")}
mainMessage={t("banner.mainMessage")}
link="https://www.redotengine.org/news/release-4-3-stable"
/>
<div className="py-5">
Expand All @@ -37,11 +40,11 @@ export const Header = () => {
target="_blank"
rel="noopener noreferrer"
>
{link.label}
{t(link.label)}
</Link>
))}
<Button asChild>
<Link href="/download">Download</Link>
<Link href="/download">{t("downloadButton")}</Link>
</Button>
</nav>
</div>
Expand Down
Loading

0 comments on commit fd7fee7

Please sign in to comment.