diff --git a/README.md b/README.md
index ac9ab6f..d8bffd4 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,9 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
## Setting Up the Environment
-It is recommended to use the same node and npm versions for development. An easy way to do so would be to use `nvm`. After installing `nvm`, run:
+It is recommended to use the same node and npm versions for development. An easy way to do so would be to use `nvm` ([Linux/MacOS](https://github.com/nvm-sh/nvm), [Windows](https://github.com/coreybutler/nvm-windows)).
+
+After installing `nvm`, run:
```bash
nvm use
@@ -21,21 +23,10 @@ npm run dev
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
-
-This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
-
-## Learn More
-
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+## References
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+This project uses the following libraries:
+- [NextJS](https://nextjs.org/docs)
+- [Next-Auth](https://next-auth.js.org/getting-started/example)
+- [Material UI](https://mui.com/material-ui/getting-started/)
+- [i18next](https://www.i18next.com/overview/getting-started)
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index b0f42d7..f8b745f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,11 +16,15 @@
"@mui/icons-material": "^5.15.0",
"@mui/material": "^5.15.0",
"@mui/material-nextjs": "^5.15.0",
+ "@unly/universal-language-detector": "^2.0.3",
+ "i18next": "^23.7.11",
+ "i18next-resources-to-backend": "^1.2.0",
"next": "14.0.4",
"next-auth": "^4.24.5",
"react": "^18",
"react-dom": "^18",
- "react-hook-form": "^7.49.2"
+ "react-hook-form": "^7.49.2",
+ "react-i18next": "^13.5.0"
},
"devDependencies": {
"@types/node": "^20",
@@ -1068,6 +1072,43 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
+ "node_modules/@unly/iso3166-1": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@unly/iso3166-1/-/iso3166-1-1.0.2.tgz",
+ "integrity": "sha512-aL/7cmcfjpwOaKFr9XHcWP/Z7lQjKLm5NMcjncT96aeSJxfblmPLnH/8lnX0GrWWFA2/CseCxnA73u5eiZcClA=="
+ },
+ "node_modules/@unly/universal-language-detector": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@unly/universal-language-detector/-/universal-language-detector-2.0.3.tgz",
+ "integrity": "sha512-UMmZIQS2XfJko8/GOrJc9ojO4UIF9HJM59NyQgcH+Ncrb0WBHL2L7EZ/7iZ/YcdoRnioonRECUQmmUELWt9z5Q==",
+ "dependencies": {
+ "@unly/iso3166-1": "1.0.2",
+ "@unly/utils": "1.0.3",
+ "accept-language-parser": "1.5.0",
+ "i18next": "19.0.2",
+ "i18next-browser-languagedetector": "4.0.1",
+ "lodash.get": "4.4.2",
+ "lodash.includes": "4.3.0"
+ }
+ },
+ "node_modules/@unly/universal-language-detector/node_modules/i18next": {
+ "version": "19.0.2",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-19.0.2.tgz",
+ "integrity": "sha512-fBa43Ann2udP1CQAz3IQpOZ1dGAkmi3mMfzisOhH17igneSRbvZ7P2RNbL+L1iRYKMufBmVwnC7G3gqcyviZ9g==",
+ "dependencies": {
+ "@babel/runtime": "^7.3.1"
+ }
+ },
+ "node_modules/@unly/utils": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@unly/utils/-/utils-1.0.3.tgz",
+ "integrity": "sha512-QTRknIDX56FvzGcIpBum5D/oRSlX3dkZ+l1op1jsFlYCTd925OGUb991V7zsFv3ePcqFfvfqfR5cNVv+w4JAOw=="
+ },
+ "node_modules/accept-language-parser": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz",
+ "integrity": "sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw=="
+ },
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
@@ -3003,6 +3044,14 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
"node_modules/human-signals": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
@@ -3012,6 +3061,44 @@
"node": ">=14.18.0"
}
},
+ "node_modules/i18next": {
+ "version": "23.7.11",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.7.11.tgz",
+ "integrity": "sha512-A/vOkw8vY99YHU9A1Td3I1dcTiYaPnwBWzrpVzfXUXSYgogK3cmBcmop/0cnXPc6QpUWIyqaugKNxRUEZVk9Nw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.0.1.tgz",
+ "integrity": "sha512-RxSoX6mB8cab0CTIQ+klCS764vYRj+Jk621cnFVsINvcdlb/cdi3vQFyrPwmnowB7ReUadjHovgZX+RPIzHVQQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5"
+ }
+ },
+ "node_modules/i18next-resources-to-backend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.0.tgz",
+ "integrity": "sha512-8f1l03s+QxDmCfpSXCh9V+AFcxAwIp0UaroWuyOx+hmmv8484GcELHs+lnu54FrNij8cDBEXvEwhzZoXsKcVpg==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
@@ -3610,6 +3697,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -4422,6 +4519,27 @@
"react": "^16.8.0 || ^17 || ^18"
}
},
+ "node_modules/react-i18next": {
+ "version": "13.5.0",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.5.0.tgz",
+ "integrity": "sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 23.2.3",
+ "react": ">= 16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@@ -5087,6 +5205,14 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
diff --git a/package.json b/package.json
index f83f509..677cae9 100644
--- a/package.json
+++ b/package.json
@@ -17,11 +17,15 @@
"@mui/icons-material": "^5.15.0",
"@mui/material": "^5.15.0",
"@mui/material-nextjs": "^5.15.0",
+ "@unly/universal-language-detector": "^2.0.3",
+ "i18next": "^23.7.11",
+ "i18next-resources-to-backend": "^1.2.0",
"next": "14.0.4",
"next-auth": "^4.24.5",
"react": "^18",
"react-dom": "^18",
- "react-hook-form": "^7.49.2"
+ "react-hook-form": "^7.49.2",
+ "react-i18next": "^13.5.0"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 59ee896..5f24ae0 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -1,4 +1,4 @@
-const Dashboard: React.FC = () => {
+const Dashboard: React.FC = async () => {
return "Dashboard (protected)";
};
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 705cdc8..9eb564b 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-
+import { detectLanguage } from "@/i18n";
import ThemeRegistry from "@/theme/ThemeRegistry";
export const metadata: Metadata = {
@@ -7,13 +7,15 @@ export const metadata: Metadata = {
description: "Generated by create next app",
};
-export default function RootLayout({
+export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
+ const lang = await detectLanguage();
+
return (
-
+
{children}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index 1708823..7f9d7d7 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -1,9 +1,8 @@
-import Grid from "@mui/material/Grid";
-import Paper from "@mui/material/Paper";
-import LoginForm from "./LoginForm";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@/config/authConfig";
+import { I18nProvider } from "@/i18n";
+import LoginPage from "@/components/LoginPage/LoginPage";
const Login: React.FC = async () => {
const session = await getServerSession(authOptions);
@@ -12,16 +11,9 @@ const Login: React.FC = async () => {
}
return (
-
-
- Hero
-
-
-
-
-
-
-
+
+
+
);
};
diff --git a/src/app/login/LoginForm.tsx b/src/components/LoginPage/LoginForm.tsx
similarity index 73%
rename from src/app/login/LoginForm.tsx
rename to src/components/LoginPage/LoginForm.tsx
index 9a8244a..38b1bab 100644
--- a/src/app/login/LoginForm.tsx
+++ b/src/components/LoginPage/LoginForm.tsx
@@ -5,10 +5,12 @@ import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
+import { TFunction } from "i18next";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
type LoginFields = {
username: string;
@@ -16,17 +18,21 @@ type LoginFields = {
};
// Error codes in https://next-auth.js.org/configuration/pages#sign-in-page
-const getHumanFriendlyErrorMessage = (serverError: string): string => {
+const getHumanFriendlyErrorMessage = (
+ t: TFunction,
+ serverError: string,
+): string => {
switch (serverError) {
case "CredentialsSignin":
- return "Invalid username or password.";
+ return t("Invalid username or password.");
case "Default":
default:
- return "Something went wrong. Please try again later.";
+ return t("Something went wrong. Please try again later.");
}
};
const LoginForm: React.FC = () => {
+ const { t } = useTranslation("login");
const {
register,
handleSubmit,
@@ -60,23 +66,23 @@ const LoginForm: React.FC = () => {
component="form"
onSubmit={handleSubmit(onSubmit)}
>
- Sign In
+ {t("Sign In")}
{serverError && (
- {getHumanFriendlyErrorMessage(serverError)}
+ {getHumanFriendlyErrorMessage(t, serverError)}
)}
);
diff --git a/src/components/LoginPage/LoginPage.tsx b/src/components/LoginPage/LoginPage.tsx
new file mode 100644
index 0000000..497d188
--- /dev/null
+++ b/src/components/LoginPage/LoginPage.tsx
@@ -0,0 +1,20 @@
+import Grid from "@mui/material/Grid";
+import Paper from "@mui/material/Paper";
+import LoginForm from "./LoginForm";
+
+const LoginPage = () => {
+ return (
+
+
+ test
+
+
+
+
+
+
+
+ );
+};
+
+export default LoginPage;
diff --git a/src/components/LoginPage/index.ts b/src/components/LoginPage/index.ts
new file mode 100644
index 0000000..f815230
--- /dev/null
+++ b/src/components/LoginPage/index.ts
@@ -0,0 +1 @@
+export { default } from "./LoginPage";
diff --git a/src/config/authConfig.ts b/src/config/authConfig.ts
index f2b689e..bc15325 100644
--- a/src/config/authConfig.ts
+++ b/src/config/authConfig.ts
@@ -1,7 +1,12 @@
-import { AuthOptions } from "next-auth";
+import { AuthOptions, Session } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { LOGIN_API_PATH } from "./api";
+interface SessionWithTokens extends Session {
+ accessToken?: string;
+ refreshToken?: string;
+}
+
export const authOptions: AuthOptions = {
debug: process.env.NODE_ENV === "development",
providers: [
@@ -33,10 +38,21 @@ export const authOptions: AuthOptions = {
},
callbacks: {
jwt(params) {
- return params.token;
+ // Add the data from user to the token
+ const { token, user } = params;
+ const newToken = { ...token, ...user };
+
+ return newToken;
},
- session(params) {
- return params.session;
+ session({ session, token }) {
+ const sessionWithToken: SessionWithTokens = {
+ ...session,
+ // Add the data from the token to the session
+ accessToken: token.accessToken as string | undefined,
+ refreshToken: token.refreshToken as string | undefined,
+ };
+
+ return sessionWithToken;
},
},
};
diff --git a/src/i18n/I18nClientProvider.tsx b/src/i18n/I18nClientProvider.tsx
new file mode 100644
index 0000000..0d2c13a
--- /dev/null
+++ b/src/i18n/I18nClientProvider.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import { createInstance, i18n } from "i18next";
+import React, { useMemo } from "react";
+import { I18nextProvider } from "react-i18next";
+
+interface Props {
+ children: React.ReactNode;
+ language: string;
+ resources: { [ns: string]: any };
+ namespaces: string[];
+}
+
+export const I18nContext = React.createContext(null);
+
+const I18nProvider: React.FC = ({
+ children,
+ language,
+ resources,
+ namespaces,
+}) => {
+ const i18n = useMemo(() => {
+ const instance = createInstance();
+ instance.init({
+ resources: {
+ [language]: resources,
+ },
+ fallbackLng: language,
+ interpolation: {
+ escapeValue: false,
+ },
+ ns: namespaces,
+ });
+ return instance as i18n;
+ // No need to check dependencies since this
+ // should only be created once from the server
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return {children};
+};
+
+export default I18nProvider;
diff --git a/src/i18n/en/login.json b/src/i18n/en/login.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/src/i18n/en/login.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json
new file mode 100644
index 0000000..080318d
--- /dev/null
+++ b/src/i18n/en/translation.json
@@ -0,0 +1,3 @@
+{
+ "test": "abc"
+}
\ No newline at end of file
diff --git a/src/i18n/index.tsx b/src/i18n/index.tsx
new file mode 100644
index 0000000..b404524
--- /dev/null
+++ b/src/i18n/index.tsx
@@ -0,0 +1,92 @@
+import { cookies, headers } from "next/headers";
+import { createInstance, i18n, LanguageDetectorAsyncModule } from "i18next";
+import resourcesToBackend from "i18next-resources-to-backend";
+import { getServerSession } from "next-auth";
+import { authOptions } from "@/config/authConfig";
+import I18nClientProvider from "./I18nClientProvider";
+import universalLanguageDetect from "@unly/universal-language-detector";
+
+const FALLBACK_LANG = "en";
+const SUPPORTED_LANGUAGES = ["en", "zh"];
+
+export const detectLanguage = async (): Promise => {
+ // Logic to get language preference from cookies/headers/session
+ const cookiesList = cookies();
+ const cookiesObj = cookiesList
+ .getAll()
+ .reduce<{ [name: string]: string }>(
+ (acc, cookie) => ({ ...acc, [cookie.name]: cookie.value }),
+ {},
+ );
+ const headersList = headers();
+ const session = await getServerSession(authOptions);
+
+ const lang = universalLanguageDetect({
+ supportedLanguages: SUPPORTED_LANGUAGES,
+ fallbackLanguage: FALLBACK_LANG,
+ acceptLanguageHeader: headersList.get("accept-language") || undefined,
+ serverCookies: cookiesObj,
+ });
+
+ return lang;
+};
+
+const languageDetector: LanguageDetectorAsyncModule = {
+ type: "languageDetector",
+ detect: detectLanguage,
+ async: true,
+};
+
+const initI18next = async (namespaces: string[]): Promise => {
+ const i18nInstance = createInstance();
+ await i18nInstance
+ .use(languageDetector)
+ .use(
+ resourcesToBackend((language: string, namespace: string) => {
+ return import(`./${language}/${namespace}.json`);
+ }),
+ )
+ .init({
+ fallbackLng: "en",
+ interpolation: {
+ escapeValue: false,
+ },
+ ns: namespaces,
+ });
+ return i18nInstance as i18n;
+};
+
+export const getServerI18n = async (...namespaces: string[]) => {
+ return initI18next(namespaces);
+};
+
+interface Props {
+ children: React.ReactNode;
+ namespaces: string[];
+}
+
+// Provides the resources for the client
+export const I18nProvider: React.FC = async ({
+ children,
+ namespaces,
+}) => {
+ const i18n = await getServerI18n(...namespaces);
+ const language = i18n.language;
+ const resources = namespaces.reduce<{ [ns: string]: any }>(
+ (acc, ns) => ({
+ ...acc,
+ [ns]: i18n.getResourceBundle(language, ns),
+ }),
+ {},
+ );
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/i18n/zh/login.json b/src/i18n/zh/login.json
new file mode 100644
index 0000000..da69471
--- /dev/null
+++ b/src/i18n/zh/login.json
@@ -0,0 +1,10 @@
+{
+ "Invalid username or password.": "帳號或密碼錯誤。",
+ "Something went wrong. Please try again later.": "出了些問題。請稍後再試。",
+ "Username": "帳號",
+ "Password": "密碼",
+ "Please enter a username": "請輸入帳號",
+ "Please enter a password": "請輸入密碼",
+ "Login": "登入",
+ "Sign In": "登入"
+}
\ No newline at end of file
diff --git a/src/i18n/zh/translation.json b/src/i18n/zh/translation.json
new file mode 100644
index 0000000..cd1d02f
--- /dev/null
+++ b/src/i18n/zh/translation.json
@@ -0,0 +1,3 @@
+{
+ "test": "def"
+}
\ No newline at end of file
diff --git a/src/middleware.ts b/src/middleware.ts
index 856504c..a701511 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -1,8 +1,30 @@
-import { withAuth } from "next-auth/middleware";
+import { NextRequestWithAuth, withAuth } from "next-auth/middleware";
import { authOptions } from "@/config/authConfig";
+import { NextFetchEvent, NextResponse } from "next/server";
-export default withAuth({
+const PUBLIC_ROUTES = ["/login"];
+const LANG_QUERY_PARAM = "lang";
+
+const authMiddleware = withAuth({
pages: authOptions.pages,
});
-export const config = { matcher: ["/dashboard"] };
+export default async function middleware(
+ req: NextRequestWithAuth,
+ event: NextFetchEvent,
+) {
+ const langPref = req.nextUrl.searchParams.get(LANG_QUERY_PARAM);
+ if (langPref) {
+ // Redirect to same url without the lang query param + set cookies
+ const newUrl = new URL(req.nextUrl);
+ newUrl.searchParams.delete(LANG_QUERY_PARAM);
+ const response = NextResponse.redirect(newUrl);
+ response.cookies.set("i18next", langPref);
+ return response;
+ }
+
+ // Matcher for using the auth middleware
+ return PUBLIC_ROUTES.some((route) => req.nextUrl.pathname.startsWith(route))
+ ? NextResponse.next() // Return normal response
+ : await authMiddleware(req, event); // Let auth middleware handle response
+}