+ {dictionary.main.hello},
Get started by editing
src/app/page.tsx
diff --git a/dictionaries.ts b/dictionaries.ts new file mode 100644 index 0000000..262c41f --- /dev/null +++ b/dictionaries.ts @@ -0,0 +1,12 @@ +import "server-only"; +import type { Locale } from "./i18n-config"; + +// We enumerate all dictionaries here for better linting and typescript support +// We also get the default import for cleaner types +const dictionaries = { + en: () => import("./dictionaries/en.json").then((module) => module.default), + bg: () => import("./dictionaries/bg.json").then((module) => module.default), +}; + +export const getDictionary = async (locale: Locale) => + dictionaries[locale]?.() ?? dictionaries.en(); \ No newline at end of file diff --git a/dictionaries/bg.json b/dictionaries/bg.json new file mode 100644 index 0000000..401f239 --- /dev/null +++ b/dictionaries/bg.json @@ -0,0 +1,5 @@ +{ + "main": { + "hello": "Добре дошли" + } +} \ No newline at end of file diff --git a/dictionaries/en.json b/dictionaries/en.json new file mode 100644 index 0000000..68bf901 --- /dev/null +++ b/dictionaries/en.json @@ -0,0 +1,5 @@ +{ + "main": { + "hello": "Welcome" + } +} \ No newline at end of file diff --git a/i18n-config.ts b/i18n-config.ts new file mode 100644 index 0000000..59cf9bc --- /dev/null +++ b/i18n-config.ts @@ -0,0 +1,6 @@ +export const i18n = { + defaultLocale: "en", + locales: ["en", "bg"], +} as const; + +export type Locale = (typeof i18n)["locales"][number]; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9b678bd..c624245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,14 @@ "name": "openfest", "version": "0.1.0", "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^0.6.3", "next": "14.2.3", "react": "^18", "react-dom": "^18" }, "devDependencies": { + "@types/negotiator": "^0.6.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -103,6 +106,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -448,6 +459,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@types/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-JkXTOdKs5MF086b/pt8C3+yVp3iDUwG635L7oCH6HvJvvr6lSUU5oe/gLXnPEfYRROHjJIPgCV6cuAg8gGkntQ==", + "dev": true + }, "node_modules/@types/node": { "version": "20.14.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.1.tgz", @@ -3112,6 +3129,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.2.3", "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", diff --git a/package.json b/package.json index ca956ed..5957909 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,21 @@ "lint": "next lint" }, "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^0.6.3", + "next": "14.2.3", "react": "^18", - "react-dom": "^18", - "next": "14.2.3" + "react-dom": "^18" }, "devDependencies": { - "typescript": "^5", + "@types/negotiator": "^0.6.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "14.2.3", "postcss": "^8", "tailwindcss": "^3.4.1", - "eslint": "^8", - "eslint-config-next": "14.2.3" + "typescript": "^5" } } diff --git a/src/app/favicon.ico b/src/app/[lang]/favicon.ico similarity index 100% rename from src/app/favicon.ico rename to src/app/[lang]/favicon.ico diff --git a/src/app/globals.css b/src/app/[lang]/globals.css similarity index 100% rename from src/app/globals.css rename to src/app/[lang]/globals.css diff --git a/src/app/layout.tsx b/src/app/[lang]/layout.tsx similarity index 65% rename from src/app/layout.tsx rename to src/app/[lang]/layout.tsx index 3314e47..7513bbd 100644 --- a/src/app/layout.tsx +++ b/src/app/[lang]/layout.tsx @@ -1,6 +1,11 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import { i18n, type Locale } from "../../../i18n-config"; + +export async function generateStaticParams() { + return i18n.locales.map((locale) => ({ lang: locale })); +} const inter = Inter({ subsets: ["latin"] }); @@ -11,11 +16,13 @@ export const metadata: Metadata = { export default function RootLayout({ children, + params }: Readonly<{ children: React.ReactNode; + params: { lang: Locale }; }>) { return ( - +
{children} ); diff --git a/src/app/page.tsx b/src/app/[lang]/page.tsx similarity index 95% rename from src/app/page.tsx rename to src/app/[lang]/page.tsx index 2acfd44..453ed06 100644 --- a/src/app/page.tsx +++ b/src/app/[lang]/page.tsx @@ -1,10 +1,16 @@ import Image from "next/image"; +import { getDictionary } from '../../../dictionaries' +import { Locale } from "../../../i18n-config"; + + +export default async function Home({ params: { lang }, }: { params: { lang: Locale }; }) { + const dictionary = await getDictionary(lang); -export default function Home() { return (
+ {dictionary.main.hello},
Get started by editing
src/app/page.tsx