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: