feat: configure i18n with next-international

enable rewrite strategy to default language, move pages under /[locale], configure middleware and client/server utils
main
RaviAnand Mohabir 3 weeks ago
parent e424ae8d0d
commit a02c53a99d

@ -1,8 +1,8 @@
import { withPayload } from '@payloadcms/next/withPayload'
import { withPayload } from "@payloadcms/next/withPayload";
/** @type {import('next').NextConfig} */
const nextConfig = {
// Your Next.js config here
}
};
export default withPayload(nextConfig)
export default withPayload(nextConfig);

@ -29,6 +29,7 @@
"localbites": "file:",
"lucide-react": "^0.436.0",
"next": "15.0.0-canary.104",
"next-international": "^1.2.4",
"payload": "beta",
"react": "19.0.0-rc-06d0b89e-20240801",
"react-dom": "19.0.0-rc-06d0b89e-20240801",

@ -0,0 +1,6 @@
import { getPayload } from "@/utils/payload";
export const getAbout = async () => {
const payload = await getPayload();
return await payload.findGlobal({ slug: "about" });
};

@ -0,0 +1,6 @@
import { getPayload } from "@/utils/payload";
export const getContact = async () => {
const payload = await getPayload();
return await payload.findGlobal({ slug: "contact" });
};

@ -0,0 +1,6 @@
import { getPayload } from "@/utils/payload";
export const getGallery = async () => {
const payload = await getPayload();
return await payload.findGlobal({ slug: "gallery" });
};

@ -0,0 +1,6 @@
import { getPayload } from "@/utils/payload";
export const getHome = async () => {
const payload = await getPayload();
return await payload.findGlobal({ slug: "home" });
};

@ -0,0 +1,4 @@
export * from "./about";
export * from "./contact";
export * from "./gallery";
export * from "./home";

@ -0,0 +1,24 @@
import { Box, styled } from "@styled-system/jsx";
import { Metadata } from "next";
import RichText from "@/components/rich-text";
import { getAbout } from "@/api";
export default async function About() {
const about = await getAbout();
return (
<Box>
<styled.h1></styled.h1>
<RichText content={about.text!} />
</Box>
);
}
export async function generateMetadata(): Promise<Metadata> {
const about = await getAbout();
return {
title: about.name,
};
}

@ -1,10 +1,9 @@
import Carousel from "@/components/ui/carousel";
import { Media } from "@/payload-types";
import { getPayload } from "@/utils/payload";
import { getGallery } from "@/api";
export default async function Gallery() {
const payload = await getPayload();
const { images } = await payload.findGlobal({ slug: "gallery" });
const { images } = await getGallery();
return <Carousel images={images.map(({ image }) => image as Media)} w="100%" />;
}

@ -0,0 +1,47 @@
import "../../globals.css";
import Footer from "@/components/layout/footer";
import { I18nProviderClient } from "@/i18n/client";
import { Metadata } from "next";
import Navbar from "@/components/layout/navbar";
import { getAbout } from "@/api";
import localFont from "next/font/local";
import { locales } from "@/i18n/settings";
import { styled } from "@styled-system/jsx";
const moderustic = localFont({
src: "./Moderustic-VariableFont_wght.ttf",
display: "swap",
});
export default async function RootLayout({
params: { locale },
children,
}: {
params: { locale: string };
children: React.ReactNode;
}) {
return (
<html lang={locale}>
<styled.body className={moderustic.className}>
<Navbar />
<styled.main mt={20} pb={20}>
<I18nProviderClient locale={locale}>{children}</I18nProviderClient>{" "}
</styled.main>
<Footer />
</styled.body>
</html>
);
}
export async function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export async function generateMetadata(): Promise<Metadata> {
const about = await getAbout();
return {
title: about.name,
};
}

