diff --git a/next.config.mjs b/next.config.mjs index e8b1ccf..23e3526 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,7 @@ +import createNextIntlPlugin from "next-intl/plugin"; + +const withNextIntl = createNextIntlPlugin("./src/lib/i18n.ts"); + /** @type {import('next').NextConfig} */ const nextConfig = { images: { @@ -10,4 +14,4 @@ const nextConfig = { }, }; -export default nextConfig; +export default withNextIntl(nextConfig); diff --git a/package.json b/package.json index 37eba83..4a4d492 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "clsx": "^2.1.1", "lucide-react": "^0.428.0", "next": "14.2.5", + "next-intl": "^3.17.6", "next-themes": "^0.3.0", "react": "^18", "react-dom": "^18", diff --git a/src/app/events/page.tsx b/src/app/[locale]/events/page.tsx similarity index 63% rename from src/app/events/page.tsx rename to src/app/[locale]/events/page.tsx index 24fe400..996387e 100644 --- a/src/app/events/page.tsx +++ b/src/app/[locale]/events/page.tsx @@ -1,8 +1,6 @@ -import { FC } from "react"; - import EventsPage from "@/features/events"; -const Events: FC = () => { +const Events = () => { return ; }; export default Events; diff --git a/src/app/globals.css b/src/app/[locale]/globals.css similarity index 100% rename from src/app/globals.css rename to src/app/[locale]/globals.css diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx new file mode 100644 index 0000000..d94d0e8 --- /dev/null +++ b/src/app/[locale]/layout.tsx @@ -0,0 +1,35 @@ +import type { Metadata } from "next"; +import { NextIntlClientProvider } from "next-intl"; +import { getMessages } from "next-intl/server"; +import { Sora } from "next/font/google"; +import "./globals.css"; +import Wrapper from "@/components/layout/wrapper"; +const sora = Sora({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Welcome to Hammercode!", + description: "Hammercode is a community based in Palu, Indonesia", +}; + +export default async function LocaleRootLayout({ + children, + params: { locale }, +}: Readonly<{ + children: React.ReactNode; + params: { locale: string }; +}>) { + const messages = await getMessages(); + + return ( + + + + + + + {children} + + + + ); +} diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx new file mode 100644 index 0000000..ecde379 --- /dev/null +++ b/src/app/[locale]/page.tsx @@ -0,0 +1,5 @@ +import Home from "@/features/home"; + +export default function HomePage() { + return ; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0313011..568d357 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,28 +1,9 @@ -import type { Metadata } from "next"; -import { Sora } from "next/font/google"; -import "./globals.css"; -import Wrapper from "@/components/layout/wrapper"; +import { ReactNode } from "react"; -const sora = Sora({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Welcome to Hammercode!", - description: "Hammercode is a community based in Palu, Indonesia", +type Props = { + children: ReactNode; }; -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - - - - {children} - - - ); +export default function RootLayout({ children }: Props) { + return children; } diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index a23b2bc..23405db 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,4 +1,13 @@ -const NotFound = () => { - return
404
; -}; -export default NotFound; +"use client"; + +import Error from "next/error"; + +export default function NotFound() { + return ( + + + + + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index ecde379..77d15e3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ -import Home from "@/features/home"; +import { redirect } from "next/navigation"; -export default function HomePage() { - return ; +export default function RootPage() { + redirect("/en"); } diff --git a/src/components/common/locale-toggle/index.tsx b/src/components/common/locale-toggle/index.tsx new file mode 100644 index 0000000..acc497a --- /dev/null +++ b/src/components/common/locale-toggle/index.tsx @@ -0,0 +1,54 @@ +import { useTransition } from "react"; +import { useLocale } from "next-intl"; +import { useParams } from "next/navigation"; + +import { usePathname, useRouter } from "@/lib/navigation"; +import { Locale } from "@/lib/i18n"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Button } from "@/components/ui/button"; + +const LocaleToggle = () => { + const locale = useLocale(); + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const pathname = usePathname(); + const params = useParams(); + + const handleSwitch = (value: string) => { + const nextLocale = value as Locale; + startTransition(() => { + router.replace( + // @ts-expect-error -- TypeScript will validate that only known `params` + // are used in combination with a given `pathname`. Since the two will + // always match for the current route, we can skip runtime checks. + { pathname, params }, + { locale: nextLocale } + ); + }); + }; + return ( + + + + + + handleSwitch("id")} className="cursor-pointer"> + 🇮🇩 ID + + handleSwitch("en")} className="cursor-pointer"> + 🇬🇧 EN + + + + ); +}; + +export default LocaleToggle; diff --git a/src/components/common/modetoggle/index.tsx b/src/components/common/mode-toggle/index.tsx similarity index 100% rename from src/components/common/modetoggle/index.tsx rename to src/components/common/mode-toggle/index.tsx diff --git a/src/components/common/navbar/NavbarList.tsx b/src/components/common/navbar/NavbarList.tsx index 3920fc1..09ae151 100644 --- a/src/components/common/navbar/NavbarList.tsx +++ b/src/components/common/navbar/NavbarList.tsx @@ -1,13 +1,15 @@ -import Link from "next/link"; +import { Link } from "@/lib/navigation"; +import { ComponentProps } from "react"; +import { pathnames } from "@/lib/config"; -import { NavbarListType } from "./types"; +export type NavbarListProps = ComponentProps>; -const NavbarList = ({ title, link }: NavbarListType) => { +function NavbarList({ href, ...rest }: NavbarListProps) { return ( - - {title} + + {rest.title} ); -}; +} export default NavbarList; diff --git a/src/components/common/navbar/constant.ts b/src/components/common/navbar/constant.ts new file mode 100644 index 0000000..35c0f6f --- /dev/null +++ b/src/components/common/navbar/constant.ts @@ -0,0 +1,17 @@ +import { NavbarListProps } from "./NavbarList"; +import { ValidPathnames } from "./type"; + +export const LINK: NavbarListProps[] = [ + { + id: "1", + href: "/about", + }, + { + id: "2", + href: "/events", + }, + { + id: "3", + href: "/contact", + }, +]; diff --git a/src/components/common/navbar/constants.tsx b/src/components/common/navbar/constants.tsx deleted file mode 100644 index 27b9e20..0000000 --- a/src/components/common/navbar/constants.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { NavbarListType } from "./types"; - -export const navbarLists: NavbarListType[] = [ - { - title: "About", - link: "/about", - }, - { - title: "Contact", - link: "/contact", - }, - { - title: "Event", - link: "/events", - }, -]; diff --git a/src/components/common/navbar/index.tsx b/src/components/common/navbar/index.tsx index cfe78ff..b8301df 100644 --- a/src/components/common/navbar/index.tsx +++ b/src/components/common/navbar/index.tsx @@ -1,10 +1,13 @@ import Link from "next/link"; import NavbarList from "./NavbarList"; -import { navbarLists } from "./constants"; -import { ModeToggle } from "../modetoggle"; +import { ModeToggle } from "../mode-toggle"; +import { useTranslations } from "next-intl"; +import LocaleToggle from "../locale-toggle"; +import { LINK } from "./constant"; const Navbar = () => { + const t = useTranslations("Layout"); return (
@@ -14,10 +17,13 @@ const Navbar = () => {
diff --git a/src/components/common/navbar/type.ts b/src/components/common/navbar/type.ts new file mode 100644 index 0000000..c2c6eac --- /dev/null +++ b/src/components/common/navbar/type.ts @@ -0,0 +1,7 @@ +import { pathnames } from "@/lib/config"; + +export type PathnamesKeys = keyof typeof pathnames; +export type PathnameValues = { + [K in PathnamesKeys]: (typeof pathnames)[K] extends string ? string : keyof (typeof pathnames)[K]; +}; +export type ValidPathnames = PathnamesKeys; diff --git a/src/components/common/navbar/types.tsx b/src/components/common/navbar/types.tsx deleted file mode 100644 index 1d9097d..0000000 --- a/src/components/common/navbar/types.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export type NavbarListType = { - title: string; - link: string; -}; diff --git a/src/features/events/index.tsx b/src/features/events/index.tsx index 6fdd237..cc0cd06 100644 --- a/src/features/events/index.tsx +++ b/src/features/events/index.tsx @@ -2,15 +2,17 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { eventsData } from "./constants"; import CardEvent from "./components/Card"; +import { useTranslations } from "next-intl"; const Events = () => { + const t = useTranslations("EventsPage"); return (
-

Events

-

Ayo ikuti berbagai Event dan Agenda menarik

+

{t("title")}

+

{t("description")}