├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── [locale] │ ├── about │ │ ├── page.tsx │ │ └── profile │ │ │ └── page.tsx │ ├── layout.tsx │ └── page.tsx ├── favicon.ico ├── fonts │ ├── GeistMonoVF.woff │ └── GeistVF.woff └── globals.css ├── components └── Navbar.tsx ├── i18n.ts ├── messages ├── en.json └── fr.json ├── middleware.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── file-text.svg ├── globe.svg ├── next.svg ├── vercel.svg └── window.svg ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # env files (can opt-in for commiting if needed) 29 | .env* 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Watch Tutorial: https://www.youtube.com/watch?v=2Jh9olZXBfw 2 | 3 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app). 4 | 5 | ## Getting Started 6 | 7 | First, run the development server: 8 | 9 | ```bash 10 | npm run dev 11 | # or 12 | yarn dev 13 | # or 14 | pnpm dev 15 | # or 16 | bun dev 17 | ``` 18 | 19 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 20 | 21 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | 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. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 39 | -------------------------------------------------------------------------------- /app/[locale]/about/page.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { getMessages } from "next-intl/server"; 3 | import React from "react"; 4 | 5 | export async function generateMetadata({ 6 | params: { locale }, 7 | }: { 8 | params: { locale: string }; 9 | }) { 10 | const messages: any = await getMessages({ locale }); 11 | const title = messages.NavbarLinks.aboutTitle; 12 | 13 | return { 14 | title, 15 | }; 16 | } 17 | 18 | const AboutPage = () => { 19 | const t = useTranslations("AboutPage"); 20 | return ( 21 |
22 |

{t("title")}

23 |
24 | ); 25 | }; 26 | 27 | export default AboutPage; 28 | -------------------------------------------------------------------------------- /app/[locale]/about/profile/page.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { getMessages } from "next-intl/server"; 3 | import React from "react"; 4 | 5 | export async function generateMetadata({ 6 | params: { locale }, 7 | }: { 8 | params: { locale: string }; 9 | }) { 10 | const messages: any = await getMessages({ locale }); 11 | const title = messages.NavbarLinks.profileTitle; 12 | 13 | return { 14 | title, 15 | }; 16 | } 17 | 18 | const ProfilePage = () => { 19 | const t = useTranslations("ProfilePage"); 20 | return ( 21 |
22 |

{t("title")}

23 |
24 | ); 25 | }; 26 | 27 | export default ProfilePage; 28 | -------------------------------------------------------------------------------- /app/[locale]/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "@/components/Navbar"; 2 | import "../globals.css"; 3 | import { NextIntlClientProvider } from "next-intl"; 4 | import { getMessages } from "next-intl/server"; 5 | 6 | export default async function RootLayout({ 7 | children, 8 | params: { locale }, 9 | }: Readonly<{ 10 | children: React.ReactNode; 11 | params: { locale: string }; 12 | }>) { 13 | const messages = await getMessages(); 14 | return ( 15 | 16 | 17 | 18 |
19 | 20 | {children} 21 |
22 |
23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/[locale]/page.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { getMessages } from "next-intl/server"; 3 | 4 | export async function generateMetadata({ 5 | params: { locale }, 6 | }: { 7 | params: { locale: string }; 8 | }) { 9 | const messages: any = await getMessages({ locale }); 10 | const title = messages.NavbarLinks.homeTitle; 11 | 12 | return { 13 | title, 14 | }; 15 | } 16 | 17 | export default function Home() { 18 | const t = useTranslations("HomePage"); 19 | return ( 20 |
21 |
{t("title")}
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umairjameel321/next15-intl-i18n/3b68bd2c2d64501e68a46bfc4a3e73de480d5944/app/favicon.ico -------------------------------------------------------------------------------- /app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umairjameel321/next15-intl-i18n/3b68bd2c2d64501e68a46bfc4a3e73de480d5944/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umairjameel321/next15-intl-i18n/3b68bd2c2d64501e68a46bfc4a3e73de480d5944/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-synthesis: none; 21 | } 22 | -------------------------------------------------------------------------------- /components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslations } from "next-intl"; 4 | import Link from "next/link"; 5 | import { usePathname, useRouter } from "next/navigation"; 6 | import React, { ChangeEvent } from "react"; 7 | 8 | const Navbar = ({ locale }: { locale: string }) => { 9 | const t = useTranslations("NavbarLinks"); 10 | const pathname = usePathname(); 11 | const router = useRouter(); 12 | 13 | const handleLanguageChange = (e: ChangeEvent) => { 14 | const newLocale = e.target.value as string; 15 | const path = pathname.split("/").slice(2).join("/"); 16 | router.push(`/${newLocale}/${path}`); 17 | }; 18 | return ( 19 |
20 |
21 | {t("home")} 22 | {t("about")} 23 | {t("profile")} 24 |
25 | 33 |
34 | ); 35 | }; 36 | 37 | export default Navbar; 38 | -------------------------------------------------------------------------------- /i18n.ts: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | import { getRequestConfig } from "next-intl/server"; 3 | 4 | const locales = ["en", "fr"]; 5 | 6 | export default getRequestConfig(async ({ locale }) => { 7 | if (!locales.includes(locale as any)) notFound(); 8 | 9 | return { 10 | messages: (await import(`./messages/${locale}.json`)).default, 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /messages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "NavbarLinks": { 3 | "home": "Home", 4 | "homeTitle": "Home page", 5 | "about": "About", 6 | "aboutTitle": "About page", 7 | "profile": "Profile", 8 | "profileTitle": "Profile page" 9 | }, 10 | "HomePage": { 11 | "title": "Landing page" 12 | }, 13 | "AboutPage": { 14 | "title": "About" 15 | }, 16 | "ProfilePage": { 17 | "title": "Profile" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /messages/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "NavbarLinks": { 3 | "home": "Accueil", 4 | "homeTitle": "Page d'accueil", 5 | "about": "À propos", 6 | "aboutTitle": "Page d'à propos", 7 | "profile": "Profil", 8 | "profileTitle": "Page de profil" 9 | }, 10 | "HomePage": { 11 | "title": "Page d'atterrissage" 12 | }, 13 | "AboutPage": { 14 | "title": "À propos" 15 | }, 16 | "ProfilePage": { 17 | "title": "Profil" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import createMiddleware from "next-intl/middleware"; 2 | 3 | export default createMiddleware({ 4 | locales: ["en", "fr"], 5 | defaultLocale: "en", 6 | }); 7 | 8 | export const config = { 9 | matcher: ["/", "/(fr|en)/:path*"], 10 | }; 11 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | import createNextIntlPlugin from "next-intl/plugin"; 4 | 5 | const withNextIntl = createNextIntlPlugin(); 6 | const nextConfig = {}; 7 | 8 | export default withNextIntl(nextConfig); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next15-langs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "15.0.0-rc.0", 13 | "next-intl": "^3.17.2", 14 | "react": "19.0.0-rc-f994737d14-20240522", 15 | "react-dom": "19.0.0-rc-f994737d14-20240522" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "15.0.0-rc.0", 23 | "postcss": "^8", 24 | "tailwindcss": "^3.4.1", 25 | "typescript": "^5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/file-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | fontFamily: { 16 | sans: ["var(--font-geist-sans)"], 17 | mono: ["var(--font-geist-mono)"], 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | }; 23 | export default config; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------