From 7da65cea6250007627e471b26941d7d7575aa07d Mon Sep 17 00:00:00 2001 From: Gabriel Majeri Date: Sun, 14 Jan 2024 23:28:47 +0200 Subject: [PATCH] Set up `next-intl` --- next.config.js | 4 +- package-lock.json | 155 +++++++++++++++++++++++++++++ package.json | 1 + src/app/{ => [locale]}/globals.css | 0 src/app/{ => [locale]}/layout.tsx | 6 +- src/app/{ => [locale]}/page.tsx | 8 +- src/i18n.ts | 15 +++ src/messages/en.json | 6 ++ src/messages/ro.json | 6 ++ src/middleware.ts | 15 +++ 10 files changed, 212 insertions(+), 4 deletions(-) rename src/app/{ => [locale]}/globals.css (100%) rename src/app/{ => [locale]}/layout.tsx (84%) rename src/app/{ => [locale]}/page.tsx (97%) create mode 100644 src/i18n.ts create mode 100644 src/messages/en.json create mode 100644 src/messages/ro.json create mode 100644 src/middleware.ts diff --git a/next.config.js b/next.config.js index 658404a..9b599a8 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,6 @@ +const withNextIntl = require("next-intl/plugin")(); + /** @type {import('next').NextConfig} */ const nextConfig = {}; -module.exports = nextConfig; +module.exports = withNextIntl(nextConfig); diff --git a/package-lock.json b/package-lock.json index 1e03e31..8e0895e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "next": "14.0.4", + "next-intl": "^3.4.2", "react": "^18", "react-dom": "^18" }, @@ -114,6 +115,92 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", + "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", + "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz", + "integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz", + "integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.11.4", + "@formatjs/icu-skeleton-parser": "1.3.6", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/ecma402-abstract": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", + "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.25", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/intl-localematcher": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", + "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz", + "integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/ecma402-abstract": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", + "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.25", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/intl-localematcher": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", + "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", + "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -2385,6 +2472,34 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz", + "integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.11.4", + "@formatjs/fast-memoize": "1.2.1", + "@formatjs/icu-messageformat-parser": "2.1.0", + "tslib": "^2.1.0" + } + }, + "node_modules/intl-messageformat/node_modules/@formatjs/ecma402-abstract": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", + "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.25", + "tslib": "^2.1.0" + } + }, + "node_modules/intl-messageformat/node_modules/@formatjs/intl-localematcher": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", + "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -3025,6 +3140,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "14.0.4", "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", @@ -3071,6 +3194,26 @@ } } }, + "node_modules/next-intl": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.4.2.tgz", + "integrity": "sha512-DHAMtXfuItIGeNv5pUYqwHRWBmyL47dyuS1BDoG1dRUq+drcV5xk8eygD0n7xicD8yYUhgGcLMhNajEwRwpWNQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "dependencies": { + "@formatjs/intl-localematcher": "^0.2.32", + "negotiator": "^0.6.3", + "use-intl": "^3.4.2" + }, + "peerDependencies": { + "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -4533,6 +4676,18 @@ "punycode": "^2.1.0" } }, + "node_modules/use-intl": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.4.2.tgz", + "integrity": "sha512-+9xUu01CqkxV0fqMXmxi08gUNCcWGSDFmSHiMSzodRXWGBPFeXcnhQXQ5UOGoCUQygW6vH+dntyXTKtb4hefYw==", + "dependencies": { + "@formatjs/ecma402-abstract": "^1.11.4", + "intl-messageformat": "^9.3.18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index aa8b112..29f9a52 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "next": "14.0.4", + "next-intl": "^3.4.2", "react": "^18", "react-dom": "^18" }, 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/layout.tsx b/src/app/[locale]/layout.tsx similarity index 84% rename from src/app/layout.tsx rename to src/app/[locale]/layout.tsx index 323bd9c..c36aad2 100644 --- a/src/app/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -11,11 +11,15 @@ export const metadata: Metadata = { export default function RootLayout({ children, + params: { locale }, }: { children: React.ReactNode; + params: { + locale: string; + }; }) { return ( - + {children} ); diff --git a/src/app/page.tsx b/src/app/[locale]/page.tsx similarity index 97% rename from src/app/page.tsx rename to src/app/[locale]/page.tsx index 992bc44..75c3d91 100644 --- a/src/app/page.tsx +++ b/src/app/[locale]/page.tsx @@ -1,11 +1,15 @@ import Image from "next/image"; +import { useTranslations } from "next-intl"; + export default function Home() { + const t = useTranslations("Index"); + return (

- Get started by editing  + {t("getStarted")}  src/app/page.tsx

@@ -15,7 +19,7 @@ export default function Home() { target="_blank" rel="noopener noreferrer" > - By{" "} + {t("by")}{" "} Vercel Logo { + // Validate that the incoming `locale` parameter is valid + if (!locales.includes(locale as any)) { + notFound(); + } + + return { + messages: (await import(`./messages/${locale}.json`)).default, + }; +}); diff --git a/src/messages/en.json b/src/messages/en.json new file mode 100644 index 0000000..ced1954 --- /dev/null +++ b/src/messages/en.json @@ -0,0 +1,6 @@ +{ + "Index": { + "getStarted": "Get started by editing", + "by": "By" + } +} diff --git a/src/messages/ro.json b/src/messages/ro.json new file mode 100644 index 0000000..97bbfe0 --- /dev/null +++ b/src/messages/ro.json @@ -0,0 +1,6 @@ +{ + "Index": { + "getStarted": "Începe prin a edita fișierul", + "by": "De" + } +} diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..6ce780a --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,15 @@ +import createMiddleware from "next-intl/middleware"; +import { locales } from "./i18n"; + +export default createMiddleware({ + // A list of all locales that are supported + locales, + + // Used when no locale matches + defaultLocale: "ro", +}); + +export const config = { + // Match only internationalized pathnames + matcher: ["/", "/(en|ro)/:path*"], +};