@ -1,4 +1,5 @@
import { Box, Container, Stack } from "@styled-system/jsx";
import { getAbout, getHome } from "@/api";
import Gallery from "./gallery";
import Image from "next/image";
@ -6,13 +7,11 @@ import { Media } from "@/payload-types";
import { Metadata } from "next";
import RichText from "@/components/rich-text";
import { css } from "@styled-system/css";
import { getPayload } from "@/utils/payload";
import { styled } from "@styled-system/jsx";
export default async function Home() {
const payload = await getPayload();
const home = await payload.findGlobal({ slug: "home" });
const about = await payload.findGlobal({ slug: "about" });
const home = await getHome();
const about = await getAbout();
return (
<Stack gap={10} align="center">
@ -43,12 +42,3 @@ export default async function Home() {
</Stack>
);
}
export async function generateMetadata(): Promise<Metadata> {
const payload = await getPayload();
const about = await payload.findGlobal({ slug: "about" });
return {
title: about.name,
};
}

@ -1 +0,0 @@
export default async function About() {}

@ -1,29 +0,0 @@
import "../globals.css";
import Footer from "@/components/layout/footer";
import Navbar from "@/components/layout/navbar";
import localFont from "next/font/local";
import { styled } from "@styled-system/jsx";
const moderustic = localFont({
src: "./Moderustic-VariableFont_wght.ttf",
display: "swap",
});
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<styled.body className={moderustic.className}>
<Navbar />
<styled.main mt={20} pb={20}>
{children}
</styled.main>
<Footer />
</styled.body>
</html>
);
}

