diff --git a/app/layout.tsx b/app/layout.tsx
index ce2bc00..0b71636 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,22 +1,22 @@
-import { type Metadata } from 'next';
+import { NextIntlClientProvider } from 'next-intl';
+import { getLocale, getMessages } from 'next-intl/server';
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin', 'cyrillic'] });
-export const metadata: Metadata = {
- description: 'Generated by create next app',
- title: 'Create Next App',
-};
+export default async function RootLayout({ children }: { readonly children: React.ReactNode }) {
+ const locale = await getLocale();
+
+ // Providing all messages to the client
+ // side is the easiest way to get started
+ const messages = await getMessages();
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
return (
-
-
{children}
+
+
+ {children}
+
);
}
diff --git a/app/page.tsx b/app/page.tsx
index 08ea7bc..916c997 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,9 +1,10 @@
-export default function Home() {
+import { useTranslations } from 'next-intl';
+
+export default function HomePage() {
+ const t = useTranslations('HomePage');
return (
-
- Hello
-
+
{t('title')}
);
}
diff --git a/i18n/request.ts b/i18n/request.ts
new file mode 100644
index 0000000..e3cd12b
--- /dev/null
+++ b/i18n/request.ts
@@ -0,0 +1,12 @@
+import { getRequestConfig } from 'next-intl/server';
+
+export default getRequestConfig(async () => {
+ // Provide a static locale, fetch a user setting,
+ // read from `cookies()`, `headers()`, etc.
+ const locale = 'ru';
+
+ return {
+ locale,
+ messages: (await import(`../messages/${locale}.json`)).default,
+ };
+});
diff --git a/messages/ru.json b/messages/ru.json
new file mode 100644
index 0000000..b19625c
--- /dev/null
+++ b/messages/ru.json
@@ -0,0 +1,5 @@
+{
+ "HomePage": {
+ "title": "Привет!"
+ }
+}
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..8758e51 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,8 @@
-import type { NextConfig } from "next";
+import { type NextConfig } from 'next';
+import createNextIntlPlugin from 'next-intl/plugin';
-const nextConfig: NextConfig = {
- /* config options here */
-};
+const withNextIntl = createNextIntlPlugin();
-export default nextConfig;
+const nextConfig: NextConfig = {};
+
+export default withNextIntl(nextConfig);
diff --git a/package.json b/package.json
index 718726d..fd72dbb 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"clsx": "^2.1.1",
"lucide-react": "^0.468.0",
"next": "15.0.4",
+ "next-intl": "^3.26.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^2.5.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0803ed5..cc71a5c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
next:
specifier: 15.0.4
version: 15.0.4(@babel/core@7.25.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ next-intl:
+ specifier: ^3.26.0
+ version: 3.26.0(next@15.0.4(@babel/core@7.25.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
react:
specifier: ^19.0.0
version: 19.0.0
@@ -754,6 +757,21 @@ packages:
resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@formatjs/ecma402-abstract@2.2.5':
+ resolution: {integrity: sha512-ep/5vGkyZvMSi6s8nQG8k7vTcKjuXs402fgGIWixj0AWRgKbeaZeLuYc32NIPXexgBjWepMeZGgHLuZXkuD2Gg==}
+
+ '@formatjs/fast-memoize@2.2.4':
+ resolution: {integrity: sha512-8SzI0cBADgbLOYsoQW/IqVHljCH964CrOdESFQ07wMkRLP90+MfV7k6gZPiGD88ubqET9igJV5c292rT28B7xQ==}
+
+ '@formatjs/icu-messageformat-parser@2.9.5':
+ resolution: {integrity: sha512-mHauC9wuVXtnshAIoAYjlNrh6+OFOT6cC4fpK+AG+DHkVWwIPFVQE28hLQ/KptuvQ8VMfG/zYx6rRjtaeFPkSQ==}
+
+ '@formatjs/icu-skeleton-parser@1.8.9':
+ resolution: {integrity: sha512-1KSSlU7ywsU5E5v7xr6VTlBzLGszMi3GOu7EVINjkfA501GN5OkeNSbd5q6ie1wIknZJGBlqkvXPYYdp3YXjpw==}
+
+ '@formatjs/intl-localematcher@0.5.8':
+ resolution: {integrity: sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==}
+
'@graphql-eslint/eslint-plugin@3.20.1':
resolution: {integrity: sha512-RbwVlz1gcYG62sECR1u0XqMh8w5e5XMCCZoMvPQ3nJzEBCTfXLGX727GBoRmSvY1x4gJmqNZ1lsOX7lZY14RIw==}
engines: {node: '>=12'}
@@ -2303,6 +2321,9 @@ packages:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
+ intl-messageformat@10.7.8:
+ resolution: {integrity: sha512-XnFFzJnTfdaDqeiF/ZAUjpkoKEM8UKwHijQXuqpLiM42kuJCawytP/rYAMDYNNaWww/PTaI0rIoG4oUjRrRlnA==}
+
is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'}
@@ -2652,6 +2673,16 @@ packages:
resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==}
engines: {node: '>=18'}
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ next-intl@3.26.0:
+ resolution: {integrity: sha512-gkamnHIANQzeW8xpTGRxd0xiOCztQhY8GDp79fgdlw0GioqrjTEfSWLhHkgaAtvHRbuh/ByJdwiEY5eNK9bUSQ==}
+ peerDependencies:
+ next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+
next@15.0.4:
resolution: {integrity: sha512-nuy8FH6M1FG0lktGotamQDCXhh5hZ19Vo0ht1AOIQWrYJLP598TIUagKtvJrfJ5AGwB/WmDqkKaKhMpVifvGPA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -3366,6 +3397,11 @@ packages:
urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
+ use-intl@3.26.0:
+ resolution: {integrity: sha512-HGXmpjGlbEv1uFZPfm557LK8p/hv0pKF9UwnrJeHUTxQx6bUGzMgpmPRLCVY3zkr7hfjy4LPwQJfk4Fhnn+dIg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -4353,6 +4389,31 @@ snapshots:
dependencies:
levn: 0.4.1
+ '@formatjs/ecma402-abstract@2.2.5':
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.4
+ '@formatjs/intl-localematcher': 0.5.8
+ tslib: 2.8.1
+
+ '@formatjs/fast-memoize@2.2.4':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/icu-messageformat-parser@2.9.5':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.2.5
+ '@formatjs/icu-skeleton-parser': 1.8.9
+ tslib: 2.8.1
+
+ '@formatjs/icu-skeleton-parser@1.8.9':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.2.5
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.5.8':
+ dependencies:
+ tslib: 2.8.1
+
'@graphql-eslint/eslint-plugin@3.20.1(@babel/core@7.25.2)(@types/node@20.17.9)(graphql@16.9.0)':
dependencies:
'@babel/code-frame': 7.26.2
@@ -6394,6 +6455,13 @@ snapshots:
hasown: 2.0.2
side-channel: 1.0.6
+ intl-messageformat@10.7.8:
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.2.5
+ '@formatjs/fast-memoize': 2.2.4
+ '@formatjs/icu-messageformat-parser': 2.9.5
+ tslib: 2.8.1
+
is-arguments@1.1.1:
dependencies:
call-bind: 1.0.8
@@ -6715,6 +6783,16 @@ snapshots:
natural-orderby@5.0.0: {}
+ negotiator@1.0.0: {}
+
+ next-intl@3.26.0(next@15.0.4(@babel/core@7.25.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@formatjs/intl-localematcher': 0.5.8
+ negotiator: 1.0.0
+ next: 15.0.4(@babel/core@7.25.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ use-intl: 3.26.0(react@19.0.0)
+
next@15.0.4(@babel/core@7.25.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@next/env': 15.0.4
@@ -7486,6 +7564,12 @@ snapshots:
urlpattern-polyfill@8.0.2: {}
+ use-intl@3.26.0(react@19.0.0):
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.4
+ intl-messageformat: 10.7.8
+ react: 19.0.0
+
util-deprecate@1.0.2: {}
validate-npm-package-license@3.0.4: