Skip to content

Commit 8cefab9

Browse files
authored
feat: add i18n (#157)
1 parent 4197f07 commit 8cefab9

15 files changed

Lines changed: 250 additions & 18 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- 🗺️ Sitemap & robots.txt
3535
- 💯 Perfect Lighthouse score
3636
- 💾 Neon database
37+
- 🌐 I18n with next-intl
3738

3839
## 🚀 Deployment
3940
Easily deploy your Next.js app with <a href="https://vercel.com/">Vercel</a> by clicking the button below:

messages/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"home": {
3+
"signIn": "Sign in",
4+
"signOut": "Sign out",
5+
"subtitle": "A Next.js starter template, packed with features like TypeScript, Tailwind CSS, Next-auth, Eslint, Stripe, testing tools and more. Jumpstart your project with efficiency and style.",
6+
"getStartedButton": "Get started"
7+
}
8+
}

messages/pl.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"home": {
3+
"signIn": "Zaloguj się",
4+
"signOut": "Wyloguj się",
5+
"subtitle": "Szablon startowy Next.js, pełen funkcji takich jak TypeScript, Tailwind CSS, Next-auth, Eslint, Stripe, narzędzia testowe i wiele innych. Rozpocznij swój projekt wydajnie i stylowo.",
6+
"getStartedButton": "Zacznij"
7+
}
8+
}

next.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { NextConfig } from "next";
2+
import createNextIntlPlugin from "next-intl/plugin";
23

34
const nextConfig: NextConfig = {
45
allowedDevOrigins: ["127.0.0.1"],
@@ -12,4 +13,5 @@ const nextConfig: NextConfig = {
1213
},
1314
};
1415

15-
export default nextConfig;
16+
const withNextIntl = createNextIntlPlugin();
17+
export default withNextIntl(nextConfig);

package-lock.json

Lines changed: 129 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"lucide-react": "^0.536.0",
4242
"next": "15.4.5",
4343
"next-auth": "^5.0.0-beta.28",
44+
"next-intl": "^4.3.4",
4445
"next-themes": "^0.4.6",
4546
"react": "^19.1.1",
4647
"react-dom": "^19.1.1",
Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import "@/styles/globals.css";
22

33
import type { Metadata } from "next";
4-
import { PropsWithChildren } from "react";
4+
import { notFound } from "next/navigation";
5+
import { hasLocale, NextIntlClientProvider } from "next-intl";
56

7+
import { LangSwitcher } from "@/components/lang-switcher";
68
import { ThemeProvider } from "@/components/theme-provider";
79
import { ThemeSwitcher } from "@/components/theme-switcher";
10+
import { routing } from "@/i18n/routing";
811
import { fonts } from "@/lib/fonts";
912
import { siteConfig } from "@/lib/site-config";
1013
import { cn } from "@/lib/utils";
@@ -42,14 +45,29 @@ export const metadata: Metadata = {
4245
},
4346
};
4447

45-
const RootLayout = ({ children }: PropsWithChildren) => {
48+
const RootLayout = async ({
49+
children,
50+
params,
51+
}: {
52+
children: React.ReactNode;
53+
params: Promise<{ locale: string }>;
54+
}) => {
55+
const { locale } = await params;
56+
57+
if (!hasLocale(routing.locales, locale)) {
58+
notFound();
59+
}
60+
4661
return (
47-
<html lang="en" suppressHydrationWarning>
62+
<html lang={locale} suppressHydrationWarning>
4863
<body className={cn("min-h-screen font-sans", fonts)}>
49-
<ThemeProvider attribute="class">
50-
{children}
51-
<ThemeSwitcher className="absolute right-5 bottom-5 z-10" />
52-
</ThemeProvider>
64+
<NextIntlClientProvider>
65+
<ThemeProvider attribute="class">
66+
{children}
67+
<LangSwitcher className="absolute right-5 bottom-16 z-10" />
68+
<ThemeSwitcher className="absolute right-5 bottom-5 z-10" />
69+
</ThemeProvider>
70+
</NextIntlClientProvider>
5371
</body>
5472
</html>
5573
);
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Link from "next/link";
2+
import { getTranslations } from "next-intl/server";
23

34
import { AuthControls } from "@/components/auth-controls";
45
import { Icons } from "@/components/icons";
@@ -8,12 +9,13 @@ import { auth } from "@/lib/auth";
89

910
const HomePage = async () => {
1011
const session = await auth();
12+
const t = await getTranslations("home");
1113

1214
return (
1315
<>
1416
<header className="w-full border-b">
1517
<div className="container flex h-16 items-center justify-between">
16-
<Link href="/" className="font-mono text-lg font-bold">
18+
<Link href="#" className="font-mono text-lg font-bold">
1719
next-starter
1820
</Link>
1921
<div className="flex items-center gap-2">
@@ -29,9 +31,7 @@ const HomePage = async () => {
2931
starter template
3032
</h1>
3133
<p className="text-muted-foreground max-w-2xl md:text-lg">
32-
A Next.js starter template, packed with features like TypeScript,
33-
Tailwind CSS, Next-auth, Eslint, Stripe, testing tools and more.
34-
Jumpstart your project with efficiency and style.
34+
{t("subtitle")}
3535
</p>
3636
<div className="mt-2 flex gap-4">
3737
{session ? (
@@ -42,7 +42,7 @@ const HomePage = async () => {
4242
target="_blank"
4343
className={buttonVariants({ size: "lg" })}
4444
>
45-
Get started
45+
{t("getStartedButton")}
4646
</Link>
4747
)}
4848
<Link
@@ -55,7 +55,7 @@ const HomePage = async () => {
5555
</div>
5656
</section>
5757
<footer className="text-muted-foreground absolute bottom-3 w-full text-center text-sm">
58-
© {new Date().getFullYear()} By{" "}
58+
© {new Date().getFullYear()}{" "}
5959
<Link
6060
href="https://michalskolak.pl"
6161
className={buttonVariants({ variant: "link", className: "!p-0" })}

src/components/auth-controls.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import Image from "next/image";
44
import { Session } from "next-auth";
55
import { signIn, signOut } from "next-auth/react";
6+
import { useTranslations } from "next-intl";
67

78
import { Button } from "@/components/ui/button";
89

@@ -11,13 +12,15 @@ type AuthControlsProps = {
1112
};
1213

1314
export const AuthControls = ({ session }: AuthControlsProps) => {
15+
const t = useTranslations("home");
16+
1417
if (!session)
1518
return (
1619
<Button
1720
className="cursor-pointer"
1821
onClick={async () => await signIn("github")}
1922
>
20-
Sign in
23+
{t("signIn")}
2124
</Button>
2225
);
2326

@@ -33,7 +36,7 @@ export const AuthControls = ({ session }: AuthControlsProps) => {
3336
height={32}
3437
/>
3538
<Button className="cursor-pointer" onClick={async () => await signOut()}>
36-
Sign out
39+
{t("signOut")}
3740
</Button>
3841
</>
3942
);

src/components/lang-switcher.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Link from "next/link";
2+
import { useLocale } from "next-intl";
3+
import { ComponentProps } from "react";
4+
5+
import { buttonVariants } from "@/components/ui/button";
6+
import { cn } from "@/lib/utils";
7+
8+
type LangSwitcherProps = {
9+
className?: ComponentProps<typeof Link>["className"];
10+
};
11+
12+
export const LangSwitcher = ({ className }: LangSwitcherProps) => {
13+
const locale = useLocale();
14+
15+
return (
16+
<Link
17+
className={cn(
18+
buttonVariants({ variant: "outline", size: "icon" }),
19+
className,
20+
)}
21+
href={locale === "en" ? "/pl" : "/en"}
22+
>
23+
{locale === "en" ? "PL" : "EN"}
24+
</Link>
25+
);
26+
};

0 commit comments

Comments
 (0)