@ -1,12 +1,12 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import configPromise from "@payload-config";
import "@payloadcms/next/css";
import { RootLayout } from "@payloadcms/next/layouts";
import "./custom.scss";
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import React from "react";
import "./custom.scss";
import { importMap } from "./admin/importMap";
import { RootLayout } from "@payloadcms/next/layouts";
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import configPromise from "@payload-config";
import { importMap } from "./importMap";
type Args = {
children: React.ReactNode;

@ -1,14 +0,0 @@
import configPromise from "@payload-config";
import { getPayload } from "payload";
export const GET = async () => {
const payload = await getPayload({
config: configPromise,
});
const data = await payload.find({
collection: "users",
});
return Response.json(data);
};

@ -1,17 +1,16 @@
import { Box, HStack, Stack, styled } from "@styled-system/jsx";
import { getAbout, getContact } from "@/api";
import { IconButton } from "@/components/ui/icon-button";
import { SiFacebook } from "@icons-pack/react-simple-icons";
import { getPayload } from "@/utils/payload";
import { stack } from "@styled-system/patterns";
export default async function Footer() {
const payload = await getPayload();
const about = await payload.findGlobal({ slug: "about" });
const contact = await payload.findGlobal({ slug: "contact" });
const about = await getAbout();
const contact = await getContact();
return (
<styled.footer h={60} p={8} className={stack({ gap: 4 })}>
<styled.footer h={60} p={8} className={stack({ gap: 4, justify: "end" })}>
<HStack justify="space-between" alignItems="start">
<Box>
<styled.p fontWeight="bold">{about.name}</styled.p>

@ -5,12 +5,13 @@ import MobileNav from "./mobile-nav";
import NavLink from "./nav-link";
import { css } from "@styled-system/css";
import { flex } from "@styled-system/patterns";
import { getPayload } from "@/utils/payload";
import { getAbout } from "@/api";
import { getI18n } from "@/i18n/server";
import { styled } from "@styled-system/jsx";
export default async function Navbar() {
const payload = await getPayload();
const about = await payload.findGlobal({ slug: "about" });
const t = await getI18n();
const about = await getAbout();
return (
<styled.nav
@ -44,10 +45,10 @@ export default async function Navbar() {
<styled.div flexGrow={1} />
<NavLink href="/about" className={css({ display: "none", sm: { display: "block" } })}>
Über uns
{t("general.about")}
</NavLink>
<NavLink href="/menu" className={css({ display: "none", sm: { display: "block" } })}>
Menü
{t("general.menu")}
</NavLink>
<NavLink
@ -55,7 +56,7 @@ export default async function Navbar() {
type="button"
className={css({ display: "none", sm: { display: "block" } })}
>
Kontakt
{t("general.contact")}
</NavLink>
<MobileNav />

@ -0,0 +1,8 @@
"use client";
import { createI18nClient } from "next-international/client";
export const { useI18n, useScopedI18n, I18nProviderClient } = createI18nClient({
de: () => import("./de"),
en: () => import("./en"),
});

@ -0,0 +1,7 @@
export default {
general: {
about: "Über uns",
menu: "Menü",
contact: "Kontakt",
},
} as const;

@ -0,0 +1,7 @@
export default {
general: {
about: "About",
menu: "Menu",
contact: "Contact",
},
} as const;

@ -0,0 +1,6 @@
import { createI18nServer } from "next-international/server";
export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer({
de: () => import("./de"),
en: () => import("./en"),
});

@ -0,0 +1,2 @@
export const defaultLocale = "de" as const;
export const locales = [defaultLocale, "en"] as const;

@ -0,0 +1,18 @@
import { defaultLocale, locales } from "@/i18n/settings";
import { NextRequest } from "next/server";
import { createI18nMiddleware } from "next-international/middleware";
const I18nMiddleware = createI18nMiddleware({
locales,
defaultLocale,
urlMappingStrategy: "rewriteDefault",
});
export function middleware(request: NextRequest) {
return I18nMiddleware(request);
}
export const config = {
matcher: ["/((?!api|admin|static|.*\\..*|_next|favicon.ico|robots.txt).*)"],
};

@ -47,13 +47,7 @@ export default buildConfig({
Vacation,
Holiday,
],
globals: [
Home,
Gallery,
About,
Contact,
Menu,
],
globals: [Home, Gallery, About, Contact, Menu],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || "",
typescript: {

@ -1402,6 +1402,45 @@
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz"
integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==
"@formatjs/ecma402-abstract@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17"
integrity sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==
dependencies:
"@formatjs/intl-localematcher" "0.5.4"
tslib "^2.4.0"
"@formatjs/fast-memoize@2.2.0", "@formatjs/fast-memoize@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz#33bd616d2e486c3e8ef4e68c99648c196887802b"
integrity sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==
dependencies:
tslib "^2.4.0"
"@formatjs/icu-messageformat-parser@2.7.8":
version "2.7.8"
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz#f6d7643001e9bb5930d812f1f9a9856f30fa0343"
integrity sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==
dependencies:
"@formatjs/ecma402-abstract" "2.0.0"
"@formatjs/icu-skeleton-parser" "1.8.2"
tslib "^2.4.0"
"@formatjs/icu-skeleton-parser@1.8.2":
version "1.8.2"
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz#2252c949ae84ee66930e726130ea66731a123c9f"
integrity sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==
dependencies:
"@formatjs/ecma402-abstract" "2.0.0"
tslib "^2.4.0"
"@formatjs/intl-localematcher@0.5.4", "@formatjs/intl-localematcher@^0.5.4":
version "0.5.4"
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz#caa71f2e40d93e37d58be35cfffe57865f2b366f"
integrity sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==
dependencies:
tslib "^2.4.0"
"@humanwhocodes/config-array@^0.11.14":
version "0.11.14"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz"
@ -4857,7 +4896,7 @@ cli-color@^2.0.2:
memoizee "^0.4.15"
timers-ext "^0.1.7"
client-only@0.0.1:
client-only@0.0.1, client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
@ -6347,6 +6386,21 @@ internal-slot@^1.0.4, internal-slot@^1.0.7:
hasown "^2.0.0"
side-channel "^1.0.4"
international-types@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/international-types/-/international-types-0.8.1.tgz#c0e593d9911c1a23f64bbd6eb1abb2941fe2353f"
integrity sha512-tajBCAHo4I0LIFlmQ9ZWfjMWVyRffzuvfbXCd6ssFt5u1Zw15DN0UBpVTItXdNa1ls+cpQt3Yw8+TxsfGF8JcA==
intl-messageformat@^10.5.14:
version "10.5.14"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.14.tgz#e5bb373f8a37b88fbe647d7b941f3ab2a37ed00a"
integrity sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==
dependencies:
"@formatjs/ecma402-abstract" "2.0.0"
"@formatjs/fast-memoize" "2.2.0"
"@formatjs/icu-messageformat-parser" "2.7.8"
tslib "^2.4.0"
ip-address@^9.0.5:
version "9.0.5"
resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz"
@ -6914,6 +6968,7 @@ lines-and-columns@^1.1.6:
version "1.0.0"
dependencies:
"@ark-ui/react" "^3.9.0"
"@icons-pack/react-simple-icons" "^10.0.0"
"@payloadcms/db-mongodb" beta
"@payloadcms/next" beta
"@payloadcms/plugin-cloud" beta
@ -6921,9 +6976,10 @@ lines-and-columns@^1.1.6:
"@payloadcms/translations" beta
cross-env "^7.0.3"
graphql "^16.8.1"
localbites "file:../../../AppData/Local/Yarn/Cache/v6/npm-localbites-1.0.0-fb528dd6-b1cf-42f8-b6fa-cc8c7e28e296-1724662317868/node_modules/localbites"
localbites "file:../../../AppData/Local/Yarn/Cache/v6/npm-localbites-1.0.0-e1440f51-13fa-4ff1-949c-87ecfbcb69c6-1724677077929/node_modules/localbites"
lucide-react "^0.436.0"
next "15.0.0-canary.104"
next-international "^1.2.4"
payload beta
react "19.0.0-rc-06d0b89e-20240801"
react-dom "19.0.0-rc-06d0b89e-20240801"
@ -7219,6 +7275,29 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
negotiator@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
next-international@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/next-international/-/next-international-1.2.4.tgz#abe50b2aa3ba7ecf92d41f87537796a4b2dd0ba3"
integrity sha512-JQvp+h2iSgA/t8hu5S/Lwow1ZErJutQRdpnplxjv4VTlCiND8T95fYih8BjkHcVhQbtM+Wu9Mb1CM32wD9hlWQ==
dependencies:
client-only "^0.0.1"
international-types "^0.8.1"
server-only "^0.0.1"
next-intl@^3.17.6:
version "3.17.6"
resolved "https://registry.yarnpkg.com/next-intl/-/next-intl-3.17.6.tgz#859ab4c406c7f421400174f2e42889e4726e3838"
integrity sha512-giPh2Us/C7B5P/60qdfHZMj9YPg5yXp6c9msFxv6Ua4zJt1IueNrKivInowN8FAwwSqEyC7TNYXBkXuG6oToTQ==
dependencies:
"@formatjs/intl-localematcher" "^0.5.4"
negotiator "^0.6.3"
use-intl "^3.17.6"
next-tick@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz"
@ -8208,6 +8287,11 @@ semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semve
resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
server-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/server-only/-/server-only-0.0.1.tgz#0f366bb6afb618c37c9255a314535dc412cd1c9e"
integrity sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==
set-function-length@^1.2.1:
version "1.2.2"
resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz"
@ -8951,6 +9035,14 @@ use-context-selector@2.0.0:
resolved "https://registry.npmjs.org/use-context-selector/-/use-context-selector-2.0.0.tgz"
integrity sha512-owfuSmUNd3eNp3J9CdDl0kMgfidV+MkDvHPpvthN5ThqM+ibMccNE0k+Iq7TWC6JPFvGZqanqiGCuQx6DyV24g==
use-intl@^3.17.6:
version "3.17.6"
resolved "https://registry.yarnpkg.com/use-intl/-/use-intl-3.17.6.tgz#88bc01ae8139315a1c7883b91c1160f4a53b1824"
integrity sha512-07wyVHSFSfI6M6TjhX/buZUFXnFymSz5LCimIMutTnBY11Vc/ym18j3HpZoQoWH+JYRKCCa4Jfm7NNyVKmgVww==
dependencies:
"@formatjs/fast-memoize" "^2.2.0"
intl-messageformat "^10.5.14"
use-isomorphic-layout-effect@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz"

Loading…
Cancel
Save