Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] - Add translation next-intl #15

Merged
merged 4 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import createNextIntlPlugin from "next-intl/plugin";

const withNextIntl = createNextIntlPlugin("./src/lib/i18n.ts");

/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
Expand All @@ -10,4 +14,4 @@ const nextConfig = {
},
};

export default nextConfig;
export default withNextIntl(nextConfig);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions src/app/[locale]/events/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import EventsPage from "@/features/events";
import { unstable_setRequestLocale } from "next-intl/server";

type Props = {
params: { locale: string };
};
const Events = ({ params: { locale } }: Props) => {
unstable_setRequestLocale(locale);
return <EventsPage />;
};
export default Events;
File renamed without changes.
35 changes: 35 additions & 0 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<html lang={locale}>
<head>
<link rel="icon" href="/assets/icons/ic_hmc-dark.svg" sizes="any" />
</head>
<body className={sora.className}>
<NextIntlClientProvider messages={messages}>
<Wrapper>{children}</Wrapper>
</NextIntlClientProvider>
</body>
</html>
);
}
5 changes: 5 additions & 0 deletions src/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Home from "@/features/home";

export default function HomePage() {
return <Home />;
}
8 changes: 0 additions & 8 deletions src/app/events/page.tsx

This file was deleted.

29 changes: 5 additions & 24 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<html lang="en">
<head>
<link rel="icon" href="/assets/icons/ic_hmc-dark.svg" sizes="any" />
</head>
<body className={sora.className}>
<Wrapper>{children}</Wrapper>
</body>
</html>
);
export default function RootLayout({ children }: Props) {
return children;
}
17 changes: 13 additions & 4 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
const NotFound = () => {
return <div>404</div>;
};
export default NotFound;
"use client";

import Error from "next/error";

export default function NotFound() {
return (
<html lang="en">
<body>
<Error statusCode={404} />
</body>
</html>
);
}
6 changes: 3 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Home from "@/features/home";
import { redirect } from "next/navigation";

export default function HomePage() {
return <Home />;
export default function RootPage() {
redirect("/en");
}
54 changes: 54 additions & 0 deletions src/components/common/locale-toggle/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="outline" className={`w-[3rem] font-normal`} disabled={isPending}>
{locale === "id" ? "🇮🇩 ID" : "🇬🇧 EN"}
<span className="sr-only">Toggle locale</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[3rem]">
<DropdownMenuItem onClick={() => handleSwitch("id")} className="cursor-pointer">
🇮🇩 ID
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleSwitch("en")} className="cursor-pointer">
🇬🇧 EN
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

export default LocaleToggle;
14 changes: 7 additions & 7 deletions src/components/common/navbar/NavbarList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Link from "next/link";
import { Link } from "@/lib/navigation";
import { ComponentProps } from "react";
import { pathnames } from "@/lib/config";

import { NavbarListType } from "./types";

const NavbarList = ({ title, link }: NavbarListType) => {
function NavbarList<Pathname extends keyof typeof pathnames>({ href, ...rest }: ComponentProps<typeof Link<Pathname>>) {
return (
<Link href={link}>
<span>{title}</span>
<Link href={href} {...rest}>
<span>{rest.title}</span>
</Link>
);
};
}

export default NavbarList;
16 changes: 0 additions & 16 deletions src/components/common/navbar/constants.tsx

This file was deleted.

17 changes: 11 additions & 6 deletions src/components/common/navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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";

const Navbar = () => {
const t = useTranslations("Layout");
return (
<header className="border-b">
<div className="max-w-7xl mx-auto p-5">
Expand All @@ -14,10 +16,13 @@ const Navbar = () => {
</Link>

<nav className="flex items-center gap-4">
{navbarLists.map(({ link, title }) => (
<NavbarList key={link} title={title} link={link} />
))}
<ModeToggle />
<NavbarList href="/about" title={t("navbar.link-1")} />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better di map keknya ini lung @mpsalunggg

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lupa awkwkwk:') @vickyadrii

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sikat, nanti mention aje kalo sudah @mpsalunggg

<NavbarList href="/events" title={t("navbar.link-2")} />
<NavbarList href="/contact" title={t("navbar.link-3")} />
<div className="flex gap-2 items-center">
<ModeToggle />
<LocaleToggle />
</div>
</nav>
</div>
</div>
Expand Down
4 changes: 0 additions & 4 deletions src/components/common/navbar/types.tsx

This file was deleted.

6 changes: 4 additions & 2 deletions src/features/events/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<div className="w-full bg-slate-100 dark:bg-hmc-primary-foreground rounded-lg">
<div className="container p-4 h-32 flex flex-wrap gap-1 justify-between items-center">
<div className="">
<h1 className="text-hmc-primary text-xl sm:text-3xl font-semibold">Events</h1>
<p className="text-hmc-primary text-xs sm:text-base">Ayo ikuti berbagai Event dan Agenda menarik</p>
<h1 className="text-hmc-primary text-xl sm:text-3xl font-semibold">{t("title")}</h1>
<p className="text-hmc-primary text-xs sm:text-base">{t("description")}</p>
</div>
<div className="w-full sm:w-auto">
<Select>
Expand Down
6 changes: 4 additions & 2 deletions src/features/home/components/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import Image from "next/image";
import { socialMedia } from "../constants";
import { useTranslations } from "next-intl";

const HeroSection = () => {
const t = useTranslations("HomePage");
return (
<div className="flex md:flex-row flex-col items-center gap-6 justify-between">
<div className="basis-1/2">
<div className="space-y-4">
<div className="space-y-1">
<h1 className="text-hmc-primary md:text-5xl text-3xl font-bold md:leading-[60px]">
Improve Your Skill by Talking High-Quality Classes with Us!
{t("section-hero.title")}
</h1>
<p className="text-slate-600">Hammercode is a tech community based in Palu, Indonesia</p>
<p className="text-slate-600">{t("section-hero.description")}</p>
</div>

{/* Social Media */}
Expand Down
22 changes: 22 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Pathnames, LocalePrefix } from "next-intl/routing";

export const defaultLocale = "en" as const;
export const locales = ["en", "id"] as const;

export const pathnames = {
"/": "/",
"/about": {
en: "/about",
id: "/tentang",
},
"/events": {
en: "/events",
id: "/acara",
},
"/contact": {
en: "/contact",
id: "/kontak",
},
} satisfies Pathnames<typeof locales>;

export const localePrefix: LocalePrefix<typeof locales> = "always";
13 changes: 13 additions & 0 deletions src/lib/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
import { locales } from "./config";

export type Locale = (typeof locales)[number];

export default getRequestConfig(async ({ locale }) => {
if (!locales.includes(locale as Locale)) notFound();

return {
messages: (await (locale === "en" ? import("../locales/en.json") : import(`../locales/${locale}.json`))).default,
};
});
7 changes: 7 additions & 0 deletions src/lib/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createLocalizedPathnamesNavigation } from "next-intl/navigation";
import { locales, pathnames, localePrefix } from "./config";
export const { Link, getPathname, redirect, usePathname, useRouter } = createLocalizedPathnamesNavigation({
locales,
pathnames,
localePrefix,
});
19 changes: 19 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"Layout": {
"navbar": {
"link-1": "About",
"link-2": "Events",
"link-3": "Contact"
}
},
"HomePage": {
"section-hero": {
"title": "Improve Your Skill by Talking High-Quality Classes with Us!",
"description": "Hammercode is a tech community based in Palu, Indonesia"
}
},
"EventsPage": {
"title": "Events",
"description": "Join the fun events and have an unforgettable experience!"
}
}
Loading