├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── 01_bug_report.yml
│ ├── 02_feature_request.yml
│ ├── 03_update_docs.yml
│ └── config.yml
└── workflows
│ ├── close-stale.yml
│ ├── issue-commenter.yml
│ ├── lint-pr.yml
│ ├── main.yml
│ ├── prerelease-canary.yml
│ └── release.yml
├── .gitignore
├── .npmrc
├── .prettierrc.json
├── CHANGELOG.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── docs
├── .gitignore
├── README.md
├── eslint.config.mjs
├── lintLinks.mjs
├── media
│ └── assets.sketch
├── next-env.d.ts
├── next-sitemap.config.js
├── next.config.mjs
├── package.json
├── postcss.config.js
├── public
│ ├── crowdin-editor-schematic.png
│ ├── crowdin-repo-mapping.png
│ ├── crowdin-repo-sync.png
│ ├── crowdin-workflow.png
│ ├── example-app-router-mixed-routing.png
│ ├── example-app-router-without-i18n-routing.png
│ ├── example-app-router.png
│ ├── example-street-photography.png
│ ├── external-link.svg
│ ├── favicon
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── browserconfig.xml
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── mstile-150x150.png
│ │ ├── safari-pinned-tab.svg
│ │ └── site.webmanifest
│ ├── google2a52d47b4884506c.html
│ ├── i18n-ally-demo.mp4
│ ├── rsc-pr-participants.png
│ ├── storybook-integration.png
│ ├── testimonial-bg-daybridge-bright.png
│ ├── testimonial-bg-daybridge-dark.png
│ ├── testimonial-bg-nodejs-bright.png
│ ├── testimonial-bg-nodejs-dark.png
│ ├── testimonial-bg-todoist-bright.png
│ ├── testimonial-bg-todoist-dark.png
│ ├── testimonial-bg-watershed-bright.png
│ ├── testimonial-bg-watershed-dark.png
│ ├── twitter-image.png
│ ├── user-alvar-lagerlof.jpeg
│ ├── user-cali-castle.png
│ ├── user-claudio-wunder.jpeg
│ ├── user-colin-sidoti.jpg
│ ├── user-dawid-gawel.jpeg
│ ├── user-jokull-solberg.jpeg
│ ├── user-kieran-mchugh.jpg
│ └── user-lachlan-campbell.jpeg
├── src
│ ├── app
│ │ ├── redirect
│ │ │ └── route.tsx
│ │ └── robots.tsx
│ ├── components
│ │ ├── AlgoliaSearch.css
│ │ ├── AlgoliaSearch.tsx
│ │ ├── BlogPostLink.tsx
│ │ ├── Button.tsx
│ │ ├── Callout.tsx
│ │ ├── Card.tsx
│ │ ├── Cards.tsx
│ │ ├── Chip.tsx
│ │ ├── CodeSnippet.module.css
│ │ ├── CodeSnippet.tsx
│ │ ├── CodeSnippets.tsx
│ │ ├── CopyToClipboard.tsx
│ │ ├── Details.tsx
│ │ ├── Example.tsx
│ │ ├── FeaturePanel.tsx
│ │ ├── Footer.tsx
│ │ ├── FooterLink.tsx
│ │ ├── FooterSeparator.tsx
│ │ ├── FooterVersionSelector.tsx
│ │ ├── GetStartedBackground.module.css
│ │ ├── GetStartedBackground.tsx
│ │ ├── Hero.tsx
│ │ ├── HeroAnnouncement.tsx
│ │ ├── HeroCode.tsx
│ │ ├── Link.tsx
│ │ ├── LinkButton.tsx
│ │ ├── Logo.tsx
│ │ ├── Partner.tsx
│ │ ├── PartnerBanner.tsx
│ │ ├── PartnerContentLink.tsx
│ │ ├── PartnerLink.tsx
│ │ ├── PartnerSidebar.tsx
│ │ ├── Pre.tsx
│ │ ├── Screenshot.tsx
│ │ ├── Section.tsx
│ │ ├── StayUpdated.mdx
│ │ ├── Steps.module.css
│ │ ├── Steps.tsx
│ │ ├── UserTestimonial.tsx
│ │ ├── Wrapper.tsx
│ │ └── icons
│ │ │ ├── CheckIcon.tsx
│ │ │ └── CopyIcon.tsx
│ ├── config.js
│ ├── hooks
│ │ └── useLocationHash.tsx
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── _meta.tsx
│ │ ├── api
│ │ │ ├── Inter-Regular.otf
│ │ │ ├── Inter-SemiBold.otf
│ │ │ └── og-image.tsx
│ │ ├── blog
│ │ │ ├── _meta.tsx
│ │ │ ├── date-formatting-nextjs.mdx
│ │ │ ├── index.mdx
│ │ │ ├── next-intl-3-0.mdx
│ │ │ ├── next-intl-3-22.mdx
│ │ │ ├── next-intl-4-0.mdx
│ │ │ └── translations-outside-of-react-components.mdx
│ │ ├── docs
│ │ │ ├── _meta.tsx
│ │ │ ├── design-principles.mdx
│ │ │ ├── environments.mdx
│ │ │ ├── environments
│ │ │ │ ├── _meta.tsx
│ │ │ │ ├── actions-metadata-route-handlers.mdx
│ │ │ │ ├── core-library.mdx
│ │ │ │ ├── error-files.mdx
│ │ │ │ ├── mdx.mdx
│ │ │ │ ├── runtime-requirements.mdx
│ │ │ │ ├── server-client-components.mdx
│ │ │ │ └── testing.mdx
│ │ │ ├── getting-started.mdx
│ │ │ ├── getting-started
│ │ │ │ ├── _meta.tsx
│ │ │ │ ├── app-router.mdx
│ │ │ │ ├── app-router
│ │ │ │ │ ├── _meta.tsx
│ │ │ │ │ ├── with-i18n-routing.mdx
│ │ │ │ │ └── without-i18n-routing.mdx
│ │ │ │ └── pages-router.mdx
│ │ │ ├── routing.mdx
│ │ │ ├── routing
│ │ │ │ ├── _meta.tsx
│ │ │ │ ├── middleware.mdx
│ │ │ │ └── navigation.mdx
│ │ │ ├── usage.mdx
│ │ │ ├── usage
│ │ │ │ ├── _meta.tsx
│ │ │ │ ├── configuration.mdx
│ │ │ │ ├── dates-times.mdx
│ │ │ │ ├── lists.mdx
│ │ │ │ ├── messages.mdx
│ │ │ │ └── numbers.mdx
│ │ │ ├── workflows.mdx
│ │ │ └── workflows
│ │ │ │ ├── _meta.tsx
│ │ │ │ ├── linting.mdx
│ │ │ │ ├── localization-management.mdx
│ │ │ │ ├── messages.mdx
│ │ │ │ ├── storybook.mdx
│ │ │ │ ├── typescript.mdx
│ │ │ │ └── vscode-integration.mdx
│ │ ├── examples.mdx
│ │ └── index.mdx
│ ├── services
│ │ ├── BrowserTracker.tsx
│ │ └── ServerTracker.tsx
│ ├── styles.css
│ └── theme.config.tsx
├── tailwind.config.js
└── tsconfig.json
├── examples
├── example-app-router-migration
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── app
│ │ │ ├── [locale]
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── not-found.tsx
│ │ ├── components
│ │ │ ├── LocaleSwitcher.tsx
│ │ │ └── PageLayout.tsx
│ │ ├── i18n
│ │ │ ├── navigation.ts
│ │ │ ├── request.ts
│ │ │ └── routing.ts
│ │ ├── middleware.ts
│ │ └── pages
│ │ │ ├── [locale]
│ │ │ └── about.tsx
│ │ │ └── _app.tsx
│ └── tsconfig.json
├── example-app-router-mixed-routing
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── global.ts
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── postcss.config.js
│ ├── src
│ │ ├── app
│ │ │ ├── (public)
│ │ │ │ └── [locale]
│ │ │ │ │ ├── Login.tsx
│ │ │ │ │ ├── NavLink.tsx
│ │ │ │ │ ├── PublicNavigation.tsx
│ │ │ │ │ ├── PublicNavigationLocaleSwitcher.tsx
│ │ │ │ │ ├── about
│ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.tsx
│ │ │ ├── app
│ │ │ │ ├── AppNavigation.tsx
│ │ │ │ ├── AppNavigationLocaleButton.tsx
│ │ │ │ ├── Logout.tsx
│ │ │ │ ├── NavLink.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── profile
│ │ │ │ │ └── page.tsx
│ │ │ ├── favicon.ico
│ │ │ ├── layout.tsx
│ │ │ └── not-found.tsx
│ │ ├── components
│ │ │ ├── Document.tsx
│ │ │ ├── PageTitle.tsx
│ │ │ └── globals.css
│ │ ├── config.ts
│ │ ├── i18n
│ │ │ ├── navigation.public.ts
│ │ │ ├── request.ts
│ │ │ └── routing.public.ts
│ │ └── middleware.ts
│ ├── tailwind.config.ts
│ ├── tests
│ │ └── main.spec.ts
│ └── tsconfig.json
├── example-app-router-next-auth
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── global.ts
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── app
│ │ │ ├── [locale]
│ │ │ │ ├── Index.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── login
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── secret
│ │ │ │ │ └── page.tsx
│ │ │ ├── api
│ │ │ │ └── auth
│ │ │ │ │ └── [...nextauth]
│ │ │ │ │ └── route.ts
│ │ │ ├── layout.tsx
│ │ │ └── not-found.tsx
│ │ ├── auth.ts
│ │ ├── components
│ │ │ ├── LocaleSwitcher.tsx
│ │ │ └── PageLayout.tsx
│ │ ├── i18n
│ │ │ ├── navigation.ts
│ │ │ ├── request.ts
│ │ │ └── routing.ts
│ │ └── middleware.ts
│ ├── tests
│ │ └── main.spec.ts
│ └── tsconfig.json
├── example-app-router-playground
│ ├── .gitignore
│ ├── .storybook
│ │ ├── main.ts
│ │ ├── next-intl.ts
│ │ └── preview.ts
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── global.ts
│ ├── jest.config.js
│ ├── messages
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es.json
│ │ └── ja.json
│ ├── next-env.d.ts
│ ├── next.config.mjs
│ ├── package.json
│ ├── playwright.config.ts
│ ├── public
│ │ ├── assets
│ │ │ └── image.jpg
│ │ └── favicon.ico
│ ├── runPlaywright.mjs
│ ├── src
│ │ ├── app
│ │ │ ├── [locale]
│ │ │ │ ├── [...rest]
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── about
│ │ │ │ │ ├── de.mdx
│ │ │ │ │ ├── en.mdx
│ │ │ │ │ ├── es.mdx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── actions
│ │ │ │ │ ├── List.tsx
│ │ │ │ │ ├── ListItem.tsx
│ │ │ │ │ ├── ListItemAsync.tsx
│ │ │ │ │ ├── ListItemClient.tsx
│ │ │ │ │ ├── ZodForm.tsx
│ │ │ │ │ ├── ZodFormExample.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── api
│ │ │ │ │ └── route.ts
│ │ │ │ ├── client
│ │ │ │ │ ├── ClientContent.tsx
│ │ │ │ │ ├── DelayedServerContent.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── redirect
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── nested
│ │ │ │ │ ├── UnlocalizedPathname.tsx
│ │ │ │ │ ├── page.spec.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── news
│ │ │ │ │ ├── [articleId]
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── just-in
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── not-found.tsx
│ │ │ │ ├── opengraph-image.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── redirect
│ │ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── not-found.tsx
│ │ ├── components
│ │ │ ├── AsyncComponent.tsx
│ │ │ ├── AsyncComponentWithNamespaceAndLocale.tsx
│ │ │ ├── AsyncComponentWithoutNamespace.tsx
│ │ │ ├── AsyncComponentWithoutNamespaceAndLocale.tsx
│ │ │ ├── ClientLink.tsx
│ │ │ ├── ClientRouterWithoutProvider.tsx
│ │ │ ├── CoreLibrary.tsx
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── LocaleSwitcher.tsx
│ │ │ ├── Navigation.stories.tsx
│ │ │ ├── Navigation.tsx
│ │ │ ├── NavigationLink.tsx
│ │ │ ├── PageLayout.tsx
│ │ │ ├── RichText.tsx
│ │ │ ├── TypePortabilityTest.ts
│ │ │ ├── UseFormatterTypeTests.tsx
│ │ │ ├── UseLocaleTypeTests.tsx
│ │ │ ├── UseMessagesTypeTests.tsx
│ │ │ ├── UseTranslationsTypeTests.tsx
│ │ │ └── client
│ │ │ │ ├── 01-MessagesAsPropsCounter
│ │ │ │ ├── ClientCounter.tsx
│ │ │ │ ├── Counter.tsx
│ │ │ │ └── index.tsx
│ │ │ │ └── 02-MessagesOnClientCounter
│ │ │ │ ├── ClientCounter.tsx
│ │ │ │ ├── Counter.tsx
│ │ │ │ └── index.tsx
│ │ ├── i18n
│ │ │ ├── navigation.ts
│ │ │ ├── request.tsx
│ │ │ └── routing.ts
│ │ ├── mdx-components.tsx
│ │ └── middleware.ts
│ ├── tests
│ │ ├── base-path.spec.ts
│ │ ├── domains.spec.ts
│ │ ├── locale-cookie-false.spec.ts
│ │ ├── locale-prefix-never.spec.ts
│ │ ├── main.spec.ts
│ │ ├── trailing-slash.spec.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── example-app-router-single-locale
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── messages
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.mjs
│ ├── next.config.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── app
│ │ │ ├── about
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── components
│ │ │ └── PageLayout.tsx
│ │ └── i18n
│ │ │ └── request.ts
│ ├── tests
│ │ └── main.spec.ts
│ └── tsconfig.json
├── example-app-router-without-i18n-routing
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.mjs
│ ├── next.config.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── postcss.config.js
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── app
│ │ │ ├── app
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── profile
│ │ │ │ │ └── page.tsx
│ │ │ ├── globals.css
│ │ │ ├── layout.tsx
│ │ │ ├── login
│ │ │ │ ├── LoginForm.tsx
│ │ │ │ ├── LoginFormErrors.tsx
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── components
│ │ │ ├── Button.tsx
│ │ │ ├── FormField.tsx
│ │ │ ├── LocaleSwitcher.tsx
│ │ │ ├── LocaleSwitcherSelect.tsx
│ │ │ └── NavLink.tsx
│ │ ├── i18n
│ │ │ ├── config.ts
│ │ │ └── request.ts
│ │ └── services
│ │ │ ├── locale.ts
│ │ │ └── session.ts
│ ├── tailwind.config.js
│ ├── tests
│ │ └── main.spec.ts
│ └── tsconfig.json
├── example-app-router
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── global.ts
│ ├── jest.config.js
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── postcss.config.js
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── app
│ │ │ ├── [locale]
│ │ │ │ ├── [...rest]
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── error.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── not-found.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── pathnames
│ │ │ │ │ └── page.tsx
│ │ │ │ └── styles.css
│ │ │ ├── layout.tsx
│ │ │ ├── manifest.ts
│ │ │ ├── not-found.tsx
│ │ │ ├── page.tsx
│ │ │ ├── robots.txt
│ │ │ └── sitemap.ts
│ │ ├── components
│ │ │ ├── ExternalLink.tsx
│ │ │ ├── LocaleSwitcher.tsx
│ │ │ ├── LocaleSwitcherSelect.tsx
│ │ │ ├── Navigation.spec.tsx
│ │ │ ├── Navigation.tsx
│ │ │ ├── NavigationLink.tsx
│ │ │ ├── NotFoundPage.tsx
│ │ │ └── PageLayout.tsx
│ │ ├── config.ts
│ │ ├── i18n
│ │ │ ├── navigation.ts
│ │ │ ├── request.ts
│ │ │ └── routing.ts
│ │ └── middleware.ts
│ ├── tailwind.config.js
│ ├── tests
│ │ └── main.spec.ts
│ └── tsconfig.json
├── example-pages-router-advanced
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── global.ts
│ ├── jest.config.js
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── components
│ │ │ ├── Code.tsx
│ │ │ ├── Navigation.spec.tsx
│ │ │ ├── Navigation.tsx
│ │ │ └── PageLayout.tsx
│ │ └── pages
│ │ │ ├── 404.tsx
│ │ │ ├── _app.tsx
│ │ │ ├── about.tsx
│ │ │ ├── api
│ │ │ └── hello.tsx
│ │ │ ├── index.tsx
│ │ │ └── strict-types.tsx
│ └── tsconfig.json
├── example-pages-router-legacy
│ ├── .gitignore
│ ├── README.md
│ ├── messages
│ │ └── en.json
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ └── src
│ │ ├── components
│ │ └── PageLayout.js
│ │ └── pages
│ │ ├── _app.js
│ │ └── index.js
├── example-pages-router
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── global.ts
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── next-env.d.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── components
│ │ │ ├── LocaleSwitcher.tsx
│ │ │ └── PageLayout.tsx
│ │ └── pages
│ │ │ ├── _app.tsx
│ │ │ └── index.tsx
│ └── tsconfig.json
├── example-react-native
│ ├── .gitignore
│ ├── App.js
│ ├── README.md
│ ├── app.json
│ ├── assets
│ │ ├── adaptive-icon.png
│ │ ├── favicon.png
│ │ ├── icon.png
│ │ └── splash.png
│ ├── babel.config.js
│ └── package.json
├── example-remix
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ │ ├── root.tsx
│ │ ├── routes
│ │ │ └── _index.tsx
│ │ └── utils.tsx
│ ├── messages
│ │ ├── de.json
│ │ └── en.json
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── remix.config.js
│ └── tsconfig.json
└── example-use-intl
│ ├── .gitignore
│ ├── README.md
│ ├── global.ts
│ ├── index.html
│ ├── messages
│ └── en.json
│ ├── package.json
│ ├── public
│ └── vite.svg
│ ├── src
│ ├── App.tsx
│ ├── config.tsx
│ ├── main.tsx
│ └── vite-env.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── lerna.json
├── media
├── assets.sketch
├── logo-dark-mode.svg
├── logo.png
├── logo.svg
├── og-image.png
├── partner.svg
└── twitter-image.png
├── package.json
├── packages
├── next-intl
│ ├── .gitignore
│ ├── .size-limit.ts
│ ├── CHANGELOG.md
│ ├── __mocks__
│ │ └── react.tsx
│ ├── config.d.ts
│ ├── eslint.config.mjs
│ ├── middleware.d.ts
│ ├── navigation.d.ts
│ ├── next-env.d.ts
│ ├── package.json
│ ├── plugin.d.cts
│ ├── plugin.d.ts
│ ├── rollup.config.js
│ ├── routing.d.ts
│ ├── server.d.ts
│ ├── src
│ │ ├── config.tsx
│ │ ├── index.react-client.tsx
│ │ ├── index.react-server.tsx
│ │ ├── middleware.tsx
│ │ ├── middleware
│ │ │ ├── getAlternateLinksHeaderValue.test.tsx
│ │ │ ├── getAlternateLinksHeaderValue.tsx
│ │ │ ├── index.tsx
│ │ │ ├── middleware.test.tsx
│ │ │ ├── middleware.tsx
│ │ │ ├── resolveLocale.test.tsx
│ │ │ ├── resolveLocale.tsx
│ │ │ ├── syncCookie.tsx
│ │ │ ├── utils.test.tsx
│ │ │ └── utils.tsx
│ │ ├── navigation.react-client.tsx
│ │ ├── navigation.react-server.tsx
│ │ ├── navigation
│ │ │ ├── createNavigation.test.tsx
│ │ │ ├── react-client
│ │ │ │ ├── createNavigation.test.tsx
│ │ │ │ ├── createNavigation.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── useBasePathname.test.tsx
│ │ │ │ └── useBasePathname.tsx
│ │ │ ├── react-server
│ │ │ │ ├── createNavigation.test.tsx
│ │ │ │ ├── createNavigation.tsx
│ │ │ │ ├── getServerLocale.tsx
│ │ │ │ └── index.tsx
│ │ │ └── shared
│ │ │ │ ├── BaseLink.tsx
│ │ │ │ ├── StrictParams.tsx
│ │ │ │ ├── createSharedNavigationFns.tsx
│ │ │ │ ├── syncLocaleCookie.tsx
│ │ │ │ ├── utils.test.tsx
│ │ │ │ └── utils.tsx
│ │ ├── plugin.tsx
│ │ ├── plugin
│ │ │ ├── createMessagesDeclaration.tsx
│ │ │ ├── createNextIntlPlugin.tsx
│ │ │ ├── getNextConfig.tsx
│ │ │ ├── hasStableTurboConfig.tsx
│ │ │ ├── index.tsx
│ │ │ ├── types.tsx
│ │ │ ├── utils.tsx
│ │ │ └── watchFile.tsx
│ │ ├── react-client
│ │ │ ├── index.tsx
│ │ │ ├── useFormatter.test.tsx
│ │ │ ├── useNow.test.tsx
│ │ │ ├── useTimeZone.test.tsx
│ │ │ └── useTranslations.test.tsx
│ │ ├── react-server
│ │ │ ├── NextIntlClientProviderServer.test.tsx
│ │ │ ├── NextIntlClientProviderServer.tsx
│ │ │ ├── index.test.tsx
│ │ │ ├── index.tsx
│ │ │ ├── testUtils.tsx
│ │ │ ├── useConfig.tsx
│ │ │ ├── useFormatter.test.tsx
│ │ │ ├── useFormatter.tsx
│ │ │ ├── useLocale.tsx
│ │ │ ├── useMessages.tsx
│ │ │ ├── useNow.tsx
│ │ │ ├── useTimeZone.tsx
│ │ │ ├── useTranslations.test.tsx
│ │ │ └── useTranslations.tsx
│ │ ├── routing.tsx
│ │ ├── routing
│ │ │ ├── config.tsx
│ │ │ ├── defineRouting.test.tsx
│ │ │ ├── defineRouting.tsx
│ │ │ ├── index.tsx
│ │ │ ├── types.test.tsx
│ │ │ └── types.tsx
│ │ ├── server.react-client.tsx
│ │ ├── server.react-server.tsx
│ │ ├── server
│ │ │ ├── react-client
│ │ │ │ ├── index.test.tsx
│ │ │ │ └── index.tsx
│ │ │ └── react-server
│ │ │ │ ├── RequestLocale.tsx
│ │ │ │ ├── RequestLocaleCache.tsx
│ │ │ │ ├── createRequestConfig.tsx
│ │ │ │ ├── getConfig.tsx
│ │ │ │ ├── getConfigNow.tsx
│ │ │ │ ├── getDefaultNow.tsx
│ │ │ │ ├── getFormats.tsx
│ │ │ │ ├── getFormatter.test.tsx
│ │ │ │ ├── getFormatter.tsx
│ │ │ │ ├── getLocale.tsx
│ │ │ │ ├── getMessages.tsx
│ │ │ │ ├── getNow.tsx
│ │ │ │ ├── getRequestConfig.tsx
│ │ │ │ ├── getServerFormatter.tsx
│ │ │ │ ├── getServerTranslator.tsx
│ │ │ │ ├── getTimeZone.tsx
│ │ │ │ ├── getTranslations.test.tsx
│ │ │ │ ├── getTranslations.tsx
│ │ │ │ ├── index.test.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── validateLocale.test.tsx
│ │ │ │ └── validateLocale.tsx
│ │ └── shared
│ │ │ ├── NextIntlClientProvider.tsx
│ │ │ ├── constants.tsx
│ │ │ ├── types.tsx
│ │ │ ├── use.tsx
│ │ │ ├── utils.test.tsx
│ │ │ └── utils.tsx
│ ├── test
│ │ └── setup.tsx
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── types
│ │ └── index.d.ts
│ └── vitest.config.mts
└── use-intl
│ ├── .size-limit.ts
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── core.d.ts
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── react.d.ts
│ ├── rollup.config.js
│ ├── src
│ ├── core.tsx
│ ├── core
│ │ ├── AbstractIntlMessages.tsx
│ │ ├── AppConfig.tsx
│ │ ├── DateTimeFormatOptions.tsx
│ │ ├── Formats.tsx
│ │ ├── ICUArgs.tsx
│ │ ├── ICUTags.tsx
│ │ ├── IntlConfig.tsx
│ │ ├── IntlError.tsx
│ │ ├── IntlErrorCode.tsx
│ │ ├── MessageKeys.tsx
│ │ ├── NumberFormatOptions.tsx
│ │ ├── RelativeTimeFormatOptions.tsx
│ │ ├── TimeZone.tsx
│ │ ├── TranslationValues.tsx
│ │ ├── convertFormatsToIntlMessageFormat.tsx
│ │ ├── createBaseTranslator.tsx
│ │ ├── createFormatter.test.tsx
│ │ ├── createFormatter.tsx
│ │ ├── createTranslator.test.tsx
│ │ ├── createTranslator.tsx
│ │ ├── createTranslatorImpl.tsx
│ │ ├── defaults.tsx
│ │ ├── formatters.tsx
│ │ ├── hasLocale.test.tsx
│ │ ├── hasLocale.tsx
│ │ ├── index.tsx
│ │ ├── initializeConfig.tsx
│ │ ├── joinPath.tsx
│ │ ├── resolveNamespace.tsx
│ │ ├── types.tsx
│ │ └── validateMessages.tsx
│ ├── index.tsx
│ ├── react.tsx
│ └── react
│ │ ├── IntlContext.tsx
│ │ ├── IntlProvider.test.tsx
│ │ ├── IntlProvider.tsx
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ ├── useFormatter.test.tsx
│ │ ├── useFormatter.tsx
│ │ ├── useIntlContext.tsx
│ │ ├── useLocale.test.tsx
│ │ ├── useLocale.tsx
│ │ ├── useMessages.test.tsx
│ │ ├── useMessages.tsx
│ │ ├── useNow.test.tsx
│ │ ├── useNow.tsx
│ │ ├── useTimeZone.test.tsx
│ │ ├── useTimeZone.tsx
│ │ ├── useTranslations.test.tsx
│ │ ├── useTranslations.tsx
│ │ └── useTranslationsImpl.tsx
│ ├── test
│ └── setup.tsx
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── types
│ └── index.d.ts
│ └── vitest.config.mts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── tools
├── eslint.config.mjs
├── package.json
└── src
│ ├── getBuildConfig.js
│ └── index.js
└── turbo.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: amannn
2 |
--------------------------------------------------------------------------------
/.github/workflows/lint-pr.yml:
--------------------------------------------------------------------------------
1 | name: lint-pr
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | main:
12 | name: Lint PR title
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: amannn/action-semantic-pull-request@v3.1.0
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules/
4 | .vscode
5 | .vercel
6 | .turbo/
7 | dist/
8 | .next/
9 | tsconfig.tsbuildinfo
10 | .turbo
11 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | link-workspace-packages=true
2 | prefer-workspace-packages=true
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "bracketSpacing": false,
4 | "trailingComma": "none"
5 | }
6 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | .vercel
5 | public/.nextra
6 | tsconfig.tsbuildinfo
7 | .env
8 | public/robots.txt
9 | public/sitemap.xml
10 | public/sitemap-0.xml
11 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # docs
2 |
3 | Website for `next-intl`.
4 |
5 | You can run it locally like this:
6 |
7 | ```
8 | pnpm install
9 | pnpm dev
10 | ```
11 |
--------------------------------------------------------------------------------
/docs/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {getPresets} from 'eslint-config-molindo';
2 | import globals from 'globals';
3 |
4 | export default (await getPresets('typescript', 'react', 'tailwind')).concat({
5 | languageOptions: {
6 | globals: {
7 | ...globals.browser,
8 | ...globals.node
9 | }
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/docs/lintLinks.mjs:
--------------------------------------------------------------------------------
1 | import glob from 'fast-glob';
2 | import {printErrors, scanURLs, validateFiles} from 'next-validate-link';
3 |
4 | const scanned = await scanURLs();
5 |
6 | printErrors(
7 | await validateFiles(await glob('src/pages/docs/**/*.{md,mdx}'), {
8 | scanned
9 | }),
10 | true
11 | );
12 |
--------------------------------------------------------------------------------
/docs/media/assets.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/media/assets.sketch
--------------------------------------------------------------------------------
/docs/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | // NOTE: This file should not be edited
6 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
7 |
--------------------------------------------------------------------------------
/docs/next-sitemap.config.js:
--------------------------------------------------------------------------------
1 | const config = require('./src/config');
2 |
3 | /** @type {import('next-sitemap').IConfig} */
4 | module.exports = {
5 | siteUrl: config.baseUrl,
6 | generateRobotsTxt: true,
7 | exclude: ['*/_meta']
8 | };
9 |
--------------------------------------------------------------------------------
/docs/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'tailwindcss/nesting': {},
4 | tailwindcss: {},
5 | autoprefixer: {}
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/docs/public/crowdin-editor-schematic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/crowdin-editor-schematic.png
--------------------------------------------------------------------------------
/docs/public/crowdin-repo-mapping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/crowdin-repo-mapping.png
--------------------------------------------------------------------------------
/docs/public/crowdin-repo-sync.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/crowdin-repo-sync.png
--------------------------------------------------------------------------------
/docs/public/crowdin-workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/crowdin-workflow.png
--------------------------------------------------------------------------------
/docs/public/example-app-router-mixed-routing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/example-app-router-mixed-routing.png
--------------------------------------------------------------------------------
/docs/public/example-app-router-without-i18n-routing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/example-app-router-without-i18n-routing.png
--------------------------------------------------------------------------------
/docs/public/example-app-router.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/example-app-router.png
--------------------------------------------------------------------------------
/docs/public/example-street-photography.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/example-street-photography.png
--------------------------------------------------------------------------------
/docs/public/external-link.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/docs/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/public/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/docs/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/docs/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-intl",
3 | "short_name": "next-intl",
4 | "icons": [
5 | {
6 | "src": "/favicon/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicon/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/docs/public/google2a52d47b4884506c.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google2a52d47b4884506c.html
2 |
--------------------------------------------------------------------------------
/docs/public/i18n-ally-demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/i18n-ally-demo.mp4
--------------------------------------------------------------------------------
/docs/public/rsc-pr-participants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/rsc-pr-participants.png
--------------------------------------------------------------------------------
/docs/public/storybook-integration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/storybook-integration.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-daybridge-bright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-daybridge-bright.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-daybridge-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-daybridge-dark.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-nodejs-bright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-nodejs-bright.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-nodejs-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-nodejs-dark.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-todoist-bright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-todoist-bright.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-todoist-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-todoist-dark.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-watershed-bright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-watershed-bright.png
--------------------------------------------------------------------------------
/docs/public/testimonial-bg-watershed-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/testimonial-bg-watershed-dark.png
--------------------------------------------------------------------------------
/docs/public/twitter-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/twitter-image.png
--------------------------------------------------------------------------------
/docs/public/user-alvar-lagerlof.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-alvar-lagerlof.jpeg
--------------------------------------------------------------------------------
/docs/public/user-cali-castle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-cali-castle.png
--------------------------------------------------------------------------------
/docs/public/user-claudio-wunder.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-claudio-wunder.jpeg
--------------------------------------------------------------------------------
/docs/public/user-colin-sidoti.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-colin-sidoti.jpg
--------------------------------------------------------------------------------
/docs/public/user-dawid-gawel.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-dawid-gawel.jpeg
--------------------------------------------------------------------------------
/docs/public/user-jokull-solberg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-jokull-solberg.jpeg
--------------------------------------------------------------------------------
/docs/public/user-kieran-mchugh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-kieran-mchugh.jpg
--------------------------------------------------------------------------------
/docs/public/user-lachlan-campbell.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/public/user-lachlan-campbell.jpeg
--------------------------------------------------------------------------------
/docs/src/app/robots.tsx:
--------------------------------------------------------------------------------
1 | import {MetadataRoute} from 'next';
2 |
3 | export default function robots(): MetadataRoute.Robots {
4 | if (process.env.VERCEL_ENV !== 'production') {
5 | return {
6 | rules: {
7 | userAgent: '*',
8 | disallow: '/'
9 | }
10 | };
11 | } else {
12 | return {
13 | rules: {
14 | userAgent: '*',
15 | allow: '/'
16 | }
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/docs/src/components/Cards.tsx:
--------------------------------------------------------------------------------
1 | import cx from 'clsx';
2 | import type {ComponentProps} from 'react';
3 |
4 | type Props = ComponentProps<'div'>;
5 |
6 | export default function Cards({children, className, ...props}: Props) {
7 | return (
8 |
9 | {children}
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/docs/src/components/CodeSnippet.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import {ReactNode} from 'react';
3 | import styles from './CodeSnippet.module.css';
4 |
5 | type Props = {
6 | children: ReactNode;
7 | };
8 |
9 | export default function CodeSnippet({children}: Props) {
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/docs/src/components/FooterLink.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {ComponentProps} from 'react';
3 |
4 | type Props = ComponentProps;
5 |
6 | export default function FooterLink({children, ...rest}: Props) {
7 | return (
8 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/docs/src/components/FooterSeparator.tsx:
--------------------------------------------------------------------------------
1 | export default function FooterSeparator() {
2 | return (
3 |
4 | ·
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/docs/src/components/FooterVersionSelector.tsx:
--------------------------------------------------------------------------------
1 | import {ChangeEvent} from 'react';
2 |
3 | export default function FooterVersionSelector() {
4 | function onChange(event: ChangeEvent) {
5 | const version = event.target.value;
6 | window.location.href = `https://${version}.next-intl.dev`;
7 | }
8 |
9 | return (
10 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/docs/src/components/GetStartedBackground.module.css:
--------------------------------------------------------------------------------
1 | @keyframes pulsate {
2 | 0% {
3 | opacity: 0;
4 | }
5 | 50% {
6 | opacity: 1;
7 | }
8 | 100% {
9 | opacity: 0;
10 | }
11 | }
12 |
13 | .dot {
14 | --GetStartedBackground-dot: rgba(0, 0, 0, 0.1);
15 |
16 | :global(.dark) & {
17 | --GetStartedBackground-dot: rgba(255, 255, 255, 0.1);
18 | }
19 | }
20 |
21 | .dot1 {
22 | animation: pulsate 4s infinite;
23 | }
24 |
25 | .dot2 {
26 | animation: pulsate 4s infinite 1s;
27 | }
28 |
29 | .dot3 {
30 | animation: pulsate 4s infinite 4s;
31 | }
32 |
33 | .dot4 {
34 | animation: pulsate 4s infinite 3s;
35 | }
36 |
37 | .dot5 {
38 | animation: pulsate 4s infinite 2s;
39 | }
40 |
--------------------------------------------------------------------------------
/docs/src/components/HeroAnnouncement.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {ReactNode} from 'react';
3 |
4 | type Props = {
5 | children: ReactNode;
6 | href: string;
7 | };
8 |
9 | export default function HeroAnnouncement({children, href}: Props) {
10 | return (
11 |
15 | 📣 {children}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/docs/src/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import NextLink from 'next/link';
2 | import {ComponentProps} from 'react';
3 |
4 | type Props = Omit, 'className'>;
5 |
6 | export default function Link(props: Props) {
7 | return (
8 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/docs/src/components/PartnerContentLink.tsx:
--------------------------------------------------------------------------------
1 | import {useMDXComponents} from 'nextra/mdx';
2 | import {ComponentProps, ElementType} from 'react';
3 | import PartnerLink from './PartnerLink';
4 |
5 | type Props = Omit, 'as'>;
6 |
7 | export default function PartnerContentLink(props: Props) {
8 | const components = useMDXComponents();
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/docs/src/components/PartnerLink.tsx:
--------------------------------------------------------------------------------
1 | import {HTMLAttributes} from 'react';
2 | import BrowserTracker from '@/services/BrowserTracker';
3 |
4 | type Props = HTMLAttributes & {
5 | as?: React.ElementType;
6 | href: string;
7 | name: string;
8 | };
9 |
10 | export default function PartnerLink({
11 | as: Component = 'a',
12 | href,
13 | name,
14 | ...rest
15 | }: Props) {
16 | function onClick() {
17 | BrowserTracker.trackEvent({
18 | name: 'partner-referral',
19 | data: {href, name}
20 | });
21 | }
22 |
23 | return ;
24 | }
25 |
--------------------------------------------------------------------------------
/docs/src/components/PartnerSidebar.tsx:
--------------------------------------------------------------------------------
1 | import Partner from './Partner';
2 | import PartnerLink from './PartnerLink';
3 |
4 | type Props = {
5 | intro?: string;
6 | };
7 |
8 | export default function PartnerSidebar({intro = 'Presented by'}: Props) {
9 | return (
10 |
15 |
16 | {intro}
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/docs/src/components/Screenshot.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import Image from 'next/image';
3 | import {ComponentProps} from 'react';
4 |
5 | type Props = ComponentProps;
6 |
7 | export default function Screenshot({className, ...rest}: Props) {
8 | return (
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/docs/src/components/StayUpdated.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Let's keep in touch:**
4 |
5 | - [Bluesky (Jan Amann)](https://bsky.app/profile/amann.work)
6 | - [X (Jan Amann)](https://x.com/jamannnnnn)
7 |
--------------------------------------------------------------------------------
/docs/src/components/Steps.module.css:
--------------------------------------------------------------------------------
1 | .root {
2 | @apply ml-4 border-l border-slate-200 pl-8;
3 | counter-reset: step;
4 | }
5 |
6 | .root h3 {
7 | counter-increment: step;
8 | @apply text-lg;
9 | }
10 | .root h3:before {
11 | content: counter(step);
12 | @apply absolute mt-[-6px] ml-[-52px] inline-block h-10 w-10 rounded-full border-4 border-white bg-slate-100 pt-[4px] text-center text-base font-bold text-slate-500;
13 | }
14 |
15 | :global(.dark) .root {
16 | @apply border-slate-800;
17 | }
18 | :global(.dark) .root h3:before {
19 | @apply bg-slate-800 text-white/75;
20 | border-color: rgba(17, 17, 17, var(--tw-bg-opacity)); /* bg-dark */
21 | }
22 |
--------------------------------------------------------------------------------
/docs/src/components/Steps.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 | import styles from './Steps.module.css';
3 |
4 | type Props = {
5 | children: ReactNode;
6 | };
7 |
8 | export default function Steps({children}: Props) {
9 | return {children}
;
10 | }
11 |
--------------------------------------------------------------------------------
/docs/src/components/Wrapper.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import {ReactNode} from 'react';
3 |
4 | type Props = {
5 | children: ReactNode;
6 | className?: string;
7 | };
8 |
9 | export default function Wrapper({children, className}: Props) {
10 | return (
11 |
17 | {children}
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/docs/src/components/icons/CheckIcon.tsx:
--------------------------------------------------------------------------------
1 | import {ComponentProps} from 'react';
2 |
3 | type Props = ComponentProps<'svg'>;
4 |
5 | export default function CheckIcon(props: Props) {
6 | return (
7 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/docs/src/components/icons/CopyIcon.tsx:
--------------------------------------------------------------------------------
1 | import {ComponentProps} from 'react';
2 |
3 | type Props = ComponentProps<'svg'>;
4 |
5 | export default function CopyIcon(props: Props) {
6 | return (
7 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/docs/src/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'next-intl',
3 | description: 'Internationalization (i18n) for Next.js',
4 | baseUrl: process.env.VERCEL_PROJECT_PRODUCTION_URL
5 | ? 'https://' + process.env.VERCEL_PROJECT_PRODUCTION_URL
6 | : 'http://localhost:3000',
7 | githubUrl: 'https://github.com/amannn/next-intl',
8 | blueskyUrl: 'https://bsky.app/profile/amann.work',
9 | xUrl: 'https://x.com/jamannnnnn'
10 | };
11 |
--------------------------------------------------------------------------------
/docs/src/hooks/useLocationHash.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from 'react';
2 |
3 | function getHash() {
4 | return decodeURIComponent(window.location.hash.replace('#', ''));
5 | }
6 |
7 | export default function useLocationHash() {
8 | const [hash, setHash] = useState();
9 |
10 | useEffect(() => {
11 | function updateHash() {
12 | setHash(getHash());
13 | }
14 |
15 | window.addEventListener('hashchange', updateHash);
16 |
17 | updateHash();
18 |
19 | return () => {
20 | window.removeEventListener('hashchange', updateHash);
21 | };
22 | }, []);
23 |
24 | return hash;
25 | }
26 |
--------------------------------------------------------------------------------
/docs/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import {Head, Html, Main, NextScript} from 'next/document';
2 | import {SkipNavLink} from 'nextra-theme-docs';
3 | import React from 'react';
4 |
5 | export default function Document() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/docs/src/pages/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | index: {
3 | title: 'Introduction',
4 | type: 'page',
5 | display: 'hidden',
6 | theme: {layout: 'raw'}
7 | },
8 | docs: {
9 | title: 'Docs',
10 | type: 'page'
11 | },
12 | examples: {
13 | title: 'Examples',
14 | type: 'page',
15 | theme: {
16 | sidebar: false,
17 | toc: false
18 | }
19 | },
20 | blog: {
21 | title: 'Blog',
22 | type: 'page',
23 | theme: {
24 | sidebar: false,
25 | toc: false
26 | }
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/docs/src/pages/api/Inter-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/src/pages/api/Inter-Regular.otf
--------------------------------------------------------------------------------
/docs/src/pages/api/Inter-SemiBold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/docs/src/pages/api/Inter-SemiBold.otf
--------------------------------------------------------------------------------
/docs/src/pages/blog/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | index: {
3 | title: 'Overview'
4 | },
5 | 'next-intl-4-0': {
6 | title: 'next-intl 4.0',
7 | display: 'hidden'
8 | },
9 | 'next-intl-3-22': {
10 | title: 'next-intl 3.22',
11 | display: 'hidden'
12 | },
13 | 'date-formatting-nextjs': {
14 | title: 'Reliable date formatting in Next.js',
15 | display: 'hidden'
16 | },
17 | 'next-intl-3-0': {
18 | title: 'next-intl 3.0',
19 | display: 'hidden'
20 | },
21 | 'translations-outside-of-react-components': {
22 | title: 'How (not) to use translations outside of React components',
23 | display: 'hidden'
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | 'getting-started': 'Getting started',
3 | usage: 'Usage guide',
4 | routing: 'Routing',
5 | environments: 'Environments',
6 | workflows: 'Workflows & integrations',
7 | 'design-principles': 'Design principles'
8 | };
9 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/environments/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | 'server-client-components': 'Server & Client Components',
3 | 'actions-metadata-route-handlers':
4 | 'Server Actions, Metadata & Route Handlers',
5 | 'error-files': 'Error files (e.g. not-found)',
6 | mdx: 'Markdown (MDX)',
7 | 'core-library': 'Core library',
8 | 'runtime-requirements': 'Runtime requirements',
9 | testing: 'Testing'
10 | };
11 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/getting-started/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app-router': 'App Router',
3 | 'pages-router': 'Pages Router'
4 | };
5 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/getting-started/app-router/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | 'with-i18n-routing': 'With i18n routing',
3 | 'without-i18n-routing': 'Without i18n routing'
4 | };
5 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/routing/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | middleware: 'Middleware',
3 | navigation: 'Navigation'
4 | };
5 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/usage.mdx:
--------------------------------------------------------------------------------
1 | import Callout from '@/components/Callout';
2 | import Card from '@/components/Card';
3 | import Cards from '@/components/Cards';
4 |
5 | # Usage guide
6 |
7 | This guide explains how `next-intl` can be used in React components.
8 |
9 | Note that while it might be helpful to get an overview of the capabilities of `next-intl`, you don't have to read the whole usage guide upfront. Rather, you can come back to it as necessary.
10 |
11 | Let's get started:
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/usage/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | messages: 'Messages',
3 | numbers: 'Numbers',
4 | 'dates-times': 'Dates and times',
5 | lists: 'Lists',
6 | configuration: 'Global configuration'
7 | };
8 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/workflows.mdx:
--------------------------------------------------------------------------------
1 | import Card from '@/components/Card';
2 | import Cards from '@/components/Cards';
3 |
4 | # Workflows & integrations
5 |
6 | To get the most out of `next-intl`, you can choose from these integrations to improve your workflow.
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/workflows/_meta.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | typescript: 'TypeScript augmentation',
3 | 'localization-management': 'Localization management with Crowdin',
4 | 'vscode-integration': 'VSCode integration',
5 | linting: 'ESLint',
6 | storybook: 'Storybook',
7 | messages: 'Validating messages'
8 | };
9 |
--------------------------------------------------------------------------------
/docs/src/services/BrowserTracker.tsx:
--------------------------------------------------------------------------------
1 | import * as vercel from '@vercel/analytics/react';
2 |
3 | type Event = {
4 | name: 'partner-referral';
5 | data: {href: string; name: string};
6 | };
7 |
8 | export default class BrowserTracker {
9 | public static trackEvent({data, name}: Event) {
10 | return vercel.track(name, data);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/docs/src/services/ServerTracker.tsx:
--------------------------------------------------------------------------------
1 | import * as vercel from '@vercel/analytics/server';
2 |
3 | export default class ServerTracker {
4 | public static async trackEvent({
5 | data,
6 | name
7 | }: {
8 | data?: Record;
9 | name: string;
10 | }) {
11 | return vercel.track(name, data).catch((error) => {
12 | throw new Error('Vercel tracking failed', {cause: error});
13 | });
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 | /test-results/
6 | /playwright-report/
7 | /playwright/.cache/
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/README.md:
--------------------------------------------------------------------------------
1 | # example-app-router-migration
2 |
3 | An example that showcases a basic installation of `next-intl` in a Next.js app that uses both the Pages as well as the App Router.
4 |
5 | **Important**: Do not add an `i18n` config to `next.config.js`, as it will cause the `app` directory to be ignored.
6 |
7 | ## Deploy your own
8 |
9 | By deploying to [Vercel](https://vercel.com), you can check out the example in action. Note that you'll be prompted to create a new GitHub repository as part of this, allowing you to make subsequent changes.
10 |
11 | [](https://vercel.com/new/clone?repository-url=https://github.com/amannn/next-intl/tree/main/examples/example-app-router-migration)
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | {
15 | rules: {
16 | '@typescript-eslint/no-explicit-any': 'off'
17 | }
18 | }
19 | ];
20 |
21 | export default eslintConfig;
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/messages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Start",
4 | "description": "Das ist die Startseite.",
5 | "navigateToAbout": "Zu Über uns navigieren"
6 | },
7 | "About": {
8 | "title": "Über",
9 | "description": "Das ist die Über-uns-Seite."
10 | },
11 | "LocaleSwitcher": {
12 | "switchLocale": "Zu {locale, select, de {Deutsch} en {Englisch} other {Unbekannt}} wechseln"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Home",
4 | "description": "This is the home page.",
5 | "navigateToAbout": "Navigate to about"
6 | },
7 | "About": {
8 | "title": "About",
9 | "description": "This is the about page."
10 | },
11 | "LocaleSwitcher": {
12 | "switchLocale": "Switch to {locale, select, de {German} en {English} other {Unknown}}"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | // NOTE: This file should not be edited
6 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 | import createNextIntlPlugin from 'next-intl/plugin';
3 |
4 | const withNextIntl = createNextIntlPlugin();
5 |
6 | const config: NextConfig = {};
7 |
8 | export default withNextIntl(config);
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router-migration/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/app/[locale]/page.tsx:
--------------------------------------------------------------------------------
1 | import {useLocale, useTranslations} from 'next-intl';
2 | import LocaleSwitcher from '@/components/LocaleSwitcher';
3 | import PageLayout from '@/components/PageLayout';
4 | import {Link} from '@/i18n/navigation';
5 |
6 | export default function Index() {
7 | const t = useTranslations('Index');
8 | const locale = useLocale();
9 |
10 | return (
11 |
12 | {t('description')}
13 |
14 |
15 |
16 | {t('navigateToAbout')}
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | // Since we have a `not-found.tsx` page on the root, a layout file
8 | // is required, even if it's just passing children through.
9 | export default function RootLayout({children}: Props) {
10 | return children;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Error from 'next/error';
4 |
5 | // Render the default Next.js 404 page when a route
6 | // is requested that doesn't match the middleware and
7 | // therefore doesn't have a locale associated with it.
8 |
9 | export default function NotFound() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/components/LocaleSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {useLocale, useTranslations} from 'next-intl';
3 |
4 | export default function LocaleSwitcher() {
5 | const t = useTranslations('LocaleSwitcher');
6 | const locale = useLocale();
7 | const otherLocale = locale === 'en' ? 'de' : 'en';
8 |
9 | return (
10 |
11 | {t('switchLocale', {locale: otherLocale})}
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/components/PageLayout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children?: ReactNode;
5 | title: string;
6 | };
7 |
8 | export default function PageLayout({children, title}: Props) {
9 | return (
10 | <>
11 |
18 |
19 |
{title}
20 | {children}
21 |
22 |
23 | >
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/i18n/navigation.ts:
--------------------------------------------------------------------------------
1 | import {createNavigation} from 'next-intl/navigation';
2 | import {routing} from './routing';
3 |
4 | export const {Link, redirect, usePathname, useRouter} =
5 | createNavigation(routing);
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import {hasLocale} from 'next-intl';
2 | import {getRequestConfig} from 'next-intl/server';
3 | import {routing} from './routing';
4 |
5 | export default getRequestConfig(async ({requestLocale}) => {
6 | // Typically corresponds to the `[locale]` segment
7 | const requested = await requestLocale;
8 | const locale = hasLocale(routing.locales, requested)
9 | ? requested
10 | : routing.defaultLocale;
11 |
12 | return {
13 | locale,
14 | messages: (await import(`../../messages/${locale}.json`)).default
15 | };
16 | });
17 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/i18n/routing.ts:
--------------------------------------------------------------------------------
1 | import {defineRouting} from 'next-intl/routing';
2 |
3 | export const routing = defineRouting({
4 | locales: ['en', 'de'],
5 | defaultLocale: 'en'
6 | });
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import createMiddleware from 'next-intl/middleware';
2 | import {routing} from './i18n/routing';
3 |
4 | export default createMiddleware(routing);
5 |
6 | export const config = {
7 | // Match all pathnames except for
8 | // - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
9 | // - … the ones containing a dot (e.g. `favicon.ico`)
10 | matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
11 | };
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import {AppProps} from 'next/app';
2 | import {NextRouter, withRouter} from 'next/router';
3 | import {NextIntlClientProvider} from 'next-intl';
4 |
5 | type Props = AppProps & {
6 | router: NextRouter;
7 | };
8 |
9 | function App({Component, pageProps, router}: Props) {
10 | return (
11 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default withRouter(App);
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-migration/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "strict": true,
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | },
24 | "strictNullChecks": true
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 | test-results
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | {
15 | rules: {
16 | '@typescript-eslint/no-explicit-any': 'off'
17 | }
18 | }
19 | ];
20 |
21 | export default eslintConfig;
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/global.ts:
--------------------------------------------------------------------------------
1 | import {locales} from '@/config';
2 | import messages from './messages/en.json';
3 |
4 | declare module 'next-intl' {
5 | interface AppConfig {
6 | Locale: (typeof locales)[number];
7 | Messages: typeof messages;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/messages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "About": {
3 | "title": "Über uns"
4 | },
5 | "App": {
6 | "title": "Startseite"
7 | },
8 | "AppNavigation": {
9 | "home": "Start",
10 | "profile": "Profil"
11 | },
12 | "Index": {
13 | "title": "Start"
14 | },
15 | "Login": {
16 | "label": "Anmelden →"
17 | },
18 | "Logout": {
19 | "label": "Abmelden →"
20 | },
21 | "PublicNavigation": {
22 | "about": "Über uns",
23 | "home": "Start"
24 | },
25 | "Profile": {
26 | "title": "Profil"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "About": {
3 | "title": "About"
4 | },
5 | "App": {
6 | "title": "Home"
7 | },
8 | "AppNavigation": {
9 | "home": "Home",
10 | "profile": "Profile"
11 | },
12 | "Index": {
13 | "title": "Home"
14 | },
15 | "Login": {
16 | "label": "Login →"
17 | },
18 | "Logout": {
19 | "label": "Logout →"
20 | },
21 | "PublicNavigation": {
22 | "about": "About",
23 | "home": "Home"
24 | },
25 | "Profile": {
26 | "title": "Profile"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 | import createNextIntlPlugin from 'next-intl/plugin';
3 |
4 | const withNextIntl = createNextIntlPlugin();
5 |
6 | const config: NextConfig = {};
7 |
8 | export default withNextIntl(config);
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/playwright.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import type {PlaywrightTestConfig} from '@playwright/test';
3 | import {devices} from '@playwright/test';
4 |
5 | // Use a distinct port on CI to avoid conflicts during concurrent tests
6 | const PORT = process.env.CI ? 3002 : 3000;
7 |
8 | const config: PlaywrightTestConfig = {
9 | retries: process.env.CI ? 1 : 0,
10 | testDir: './tests',
11 | projects: [
12 | {
13 | name: 'chromium',
14 | use: devices['Desktop Chrome']
15 | }
16 | ],
17 | fullyParallel: true,
18 | webServer: {
19 | command: `PORT=${PORT} pnpm start`,
20 | port: PORT,
21 | reuseExistingServer: true
22 | }
23 | };
24 |
25 | export default config;
26 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/NavLink.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useSelectedLayoutSegment} from 'next/navigation';
4 | import {ComponentProps} from 'react';
5 | import {Link} from '@/i18n/navigation.public';
6 |
7 | export default function NavLink({href, ...rest}: ComponentProps) {
8 | const selectedLayoutSegment = useSelectedLayoutSegment();
9 | const pathname = selectedLayoutSegment ? `/${selectedLayoutSegment}` : '/';
10 | const isActive = pathname === href;
11 |
12 | return (
13 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigation.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import Login from './Login';
3 | import NavLink from './NavLink';
4 |
5 | export default function PublicNavigation() {
6 | const t = useTranslations('PublicNavigation');
7 |
8 | return (
9 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/about/page.tsx:
--------------------------------------------------------------------------------
1 | import {Locale, useTranslations} from 'next-intl';
2 | import {setRequestLocale} from 'next-intl/server';
3 | import {use} from 'react';
4 | import PageTitle from '@/components/PageTitle';
5 |
6 | type Props = {
7 | params: Promise<{locale: Locale}>;
8 | };
9 |
10 | export default function About({params}: Props) {
11 | const {locale} = use(params);
12 |
13 | // Enable static rendering
14 | setRequestLocale(locale);
15 |
16 | const t = useTranslations('About');
17 | return {t('title')};
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/page.tsx:
--------------------------------------------------------------------------------
1 | import {Locale, useTranslations} from 'next-intl';
2 | import {setRequestLocale} from 'next-intl/server';
3 | import {use} from 'react';
4 | import PageTitle from '@/components/PageTitle';
5 |
6 | type Props = {
7 | params: Promise<{locale: Locale}>;
8 | };
9 |
10 | export default function Index({params}: Props) {
11 | const {locale} = use(params);
12 |
13 | // Enable static rendering
14 | setRequestLocale(locale);
15 |
16 | const t = useTranslations('Index');
17 | return {t('title')};
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/app/AppNavigation.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import NavLink from './NavLink';
3 |
4 | export default function AppNavigation() {
5 | const t = useTranslations('AppNavigation');
6 |
7 | return (
8 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/app/AppNavigationLocaleButton.tsx:
--------------------------------------------------------------------------------
1 | import {Locale, useLocale} from 'next-intl';
2 |
3 | type Props = {
4 | locale: Locale;
5 | };
6 |
7 | export default function AppNavigationLocaleButton({locale}: Props) {
8 | const curLocale = useLocale();
9 |
10 | return (
11 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/app/Logout.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {useTranslations} from 'next-intl';
3 |
4 | export default function Logout() {
5 | const t = useTranslations('Logout');
6 | return {t('label')};
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/app/NavLink.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Link from 'next/link';
4 | import {usePathname} from 'next/navigation';
5 | import {ComponentProps} from 'react';
6 |
7 | export default function NavLink({href, ...rest}: ComponentProps) {
8 | const pathname = usePathname();
9 | const isActive = pathname === href;
10 |
11 | return (
12 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/app/page.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import PageTitle from '@/components/PageTitle';
3 |
4 | export default function App() {
5 | const t = useTranslations('App');
6 | return {t('title')};
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/app/profile/page.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import PageTitle from '@/components/PageTitle';
3 |
4 | export default function Profile() {
5 | const t = useTranslations('Profile');
6 | return {t('title')};
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router-mixed-routing/src/app/favicon.ico
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | export default function RootLayout({children}: Props) {
8 | return children;
9 | }
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Error from 'next/error';
4 | import Document from '@/components/Document';
5 |
6 | export default function NotFound() {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/components/Document.tsx:
--------------------------------------------------------------------------------
1 | import {Inter} from 'next/font/google';
2 | import {ReactNode} from 'react';
3 | import './globals.css';
4 |
5 | const inter = Inter({
6 | subsets: ['latin'],
7 | display: 'swap'
8 | });
9 |
10 | type Props = {
11 | children: ReactNode;
12 | locale: string;
13 | };
14 |
15 | export default function Document({children, locale}: Props) {
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/components/PageTitle.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | export default function PageTitle({children}: Props) {
8 | return {children}
;
9 | }
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/components/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/config.ts:
--------------------------------------------------------------------------------
1 | import {Locale} from 'next-intl';
2 |
3 | export const locales = ['en', 'de'] as const;
4 |
5 | export const defaultLocale: Locale = 'en';
6 |
7 | // This cookie name is used by `next-intl` on the public pages too. By
8 | // reading/writing to this locale, we can ensure that the user's locale
9 | // is consistent across public and private pages. In case you save the
10 | // locale of registered users in a database, you can of course also use
11 | // that instead when the user is logged in.
12 | export const COOKIE_NAME = 'NEXT_LOCALE';
13 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/i18n/navigation.public.ts:
--------------------------------------------------------------------------------
1 | import {createNavigation} from 'next-intl/navigation';
2 | import {routing} from './routing.public';
3 |
4 | // Should only be used on public routes in the `[locale]` segment
5 | export const {Link, usePathname} = createNavigation(routing);
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/i18n/routing.public.ts:
--------------------------------------------------------------------------------
1 | import {defineRouting} from 'next-intl/routing';
2 | import {defaultLocale, locales} from '../config';
3 |
4 | export const routing = defineRouting({
5 | locales,
6 | defaultLocale
7 | });
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import createMiddleware from 'next-intl/middleware';
2 | import {routing} from './i18n/routing.public';
3 |
4 | export default createMiddleware(routing);
5 |
6 | export const config = {
7 | // Match all pathnames except for
8 | // - … if they start with `/app`, `/_next` or `/_vercel`
9 | // - … the ones containing a dot (e.g. `favicon.ico`)
10 | matcher: '/((?!app|_next|_vercel|.*\\..*).*)'
11 | };
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type {Config} from 'tailwindcss';
2 |
3 | const config: Config = {
4 | content: [
5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}'
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
15 | }
16 | }
17 | },
18 | plugins: []
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/examples/example-app-router-mixed-routing/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "strict": true,
6 | "allowJs": true,
7 | "skipLibCheck": 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 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/.env:
--------------------------------------------------------------------------------
1 | NEXTAUTH_SECRET=secret
2 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 | /test-results/
6 | /playwright-report/
7 | /playwright/.cache/
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/README.md:
--------------------------------------------------------------------------------
1 | # example-app-router-next-auth
2 |
3 | An example that showcases the usage of `next-intl` together with Auth.js and the App Router.
4 |
5 | **Credentials**: admin / admin
6 |
7 | See [the corresponding docs for this example](https://next-intl.dev/docs/routing/middleware#example-auth-js).
8 |
9 | ## Deploy your own
10 |
11 | By deploying to [Vercel](https://vercel.com), you can check out the example in action. Note that you'll be prompted to create a new GitHub repository as part of this, allowing you to make subsequent changes.
12 |
13 | [](https://vercel.com/new/clone?repository-url=https://github.com/amannn/next-intl/tree/main/examples/example-app-router-next-auth)
14 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | {
15 | rules: {
16 | '@typescript-eslint/no-explicit-any': 'off'
17 | }
18 | }
19 | ];
20 |
21 | export default eslintConfig;
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/global.ts:
--------------------------------------------------------------------------------
1 | import {routing} from '@/i18n/routing';
2 | import messages from './messages/en.json';
3 |
4 | declare module 'next-intl' {
5 | interface AppConfig {
6 | Locale: (typeof routing.locales)[number];
7 | Messages: typeof messages;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 | import createNextIntlPlugin from 'next-intl/plugin';
3 |
4 | const withNextIntl = createNextIntlPlugin();
5 |
6 | const config: NextConfig = {};
7 |
8 | export default withNextIntl(config);
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/playwright.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import type {PlaywrightTestConfig} from '@playwright/test';
3 | import {devices} from '@playwright/test';
4 |
5 | // Use a distinct port on CI to avoid conflicts during concurrent tests
6 | const PORT = process.env.CI ? 3003 : 3000;
7 |
8 | const config: PlaywrightTestConfig = {
9 | retries: process.env.CI ? 1 : 0,
10 | testDir: './tests',
11 | projects: [
12 | {
13 | name: 'chromium',
14 | use: devices['Desktop Chrome']
15 | }
16 | ],
17 | fullyParallel: true,
18 | webServer: {
19 | command: `PORT=${PORT} pnpm start`,
20 | port: PORT,
21 | reuseExistingServer: true
22 | }
23 | };
24 |
25 | export default config;
26 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router-next-auth/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/app/[locale]/page.tsx:
--------------------------------------------------------------------------------
1 | import {getServerSession} from 'next-auth';
2 | import auth from '@/auth';
3 | import Index from './Index';
4 |
5 | export default async function IndexPage() {
6 | const session = await getServerSession(auth);
7 | return ;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/app/[locale]/secret/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useTranslations} from 'next-intl';
4 | import PageLayout from '@/components/PageLayout';
5 |
6 | export default function Secret() {
7 | const t = useTranslations('Secret');
8 |
9 | return (
10 |
11 | {t('description')}
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth';
2 | import auth from '../../../../auth';
3 |
4 | const handler = NextAuth(auth);
5 |
6 | export {handler as GET, handler as POST};
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | // Since we have a `not-found.tsx` page on the root, a layout file
8 | // is required, even if it's just passing children through.
9 | export default function RootLayout({children}: Props) {
10 | return children;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Error from 'next/error';
4 |
5 | // Render the default Next.js 404 page when a route
6 | // is requested that doesn't match the middleware and
7 | // therefore doesn't have a locale associated with it.
8 |
9 | export default function NotFound() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/auth.ts:
--------------------------------------------------------------------------------
1 | import {AuthOptions} from 'next-auth';
2 | import CredentialsProvider from 'next-auth/providers/credentials';
3 |
4 | const auth: AuthOptions = {
5 | providers: [
6 | CredentialsProvider({
7 | name: 'Credentials',
8 | credentials: {
9 | username: {type: 'text'},
10 | password: {type: 'password'}
11 | },
12 | authorize(credentials) {
13 | if (
14 | credentials?.username === 'admin' &&
15 | credentials.password === 'admin'
16 | ) {
17 | return {id: '1', name: 'admin'};
18 | }
19 |
20 | return null;
21 | }
22 | })
23 | ]
24 | };
25 |
26 | export default auth;
27 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/components/LocaleSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import {useLocale, useTranslations} from 'next-intl';
2 | import {Link, usePathname} from '@/i18n/navigation';
3 |
4 | export default function LocaleSwitcher() {
5 | const t = useTranslations('LocaleSwitcher');
6 | const locale = useLocale();
7 | const otherLocale = locale === 'en' ? 'de' : 'en';
8 | const pathname = usePathname();
9 |
10 | return (
11 |
12 | {t('switchLocale', {locale: otherLocale})}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/components/PageLayout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 | import LocaleSwitcher from './LocaleSwitcher';
3 |
4 | type Props = {
5 | children?: ReactNode;
6 | title: string;
7 | };
8 |
9 | export default function PageLayout({children, title}: Props) {
10 | return (
11 | <>
12 |
20 |
21 |
{title}
22 | {children}
23 |
24 |
25 |
26 |
27 |
28 | >
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/i18n/navigation.ts:
--------------------------------------------------------------------------------
1 | import {createNavigation} from 'next-intl/navigation';
2 | import {routing} from './routing';
3 |
4 | export const {Link, redirect, usePathname, useRouter} =
5 | createNavigation(routing);
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import {hasLocale} from 'next-intl';
2 | import {getRequestConfig} from 'next-intl/server';
3 | import {routing} from './routing';
4 |
5 | export default getRequestConfig(async ({requestLocale}) => {
6 | // Typically corresponds to the `[locale]` segment
7 | const requested = await requestLocale;
8 | const locale = hasLocale(routing.locales, requested)
9 | ? requested
10 | : routing.defaultLocale;
11 |
12 | return {
13 | locale,
14 | messages: (await import(`../../messages/${locale}.json`)).default
15 | };
16 | });
17 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/src/i18n/routing.ts:
--------------------------------------------------------------------------------
1 | import {defineRouting} from 'next-intl/routing';
2 |
3 | export const routing = defineRouting({
4 | locales: ['en', 'de'],
5 | defaultLocale: 'en',
6 | localePrefix: 'as-needed'
7 | });
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-next-auth/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "strict": true,
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 | *storybook.log
6 | storybook-static
7 | test-results
8 | messages/*.d.json.ts
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type {StorybookConfig} from '@storybook/nextjs';
2 |
3 | const config: StorybookConfig = {
4 | stories: ['../src/**/*.stories.tsx'],
5 | addons: ['storybook-next-intl'],
6 | framework: {
7 | name: '@storybook/nextjs',
8 | options: {}
9 | },
10 | staticDirs: ['../public']
11 | };
12 | export default config;
13 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/.storybook/next-intl.ts:
--------------------------------------------------------------------------------
1 | import en from '../messages/en.json';
2 | import de from '../messages/de.json';
3 | import es from '../messages/es.json';
4 | import ja from '../messages/ja.json';
5 |
6 | const messagesByLocale: Record = {en, de, es, ja};
7 |
8 | const nextIntl = {
9 | defaultLocale: 'en',
10 | messagesByLocale
11 | };
12 |
13 | export default nextIntl;
14 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | import type {Preview} from '@storybook/react';
2 | import nextIntl from './next-intl';
3 |
4 | const preview: Preview = {
5 | initialGlobals: {
6 | locale: 'en',
7 | locales: {
8 | en: 'English',
9 | de: 'Deutsch',
10 | es: 'Español',
11 | ja: '日本語'
12 | }
13 | },
14 | parameters: {
15 | nextIntl
16 | }
17 | };
18 |
19 | export default preview;
20 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/README.md:
--------------------------------------------------------------------------------
1 | # example-app-router-playground
2 |
3 | An example that showcases various advanced use cases of using `next-intl` with the App Router.
4 |
5 | ## Deploy your own
6 |
7 | By deploying to [Vercel](https://vercel.com), you can check out the example in action. Note that you'll be prompted to create a new GitHub repository as part of this, allowing you to make subsequent changes.
8 |
9 | [](https://vercel.com/new/clone?repository-url=https://github.com/amannn/next-intl/tree/main/examples/example-app-router-playground)
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | {
15 | rules: {
16 | '@typescript-eslint/no-explicit-any': 'off',
17 | '@typescript-eslint/ban-ts-comment': 'off'
18 | }
19 | }
20 | ];
21 |
22 | export default eslintConfig;
23 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/global.ts:
--------------------------------------------------------------------------------
1 | import {formats} from '@/i18n/request';
2 | import {routing} from '@/i18n/routing';
3 | import messages from './messages/en.json';
4 |
5 | declare module 'next-intl' {
6 | interface AppConfig {
7 | Locale: (typeof routing.locales)[number];
8 | Formats: typeof formats;
9 | Messages: typeof messages;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/jest.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | const nextJest = require('next/jest');
3 |
4 | const createJestConfig = nextJest({dir: './'});
5 |
6 | module.exports = async () => ({
7 | ...(await createJestConfig({
8 | testEnvironment: 'jsdom',
9 | rootDir: 'src'
10 | })()),
11 | transformIgnorePatterns: ['node_modules/(?!next-intl)/']
12 | });
13 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/public/assets/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router-playground/public/assets/image.jpg
--------------------------------------------------------------------------------
/examples/example-app-router-playground/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router-playground/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-app-router-playground/runPlaywright.mjs:
--------------------------------------------------------------------------------
1 | import {execSync} from 'child_process';
2 |
3 | const useCases = [
4 | 'main',
5 | 'locale-prefix-never',
6 | 'trailing-slash',
7 | 'base-path',
8 | 'domains',
9 | 'locale-cookie-false'
10 | ];
11 |
12 | for (const useCase of useCases) {
13 | // eslint-disable-next-line no-console
14 | console.log(`Running tests for use case: ${useCase}`);
15 |
16 | const command = `NEXT_PUBLIC_USE_CASE=${useCase} pnpm build && NEXT_PUBLIC_USE_CASE=${useCase} TEST_MATCH=${useCase}.spec.ts playwright test`;
17 | execSync(command, {stdio: 'inherit'});
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/[...rest]/page.tsx:
--------------------------------------------------------------------------------
1 | import {notFound} from 'next/navigation';
2 |
3 | export default function CatchAll() {
4 | notFound();
5 | }
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/about/de.mdx:
--------------------------------------------------------------------------------
1 | import AsyncComponent from '@/components/AsyncComponent';
2 | import Counter from '@/components/client/02-MessagesOnClientCounter/Counter';
3 |
4 | # Über uns
5 |
6 | Ein bisschen Text …
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/about/en.mdx:
--------------------------------------------------------------------------------
1 | import AsyncComponent from '@/components/AsyncComponent';
2 | import Counter from '@/components/client/02-MessagesOnClientCounter/Counter';
3 |
4 | # About
5 |
6 | Some text …
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/about/es.mdx:
--------------------------------------------------------------------------------
1 | import AsyncComponent from '@/components/AsyncComponent';
2 | import Counter from '@/components/client/02-MessagesOnClientCounter/Counter';
3 |
4 | # Acerca de
5 |
6 | Algun texto ...
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/about/page.tsx:
--------------------------------------------------------------------------------
1 | import {Locale} from 'next-intl';
2 |
3 | type Props = {
4 | params: Promise<{
5 | locale: Locale;
6 | }>;
7 | };
8 |
9 | export default async function AboutPage({params}: Props) {
10 | const {locale} = await params;
11 | const Content = (await import(`./${locale}.mdx`)).default;
12 | return ;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/actions/ListItem.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 |
3 | export default function ListItem({id}: {id: number}) {
4 | const t = useTranslations('ServerActions');
5 | return t('item', {id: String(id)});
6 | }
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/actions/ListItemAsync.tsx:
--------------------------------------------------------------------------------
1 | import {getTranslations} from 'next-intl/server';
2 |
3 | export default async function ListItemAsync({id}: {id: number}) {
4 | const t = await getTranslations('ServerActions');
5 | return t('item', {id: String(id)});
6 | }
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/actions/ListItemClient.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useTranslations} from 'next-intl';
4 |
5 | export default function ListItemClient({id}: {id: number}) {
6 | const t = useTranslations('ServerActions');
7 | return t('item', {id: String(id)});
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/actions/ZodForm.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {ReactNode, useActionState} from 'react';
4 | import {FormResult} from './ZodFormExample';
5 |
6 | type Props = {
7 | action(prev: unknown, data: FormData): Promise;
8 | children: ReactNode;
9 | };
10 |
11 | export default function ZodForm({action, children}: Props) {
12 | const [state, formAction] = useActionState(action, null);
13 | const hasErrors = state && !state.success;
14 |
15 | return (
16 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/api/route.ts:
--------------------------------------------------------------------------------
1 | import {NextRequest, NextResponse} from 'next/server';
2 | import {Locale} from 'next-intl';
3 | import {getTranslations} from 'next-intl/server';
4 |
5 | type Props = {
6 | params: Promise<{
7 | locale: Locale;
8 | }>;
9 | };
10 |
11 | export async function GET(request: NextRequest, props: Props) {
12 | const params = await props.params;
13 | const {locale} = params;
14 |
15 | const name = request.nextUrl.searchParams.get('name');
16 | if (!name) {
17 | return new Response('Search param `name` was not provided.', {status: 400});
18 | }
19 |
20 | const t = await getTranslations({locale, namespace: 'ApiRoute'});
21 | return NextResponse.json({message: t('hello', {name})});
22 | }
23 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/client/ClientContent.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useLocale, useNow, useTimeZone} from 'next-intl';
4 | import {Link, usePathname} from '@/i18n/navigation';
5 |
6 | export default function ClientContent() {
7 | const now = useNow();
8 | const timeZone = useTimeZone();
9 | const locale = useLocale();
10 |
11 | return (
12 | <>
13 | {now.toISOString()}
14 | Go to home
15 | {usePathname()}
16 | {timeZone}
17 | {locale}
18 | >
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/client/DelayedServerContent.tsx:
--------------------------------------------------------------------------------
1 | import {useNow} from 'next-intl';
2 | import {use} from 'react';
3 |
4 | export default function DelayedServerContent() {
5 | use(new Promise((resolve) => setTimeout(resolve, 50)));
6 | const now = useNow();
7 |
8 | return (
9 | <>
10 | {now.toISOString()}
11 | >
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/client/page.tsx:
--------------------------------------------------------------------------------
1 | import {NextIntlClientProvider, useNow, useTranslations} from 'next-intl';
2 | import PageLayout from '../../../components/PageLayout';
3 | import ClientContent from './ClientContent';
4 | import DelayedServerContent from './DelayedServerContent';
5 |
6 | export default function Client() {
7 | const t = useTranslations('Client');
8 | const now = useNow();
9 |
10 | return (
11 |
12 | {t('description')}
13 | {now.toISOString()}
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/client/redirect/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useLocale} from 'next-intl';
4 | import {redirect} from '@/i18n/navigation';
5 |
6 | export default function ClientRedirectPage() {
7 | const locale = useLocale();
8 | redirect({href: '/client', locale});
9 | }
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/nested/UnlocalizedPathname.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {usePathname} from '@/i18n/navigation';
4 |
5 | export default function UnlocalizedPathname() {
6 | return {usePathname()}
;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/nested/page.spec.tsx:
--------------------------------------------------------------------------------
1 | import {render, screen} from '@testing-library/react';
2 | import pick from 'lodash/pick';
3 | import {NextIntlClientProvider} from 'next-intl';
4 | import messages from '../../../../messages/en.json';
5 | import Nested, {generateMetadata} from './page';
6 |
7 | it('renders', () => {
8 | render(
9 |
10 |
11 |
12 | );
13 | screen.getByText('Nested');
14 | });
15 |
16 | it("can't generate metadata in a test", async () => {
17 | await expect(generateMetadata()).rejects.toThrow(
18 | '`getTranslations` is not supported in Client Components.'
19 | );
20 | });
21 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/nested/page.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import {getTranslations} from 'next-intl/server';
3 | import PageLayout from '../../../components/PageLayout';
4 | import UnlocalizedPathname from './UnlocalizedPathname';
5 |
6 | export async function generateMetadata() {
7 | const t = await getTranslations('Nested');
8 | return {
9 | title: t('title')
10 | };
11 | }
12 |
13 | export default function Nested() {
14 | const t = useTranslations('Nested');
15 |
16 | return (
17 |
18 | {t('description')}
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/news/just-in/page.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 |
3 | export default function NewsArticle() {
4 | const t = useTranslations('JustIn');
5 | return {t('title')}
;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/not-found.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import PageLayout from '../../components/PageLayout';
3 |
4 | export default function NotFound() {
5 | const t = useTranslations('NotFound');
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/opengraph-image.tsx:
--------------------------------------------------------------------------------
1 | import {ImageResponse} from 'next/og';
2 | import {Locale} from 'next-intl';
3 | import {getTranslations} from 'next-intl/server';
4 |
5 | type Props = {
6 | params: {
7 | locale: Locale;
8 | };
9 | };
10 |
11 | export default async function Image({params: {locale}}: Props) {
12 | const t = await getTranslations({locale, namespace: 'OpenGraph'});
13 | return new ImageResponse({t('title')}
);
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/[locale]/redirect/page.tsx:
--------------------------------------------------------------------------------
1 | import {useLocale} from 'next-intl';
2 | import {redirect} from '@/i18n/navigation';
3 |
4 | export default function Redirect() {
5 | const locale = useLocale();
6 | redirect({href: '/client', locale});
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | // Since we have a `not-found.tsx` page on the root, a layout file
8 | // is required, even if it's just passing children through.
9 | export default function RootLayout({children}: Props) {
10 | return children;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Error from 'next/error';
4 |
5 | // Render the default Next.js 404 page when a route
6 | // is requested that doesn't match the middleware and
7 | // therefore doesn't have a locale associated with it.
8 |
9 | export default function NotFound() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/AsyncComponentWithNamespaceAndLocale.tsx:
--------------------------------------------------------------------------------
1 | import {getLocale, getTranslations} from 'next-intl/server';
2 |
3 | export default async function AsyncComponentWithNamespaceAndLocale() {
4 | const locale = await getLocale();
5 | const t = await getTranslations({locale, namespace: 'AsyncComponent'});
6 |
7 | return (
8 |
9 | {t('basic')}
10 |
11 | );
12 | }
13 |
14 | export async function TypeTest() {
15 | const locale = await getLocale();
16 | const t = await getTranslations({locale});
17 |
18 | // @ts-expect-error
19 | await getTranslations({locale, namespace: 'Unknown'});
20 |
21 | // @ts-expect-error
22 | t('AsyncComponent.unknown');
23 | }
24 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/AsyncComponentWithoutNamespace.tsx:
--------------------------------------------------------------------------------
1 | import {getTranslations} from 'next-intl/server';
2 |
3 | export default async function AsyncComponentWithoutNamespace() {
4 | const t = await getTranslations();
5 |
6 | return (
7 |
8 | {t('AsyncComponent.basic')}
9 |
10 | );
11 | }
12 |
13 | export async function TypeTest() {
14 | const t = await getTranslations();
15 |
16 | // @ts-expect-error
17 | t('AsyncComponent.unknown');
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/AsyncComponentWithoutNamespaceAndLocale.tsx:
--------------------------------------------------------------------------------
1 | import {getLocale, getTranslations} from 'next-intl/server';
2 |
3 | export default async function AsyncComponentWithoutNamespaceAndLocale() {
4 | const locale = await getLocale();
5 | const t = await getTranslations({locale});
6 |
7 | return (
8 |
9 | {t('AsyncComponent.basic')}
10 |
11 | );
12 | }
13 |
14 | export async function TypeTest() {
15 | const locale = await getLocale();
16 | const t = await getTranslations({locale});
17 |
18 | // @ts-expect-error
19 | t('AsyncComponent.unknown');
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/ClientLink.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {ComponentProps} from 'react';
4 | import {Link} from '@/i18n/navigation';
5 |
6 | export default function NavigationLink(props: ComponentProps) {
7 | return ;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/ClientRouterWithoutProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useRouter} from '@/i18n/navigation';
4 |
5 | export default function ClientRouterWithoutProvider() {
6 | const router = useRouter();
7 |
8 | function onClick() {
9 | router.push('/nested');
10 | }
11 |
12 | return (
13 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/CoreLibrary.tsx:
--------------------------------------------------------------------------------
1 | import {createFormatter, createTranslator} from 'next-intl';
2 |
3 | export default function CoreLibrary() {
4 | const t = createTranslator({
5 | locale: 'en',
6 | messages: {Index: {title: 'Relative time:'}} as any
7 | });
8 |
9 | const now = new Date(2022, 10, 6, 20, 20, 0, 0);
10 | const format = createFormatter({locale: 'en', now});
11 | const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
12 |
13 | return (
14 |
15 | {t('Index.title')} {format.relativeTime(tomorrow)}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/DropdownMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as Dropdown from '@radix-ui/react-dropdown-menu';
2 | import {Link as NextIntlLink} from '@/i18n/navigation';
3 |
4 | export default function DropdownMenu() {
5 | return (
6 |
7 | Toggle dropdown
8 |
9 |
10 | Bar
11 |
12 | Link to about
13 |
14 | Foo
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/LocaleSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import {useLocale, useTranslations} from 'next-intl';
2 | import {Link} from '@/i18n/navigation';
3 |
4 | export default function LocaleSwitcher() {
5 | const t = useTranslations('LocaleSwitcher');
6 |
7 | const locale = useLocale();
8 | const otherLocale = locale === 'en' ? 'de' : 'en';
9 |
10 | return (
11 |
12 | {t('switchLocale', {locale: otherLocale})}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/Navigation.stories.tsx:
--------------------------------------------------------------------------------
1 | import type {Meta, StoryObj} from '@storybook/react';
2 | import Navigation from './Navigation';
3 |
4 | const meta = {
5 | title: 'components/Navigation',
6 | component: Navigation
7 | } satisfies Meta;
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const Default: Story = {};
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/NavigationLink.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useSelectedLayoutSegment} from 'next/navigation';
4 | import {ComponentProps} from 'react';
5 | import {Link} from '@/i18n/navigation';
6 |
7 | export default function NavigationLink({
8 | href,
9 | ...rest
10 | }: ComponentProps) {
11 | const selectedLayoutSegment = useSelectedLayoutSegment();
12 | const pathname = selectedLayoutSegment ? `/${selectedLayoutSegment}` : '/';
13 | const isActive = pathname === href;
14 |
15 | return (
16 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/PageLayout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children?: ReactNode;
5 | title: string;
6 | };
7 |
8 | export default function PageLayout({children, title}: Props) {
9 | return (
10 |
11 |
{title}
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/RichText.tsx:
--------------------------------------------------------------------------------
1 | import {ComponentProps, ReactNode} from 'react';
2 |
3 | type Tag = 'b' | 'i';
4 |
5 | type Props = {
6 | children(tags: Record ReactNode>): ReactNode;
7 | } & Omit, 'children'>;
8 |
9 | export default function RichText({children, ...rest}: Props) {
10 | return (
11 |
12 | {children({
13 | b: (chunks: ReactNode) => {chunks},
14 | i: (chunks: ReactNode) => {chunks}
15 | })}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/client/01-MessagesAsPropsCounter/ClientCounter.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useState} from 'react';
4 |
5 | type Props = {
6 | messages: {
7 | count: string;
8 | increment: string;
9 | };
10 | };
11 |
12 | export default function ClientCounter({messages}: Props) {
13 | const [count, setCount] = useState(0);
14 |
15 | function onIncrement() {
16 | setCount(count + 1);
17 | }
18 |
19 | return (
20 |
21 |
22 | {messages.count} {count}
23 |
24 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/client/01-MessagesAsPropsCounter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import ClientCounter from './ClientCounter';
3 |
4 | export default function Counter() {
5 | const t = useTranslations('Counter');
6 |
7 | return (
8 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/client/01-MessagesAsPropsCounter/index.tsx:
--------------------------------------------------------------------------------
1 | export {default} from './Counter';
2 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/client/02-MessagesOnClientCounter/ClientCounter.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {useTranslations} from 'next-intl';
4 | import {useState} from 'react';
5 |
6 | export default function ClientCounter() {
7 | const t = useTranslations('ClientCounter');
8 | const [count, setCount] = useState(0);
9 |
10 | function onIncrement() {
11 | setCount(count + 1);
12 | }
13 |
14 | return (
15 |
16 |
{t('count', {count: String(count)})}
17 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/client/02-MessagesOnClientCounter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import pick from 'lodash/pick';
2 | import {NextIntlClientProvider, useMessages} from 'next-intl';
3 | import ClientCounter from './ClientCounter';
4 |
5 | export default function Counter() {
6 | const messages = useMessages();
7 |
8 | return (
9 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/components/client/02-MessagesOnClientCounter/index.tsx:
--------------------------------------------------------------------------------
1 | export {default} from './Counter';
2 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/i18n/navigation.ts:
--------------------------------------------------------------------------------
1 | import {createNavigation} from 'next-intl/navigation';
2 | import {routing} from './routing';
3 |
4 | export const {Link, getPathname, redirect, usePathname, useRouter} =
5 | createNavigation(routing);
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/mdx-components.tsx:
--------------------------------------------------------------------------------
1 | import type {MDXComponents} from 'mdx/types';
2 |
3 | export function useMDXComponents(components: MDXComponents): MDXComponents {
4 | return components;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import createMiddleware from 'next-intl/middleware';
2 | import {routing} from './i18n/routing';
3 |
4 | export default createMiddleware(routing);
5 |
6 | export const config = {
7 | matcher: [
8 | // Skip all paths that should not be internationalized
9 | '/((?!_next|.*/opengraph-image|.*\\..*).*)',
10 |
11 | // Necessary for base path to work
12 | '/'
13 | ]
14 | };
15 |
--------------------------------------------------------------------------------
/examples/example-app-router-playground/tests/locale-cookie-false.spec.ts:
--------------------------------------------------------------------------------
1 | import {expect, test as it} from '@playwright/test';
2 |
3 | it('never sets a cookie', async ({page}) => {
4 | async function expectNoCookie() {
5 | expect(
6 | (await page.context().cookies()).find((cur) => cur.name === 'NEXT_LOCALE')
7 | ).toBeUndefined();
8 | }
9 |
10 | await page.goto('/');
11 | await expectNoCookie();
12 |
13 | await page.getByRole('link', {name: 'Switch to German'}).click();
14 | await expect(page).toHaveURL('/de');
15 | await expectNoCookie();
16 | });
17 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 | /test-results/
6 | /playwright-report/
7 | /playwright/.cache/
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/README.md:
--------------------------------------------------------------------------------
1 | # example-app-router-single-locale
2 |
3 | An example that showcases an app that only supports a single language and doesn't utilize i18n routing.
4 |
5 | ## Deploy your own
6 |
7 | By deploying to [Vercel](https://vercel.com), you can check out the example in action. Note that you'll be prompted to create a new GitHub repository as part of this, allowing you to make subsequent changes.
8 |
9 | [](https://vercel.com/new/clone?repository-url=https://github.com/amannn/next-intl/tree/main/examples/example-app-router-single-locale)
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
6 | const compat = new FlatCompat({
7 | baseDirectory: __dirname
8 | });
9 |
10 | const eslintConfig = [
11 | ...compat.extends('next/core-web-vitals', 'next/typescript')
12 | ];
13 | export default eslintConfig;
14 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Home",
4 | "description": "This is the home page.",
5 | "navigateToAbout": "Navigate to about"
6 | },
7 | "About": {
8 | "title": "About",
9 | "description": "This is the about page."
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/next.config.mjs:
--------------------------------------------------------------------------------
1 | import createNextIntlPlugin from 'next-intl/plugin';
2 |
3 | const withNextIntl = createNextIntlPlugin();
4 |
5 | /** @type {import('next').NextConfig} */
6 | const nextConfig = {};
7 |
8 | export default withNextIntl(nextConfig);
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 | import createNextIntlPlugin from 'next-intl/plugin';
3 |
4 | const withNextIntl = createNextIntlPlugin();
5 |
6 | const config: NextConfig = {};
7 |
8 | export default withNextIntl(config);
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/playwright.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import type {PlaywrightTestConfig} from '@playwright/test';
3 | import {devices} from '@playwright/test';
4 |
5 | // Use a distinct port on CI to avoid conflicts during concurrent tests
6 | const PORT = process.env.CI ? 3005 : 3000;
7 |
8 | const config: PlaywrightTestConfig = {
9 | retries: process.env.CI ? 1 : 0,
10 | testDir: './tests',
11 | projects: [
12 | {
13 | name: 'chromium',
14 | use: devices['Desktop Chrome']
15 | }
16 | ],
17 | fullyParallel: true,
18 | webServer: {
19 | command: `PORT=${PORT} pnpm start`,
20 | port: PORT,
21 | reuseExistingServer: true
22 | }
23 | };
24 |
25 | export default config;
26 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router-single-locale/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/src/app/about/page.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import PageLayout from '@/components/PageLayout';
3 |
4 | export default function About() {
5 | const t = useTranslations('About');
6 |
7 | return (
8 |
9 | {t('description')}
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import {NextIntlClientProvider} from 'next-intl';
2 | import {getLocale} from 'next-intl/server';
3 | import {ReactNode} from 'react';
4 |
5 | type Props = {
6 | children: ReactNode;
7 | };
8 |
9 | export default async function LocaleLayout({children}: Props) {
10 | const locale = await getLocale();
11 |
12 | return (
13 |
14 |
15 | next-intl
16 |
17 |
18 | {children}
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {useTranslations} from 'next-intl';
3 | import PageLayout from '@/components/PageLayout';
4 |
5 | export default function Index() {
6 | const t = useTranslations('Index');
7 |
8 | return (
9 |
10 | {t('description')}
11 | {t('navigateToAbout')}
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/src/components/PageLayout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children?: ReactNode;
5 | title: string;
6 | };
7 |
8 | export default function PageLayout({children, title}: Props) {
9 | return (
10 | <>
11 |
18 |
19 |
{title}
20 | {children}
21 |
22 |
23 | >
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/src/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import {getRequestConfig} from 'next-intl/server';
2 |
3 | export default getRequestConfig(async () => {
4 | const locale = 'en';
5 |
6 | return {
7 | locale,
8 | messages: (await import(`../../messages/${locale}.json`)).default
9 | };
10 | });
11 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/tests/main.spec.ts:
--------------------------------------------------------------------------------
1 | import {test as it} from '@playwright/test';
2 |
3 | it('uses the provided locale', async ({page}) => {
4 | await page.goto('/');
5 | page.getByRole('heading', {name: 'Home'});
6 |
7 | await page.goto('/');
8 | page.getByRole('heading', {name: 'About'});
9 | });
10 |
--------------------------------------------------------------------------------
/examples/example-app-router-single-locale/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "strict": true,
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | },
24 | "strictNullChecks": true
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 | /test-results/
6 | /playwright-report/
7 | /playwright/.cache/
8 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript')
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/next.config.mjs:
--------------------------------------------------------------------------------
1 | import createNextIntlPlugin from 'next-intl/plugin';
2 |
3 | const withNextIntl = createNextIntlPlugin();
4 |
5 | /** @type {import('next').NextConfig} */
6 | const nextConfig = {};
7 |
8 | export default withNextIntl(nextConfig);
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 | import createNextIntlPlugin from 'next-intl/plugin';
3 |
4 | const withNextIntl = createNextIntlPlugin();
5 |
6 | const config: NextConfig = {};
7 |
8 | export default withNextIntl(config);
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/playwright.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import type {PlaywrightTestConfig} from '@playwright/test';
3 | import {devices} from '@playwright/test';
4 |
5 | // Use a distinct port on CI to avoid conflicts during concurrent tests
6 | const PORT = process.env.CI ? 3006 : 3000;
7 |
8 | const config: PlaywrightTestConfig = {
9 | retries: process.env.CI ? 1 : 0,
10 | testDir: './tests',
11 | projects: [
12 | {
13 | name: 'chromium',
14 | use: devices['Desktop Chrome']
15 | }
16 | ],
17 | fullyParallel: true,
18 | webServer: {
19 | command: `PORT=${PORT} pnpm start`,
20 | port: PORT,
21 | reuseExistingServer: true
22 | }
23 | };
24 |
25 | export default config;
26 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router-without-i18n-routing/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/app/app/page.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 |
3 | export default function HomePage() {
4 | const t = useTranslations('HomePage');
5 | return (
6 | {t('title')}
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/app/app/profile/page.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 |
3 | export default function ProfilePage() {
4 | const t = useTranslations('ProfilePage');
5 | return (
6 | {t('title')}
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | * {
6 | -webkit-font-smoothing: antialiased;
7 | @apply outline-sky-600;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import {redirect} from 'next/navigation';
2 |
3 | export default function IndexPage() {
4 | redirect('/login');
5 | }
6 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {ComponentProps} from 'react';
4 | import {useFormStatus} from 'react-dom';
5 |
6 | export default function Button(props: ComponentProps<'button'>) {
7 | const {pending} = useFormStatus();
8 |
9 | return (
10 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/components/LocaleSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import {useLocale, useTranslations} from 'next-intl';
2 | import LocaleSwitcherSelect from './LocaleSwitcherSelect';
3 |
4 | export default function LocaleSwitcher() {
5 | const t = useTranslations('LocaleSwitcher');
6 | const locale = useLocale();
7 |
8 | return (
9 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/i18n/config.ts:
--------------------------------------------------------------------------------
1 | export type Locale = (typeof locales)[number];
2 |
3 | export const locales = ['en', 'de'] as const;
4 | export const defaultLocale: Locale = 'en';
5 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import {getRequestConfig} from 'next-intl/server';
2 | import {getUserLocale} from '../services/locale';
3 |
4 | export default getRequestConfig(async () => {
5 | const locale = await getUserLocale();
6 |
7 | return {
8 | locale,
9 | messages: (await import(`../../messages/${locale}.json`)).default
10 | };
11 | });
12 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/services/locale.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import {cookies} from 'next/headers';
4 | import {Locale, defaultLocale} from '@/i18n/config';
5 |
6 | // In this example the locale is read from a cookie. You could alternatively
7 | // also read it from a database, backend service, or any other source.
8 | const COOKIE_NAME = 'NEXT_LOCALE';
9 |
10 | export async function getUserLocale() {
11 | return (await cookies()).get(COOKIE_NAME)?.value || defaultLocale;
12 | }
13 |
14 | export async function setUserLocale(locale: Locale) {
15 | (await cookies()).set(COOKIE_NAME, locale);
16 | }
17 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/src/services/session.ts:
--------------------------------------------------------------------------------
1 | export async function loginUser(credentials: {
2 | email: string;
3 | password: string;
4 | }) {
5 | // In a real app, the credentials would be checked against a
6 | // database and potentially a session token set in a cookie
7 | return new Promise((resolve) => {
8 | setTimeout(() => {
9 | resolve(
10 | credentials.email === 'jane@doe.com' &&
11 | credentials.password === 'next-intl'
12 | );
13 | }, 1000);
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./src/**/*.{js,ts,jsx,tsx}'],
4 | theme: {
5 | extend: {}
6 | },
7 | plugins: []
8 | };
9 |
--------------------------------------------------------------------------------
/examples/example-app-router-without-i18n-routing/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "strict": true,
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | },
24 | "strictNullChecks": true
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/examples/example-app-router/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 | /test-results/
6 | /playwright-report/
7 | /playwright/.cache/
8 | out
9 | messages/en.d.json.ts
10 |
--------------------------------------------------------------------------------
/examples/example-app-router/README.md:
--------------------------------------------------------------------------------
1 | # example-app-router
2 |
3 | An example that showcases basic usage of `next-intl` with the App Router, including internationalized routing.
4 |
5 | [Demo](https://next-intl-example-app-router.vercel.app/)
6 |
7 | ## Deploy your own
8 |
9 | By deploying to [Vercel](https://vercel.com), you can check out the example in action. Note that you'll be prompted to create a new GitHub repository as part of this, allowing you to make subsequent changes.
10 |
11 | [](https://vercel.com/new/clone?repository-url=https://github.com/amannn/next-intl/tree/main/examples/example-app-router)
12 |
--------------------------------------------------------------------------------
/examples/example-app-router/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | {
15 | rules: {
16 | '@typescript-eslint/no-explicit-any': 'off'
17 | }
18 | }
19 | ];
20 |
21 | export default eslintConfig;
22 |
--------------------------------------------------------------------------------
/examples/example-app-router/global.ts:
--------------------------------------------------------------------------------
1 | import {routing} from '@/i18n/routing';
2 | import messages from './messages/en.json';
3 |
4 | declare module 'next-intl' {
5 | interface AppConfig {
6 | Locale: (typeof routing.locales)[number];
7 | Messages: typeof messages;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/example-app-router/jest.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | const nextJest = require('next/jest');
3 |
4 | const createJestConfig = nextJest({dir: './'});
5 |
6 | module.exports = async () => ({
7 | ...(await createJestConfig({
8 | testEnvironment: 'jsdom',
9 | rootDir: 'src'
10 | })()),
11 | transformIgnorePatterns: ['node_modules/(?!next-intl)/']
12 | });
13 |
--------------------------------------------------------------------------------
/examples/example-app-router/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-app-router/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 | import createNextIntlPlugin from 'next-intl/plugin';
3 |
4 | const withNextIntl = createNextIntlPlugin({
5 | experimental: {
6 | createMessagesDeclaration: './messages/en.json'
7 | }
8 | });
9 |
10 | const config: NextConfig = {};
11 |
12 | export default withNextIntl(config);
13 |
--------------------------------------------------------------------------------
/examples/example-app-router/playwright.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import type {PlaywrightTestConfig} from '@playwright/test';
3 | import {devices} from '@playwright/test';
4 |
5 | // Use a distinct port on CI to avoid conflicts during concurrent tests
6 | const PORT = process.env.CI ? 3001 : 3000;
7 |
8 | const config: PlaywrightTestConfig = {
9 | retries: process.env.CI ? 1 : 0,
10 | testDir: './tests',
11 | projects: [
12 | {
13 | name: 'chromium',
14 | use: devices['Desktop Chrome']
15 | }
16 | ],
17 | fullyParallel: true,
18 | webServer: {
19 | command: `PORT=${PORT} pnpm start`,
20 | port: PORT,
21 | reuseExistingServer: true
22 | }
23 | };
24 |
25 | export default config;
26 |
--------------------------------------------------------------------------------
/examples/example-app-router/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/examples/example-app-router/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-app-router/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/[locale]/[...rest]/page.tsx:
--------------------------------------------------------------------------------
1 | import {notFound} from 'next/navigation';
2 |
3 | export default function CatchAllPage() {
4 | notFound();
5 | }
6 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/[locale]/not-found.tsx:
--------------------------------------------------------------------------------
1 | // Note that `app/[locale]/[...rest]/page.tsx`
2 | // is necessary for this page to render.
3 |
4 | export {default} from '@/components/NotFoundPage';
5 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/[locale]/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | // Since we have a `not-found.tsx` page on the root, a layout file
8 | // is required, even if it's just passing children through.
9 | export default function RootLayout({children}: Props) {
10 | return children;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/manifest.ts:
--------------------------------------------------------------------------------
1 | import {MetadataRoute} from 'next';
2 | import {getTranslations} from 'next-intl/server';
3 | import {routing} from '@/i18n/routing';
4 |
5 | export default async function manifest(): Promise {
6 | const t = await getTranslations({
7 | locale: routing.defaultLocale,
8 | namespace: 'Manifest'
9 | });
10 |
11 | return {
12 | name: t('name'),
13 | start_url: '/',
14 | theme_color: '#101E33'
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Error from 'next/error';
4 |
5 | // This page renders when a route like `/unknown.txt` is requested.
6 | // In this case, the layout at `app/[locale]/layout.tsx` receives
7 | // an invalid value as the `[locale]` param and calls `notFound()`.
8 |
9 | export default function GlobalNotFound() {
10 | return (
11 |
12 |
13 | ;
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import {redirect} from 'next/navigation';
2 |
3 | // This page only renders when the app is built statically (output: 'export')
4 | export default function RootPage() {
5 | redirect('/en');
6 | }
7 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/app/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Allow: *
3 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | type Props = {
2 | title: string;
3 | description: string;
4 | href: string;
5 | };
6 |
7 | export default function ExternalLink({description, href, title}: Props) {
8 | return (
9 |
15 |
16 | {title} →
17 |
18 | {description}
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/components/LocaleSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import {useLocale, useTranslations} from 'next-intl';
2 | import {routing} from '@/i18n/routing';
3 | import LocaleSwitcherSelect from './LocaleSwitcherSelect';
4 |
5 | export default function LocaleSwitcher() {
6 | const t = useTranslations('LocaleSwitcher');
7 | const locale = useLocale();
8 |
9 | return (
10 |
11 | {routing.locales.map((cur) => (
12 |
15 | ))}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/components/Navigation.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import LocaleSwitcher from './LocaleSwitcher';
3 | import NavigationLink from './NavigationLink';
4 |
5 | export default function Navigation() {
6 | const t = useTranslations('Navigation');
7 |
8 | return (
9 |
10 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/components/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'next-intl';
2 | import PageLayout from './PageLayout';
3 |
4 | export default function NotFoundPage() {
5 | const t = useTranslations('NotFoundPage');
6 |
7 | return (
8 |
9 | {t('description')}
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/config.ts:
--------------------------------------------------------------------------------
1 | export const port = process.env.PORT || 3000;
2 | export const host = process.env.VERCEL_PROJECT_PRODUCTION_URL
3 | ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
4 | : `http://localhost:${port}`;
5 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/i18n/navigation.ts:
--------------------------------------------------------------------------------
1 | import {createNavigation} from 'next-intl/navigation';
2 | import {routing} from './routing';
3 |
4 | export const {Link, getPathname, redirect, usePathname, useRouter} =
5 | createNavigation(routing);
6 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import {hasLocale} from 'next-intl';
2 | import {getRequestConfig} from 'next-intl/server';
3 | import {routing} from './routing';
4 |
5 | export default getRequestConfig(async ({requestLocale}) => {
6 | // Typically corresponds to the `[locale]` segment
7 | const requested = await requestLocale;
8 | const locale = hasLocale(routing.locales, requested)
9 | ? requested
10 | : routing.defaultLocale;
11 |
12 | return {
13 | locale,
14 | messages: (await import(`../../messages/${locale}.json`)).default
15 | };
16 | });
17 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/i18n/routing.ts:
--------------------------------------------------------------------------------
1 | import {defineRouting} from 'next-intl/routing';
2 |
3 | export const routing = defineRouting({
4 | locales: ['en', 'de'],
5 | defaultLocale: 'en',
6 | pathnames: {
7 | '/': '/',
8 | '/pathnames': {
9 | de: '/pfadnamen'
10 | }
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/examples/example-app-router/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import createMiddleware from 'next-intl/middleware';
2 | import {routing} from './i18n/routing';
3 |
4 | export default createMiddleware(routing);
5 |
6 | export const config = {
7 | // Match all pathnames except for
8 | // - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
9 | // - … the ones containing a dot (e.g. `favicon.ico`)
10 | matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
11 | };
12 |
--------------------------------------------------------------------------------
/examples/example-app-router/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowArbitraryExtensions": true,
4 | "target": "es5",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "strict": true,
7 | "allowJs": true,
8 | "skipLibCheck": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "Bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./src/*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | {
15 | rules: {
16 | '@typescript-eslint/no-explicit-any': 'off'
17 | }
18 | }
19 | ];
20 |
21 | export default eslintConfig;
22 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/global.ts:
--------------------------------------------------------------------------------
1 | import messages from './messages/en.json';
2 |
3 | declare module 'next-intl' {
4 | interface AppConfig {
5 | Messages: typeof messages;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/jest.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | const nextJest = require('next/jest');
3 |
4 | const createJestConfig = nextJest({dir: './'});
5 |
6 | module.exports = async () => ({
7 | ...(await createJestConfig({
8 | testEnvironment: 'jsdom',
9 | rootDir: 'src'
10 | })()),
11 | transformIgnorePatterns: ['node_modules/(?!next-intl)/']
12 | });
13 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/messages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "About": {
3 | "title": "About",
4 | "lastUpdated": "Dieses Beispiel wurde {lastUpdatedRelative} aktualisiert ({lastUpdated, date, short})."
5 | },
6 | "Index": {
7 | "title": "Start",
8 | "description": "Nur das Minimum an {locale}
Übersetzungen sind geladen um diese Seite darzustellen.
Diese Namespaces sind verfügbar:
"
9 | },
10 | "Navigation": {
11 | "index": "Start",
12 | "about": "Über",
13 | "switchLocale": "Zu {locale, select, de {Deutsch} en {Englisch} other {Unbekannt}} wechseln"
14 | },
15 | "NotFound": {
16 | "title": "Entschuldigung, diese Seite konnte nicht gefunden werden."
17 | },
18 | "PageLayout": {
19 | "pageTitle": "next-intl"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 |
3 | const config = {
4 | i18n: {
5 | locales: ['en', 'de'],
6 | defaultLocale: 'en'
7 | }
8 | } satisfies NextConfig;
9 |
10 | export default config;
11 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-pages-router-advanced/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/src/components/Code.tsx:
--------------------------------------------------------------------------------
1 | import {ReactNode} from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | export default function Code({children}: Props) {
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import pick from 'lodash/pick';
2 | import {GetStaticPropsContext} from 'next';
3 | import {useTranslations} from 'next-intl';
4 | import PageLayout from 'components/PageLayout';
5 |
6 | export default function NotFound() {
7 | const t = useTranslations('NotFound');
8 | return ;
9 | }
10 |
11 | NotFound.messages = ['NotFound', ...PageLayout.messages];
12 |
13 | export async function getStaticProps({locale}: GetStaticPropsContext) {
14 | return {
15 | props: {
16 | messages: pick(
17 | (await import(`../../messages/${locale}.json`)).default,
18 | NotFound.messages
19 | )
20 | }
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/examples/example-pages-router-advanced/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "strict": true,
6 | "baseUrl": "src",
7 | "allowJs": true,
8 | "skipLibCheck": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.js", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/README.md:
--------------------------------------------------------------------------------
1 | # example-pages-router-legacy
2 |
3 | This example is used to track legacy support of `next-intl` with Next.js and React.
4 |
5 | ## Deploy your own
6 |
7 | By deploying to [Vercel](https://vercel.com), you can check out the example in action. Note that you'll be prompted to create a new GitHub repository as part of this, allowing you to make subsequent changes.
8 |
9 | [](https://vercel.com/new/clone?repository-url=https://github.com/amannn/next-intl/tree/main/examples/example-pages-router-legacy)
10 |
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Home",
4 | "description": "This is the home page."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | i18n: {
3 | defaultLocale: 'en',
4 | locales: ['en']
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-pages-router-legacy",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next dev",
6 | "lint": "prettier src --check",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "next": "^12.0.0",
12 | "react": "^17.0.0",
13 | "react-dom": "^17.0.0",
14 | "next-intl": "^4.0.0"
15 | },
16 | "devDependencies": {
17 | "prettier": "^3.3.3"
18 | },
19 | "prettier": {
20 | "singleQuote": true,
21 | "bracketSpacing": false,
22 | "trailingComma": "none"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-pages-router-legacy/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/src/components/PageLayout.js:
--------------------------------------------------------------------------------
1 | export default function PageLayout({children, title}) {
2 | return (
3 | <>
4 |
11 |
12 |
{title}
13 | {children}
14 |
15 |
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-pages-router-legacy/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import {useRouter} from 'next/router';
3 | import {NextIntlClientProvider} from 'next-intl';
4 |
5 | export default function App({Component, pageProps}) {
6 | const router = useRouter();
7 | const {messages, now, ...rest} = pageProps;
8 |
9 | return (
10 |
16 |
17 | example-pages-router-legacy
18 |
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/examples/example-pages-router/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.next/
3 | .DS_Store
4 | tsconfig.tsbuildinfo
5 |
--------------------------------------------------------------------------------
/examples/example-pages-router/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {dirname} from 'path';
2 | import {fileURLToPath} from 'url';
3 | import {FlatCompat} from '@eslint/eslintrc';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | {
15 | rules: {
16 | '@typescript-eslint/no-explicit-any': 'off'
17 | }
18 | }
19 | ];
20 |
21 | export default eslintConfig;
22 |
--------------------------------------------------------------------------------
/examples/example-pages-router/global.ts:
--------------------------------------------------------------------------------
1 | import messages from './messages/en.json';
2 |
3 | declare module 'next-intl' {
4 | interface AppConfig {
5 | Messages: typeof messages;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/example-pages-router/messages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Start",
4 | "description": "Das ist die Startseite."
5 | },
6 | "LocaleSwitcher": {
7 | "switchLocale": "Zu {locale, select, de {Deutsch} en {Englisch} other {Unbekannt}} wechseln"
8 | },
9 | "PageLayout": {
10 | "pageTitle": "next-intl"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-pages-router/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Home",
4 | "description": "This is the home page."
5 | },
6 | "LocaleSwitcher": {
7 | "switchLocale": "Switch to {locale, select, de {German} en {English} other {Unknown}}"
8 | },
9 | "PageLayout": {
10 | "pageTitle": "next-intl"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-pages-router/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/example-pages-router/next.config.ts:
--------------------------------------------------------------------------------
1 | import {NextConfig} from 'next';
2 |
3 | const config: NextConfig = {
4 | i18n: {
5 | locales: ['en', 'de'],
6 | defaultLocale: 'en'
7 | }
8 | };
9 |
10 | export default config;
11 |
--------------------------------------------------------------------------------
/examples/example-pages-router/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-pages-router/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-pages-router/src/components/LocaleSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {useRouter} from 'next/router';
3 | import {useTranslations} from 'next-intl';
4 |
5 | export default function LocaleSwitcher() {
6 | const t = useTranslations('LocaleSwitcher');
7 |
8 | const {locale, locales, route} = useRouter();
9 | const otherLocale = locales?.find((cur) => cur !== locale) as string;
10 |
11 | return (
12 |
13 | {t('switchLocale', {locale: otherLocale})}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/examples/example-pages-router/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import {AppProps} from 'next/app';
2 | import {useRouter} from 'next/router';
3 | import {NextIntlClientProvider} from 'next-intl';
4 |
5 | export default function App({Component, pageProps}: AppProps) {
6 | const router = useRouter();
7 |
8 | return (
9 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-pages-router/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import {GetStaticPropsContext} from 'next';
2 | import {useTranslations} from 'next-intl';
3 | import LocaleSwitcher from 'components/LocaleSwitcher';
4 | import PageLayout from 'components/PageLayout';
5 |
6 | export default function Index() {
7 | const t = useTranslations('Index');
8 |
9 | return (
10 |
11 | {t('description')}
12 |
13 |
14 | );
15 | }
16 |
17 | export async function getStaticProps({locale}: GetStaticPropsContext) {
18 | return {
19 | props: {
20 | messages: (await import(`../../messages/${locale}.json`)).default
21 | }
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/examples/example-pages-router/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "strict": true,
6 | "baseUrl": "src",
7 | "allowJs": true,
8 | "skipLibCheck": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-react-native/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/examples/example-react-native/README.md:
--------------------------------------------------------------------------------
1 | # example-react-native
2 |
3 | An example that showcases the usage of `use-intl` with React Native.
4 |
5 | Please double check that [the runtime requirements](https://next-intl.dev/docs/environments/runtime-requirements) are fulfilled.
6 |
7 | ```
8 | pnpm install
9 |
10 | # Run the app with one of:
11 | pnpm run android
12 | pnpm run ios
13 | pnpm run web
14 | ```
15 |
--------------------------------------------------------------------------------
/examples/example-react-native/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-react-native/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/examples/example-react-native/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-react-native/assets/favicon.png
--------------------------------------------------------------------------------
/examples/example-react-native/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-react-native/assets/icon.png
--------------------------------------------------------------------------------
/examples/example-react-native/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-react-native/assets/splash.png
--------------------------------------------------------------------------------
/examples/example-react-native/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo']
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/examples/example-remix/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /.cache
4 | /build
5 | /public/build
6 | .env
7 |
--------------------------------------------------------------------------------
/examples/example-remix/README.md:
--------------------------------------------------------------------------------
1 | # example-remix
2 |
3 | An example that showcases a basic installation of `use-intl` in a [Remix app](https://remix.run/).
4 |
5 | The relevant integration code is:
6 |
7 | 1. [Set up translations in `messages/`](./messages/en.json)
8 | 2. [Resolve the user locale based on the `accept-language` header and fetch the relevant messages in `app/root.tsx`](./app/root.tsx#L17)
9 | 3. [Set up `IntlProvider` in `app/root.tsx`](./app/root.tsx#L38)
10 | 4. [Use translations in `app/routes/_index.tsx`](./app/routes/_index.tsx#L4)
11 |
12 | You can run the example locally like this:
13 |
14 | ```
15 | npm install
16 | npm run dev
17 | ```
18 |
--------------------------------------------------------------------------------
/examples/example-remix/app/routes/_index.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'use-intl';
2 |
3 | export default function Index() {
4 | const t = useTranslations('Index');
5 |
6 | return (
7 |
14 |
15 |
{t('title')}
16 |
{t('description')}
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-remix/app/utils.tsx:
--------------------------------------------------------------------------------
1 | import fs from 'fs/promises';
2 | import path from 'path';
3 | import acceptLanguageParser from 'accept-language-parser';
4 |
5 | export function resolveLocale(request: Request) {
6 | const supportedLanguages = ['en', 'de'];
7 | const defaultLangauge = supportedLanguages[0];
8 | const locale =
9 | acceptLanguageParser.pick(
10 | supportedLanguages,
11 | request.headers.get('accept-language') || defaultLangauge
12 | ) || defaultLangauge;
13 |
14 | return locale;
15 | }
16 |
17 | export async function getMessages(locale: string) {
18 | const messagesPath = path.join(process.cwd(), `./messages/${locale}.json`);
19 | const content = await fs.readFile(messagesPath, 'utf-8');
20 | return JSON.parse(content);
21 | }
22 |
--------------------------------------------------------------------------------
/examples/example-remix/messages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Start",
4 | "description": "Das ist die Startseite."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/example-remix/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Index": {
3 | "title": "Home",
4 | "description": "This is the home page."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/example-remix/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/examples/example-remix/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-remix/remix.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@remix-run/dev/config').AppConfig}
3 | */
4 | export default {
5 | appDirectory: 'app',
6 | assetsBuildDirectory: 'public/build',
7 | publicPath: '/build/',
8 | serverBuildDirectory: 'build',
9 | devServerPort: 8002,
10 | ignoredRouteFiles: ['.*'],
11 | future: {
12 | v3_fetcherPersist: true,
13 | v3_relativeSplatPath: true,
14 | v3_throwAbortReason: true
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/examples/example-remix/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.ts", "**/*.tsx"],
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ES2022"],
5 | "strict": true,
6 | "types": ["@remix-run/node"],
7 | "isolatedModules": true,
8 | "esModuleInterop": true,
9 | "jsx": "react-jsx",
10 | "module": "ESNext",
11 | "moduleResolution": "Bundler",
12 | "resolveJsonModule": true,
13 | "target": "ES2022",
14 | "allowJs": true,
15 | "skipLibCheck": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "baseUrl": ".",
18 | "paths": {
19 | "~/*": ["./app/*"]
20 | },
21 | "noEmit": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/example-use-intl/.gitignore:
--------------------------------------------------------------------------------
1 | pnpm-debug.log*
2 | node_modules
3 | dist
4 | .vscode/*
5 | !.vscode/extensions.json
6 | .idea
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/examples/example-use-intl/README.md:
--------------------------------------------------------------------------------
1 | # example-use-intl
2 |
3 | An example that demonstrates a minimal setup for [`use-intl`](https://www.npmjs.com/package/use-intl), the base library of `next-intl`.
4 |
5 | ## Deploy your own
6 |
7 | By deploying to [Vercel](https://vercel.com), you can check out the example in action. Note that you'll be prompted to create a new GitHub repository as part of this, allowing you to make subsequent changes.
8 |
9 | [](https://vercel.com/new/clone?repository-url=https://github.com/amannn/next-intl/tree/main/examples/example-use-intl)
10 |
--------------------------------------------------------------------------------
/examples/example-use-intl/global.ts:
--------------------------------------------------------------------------------
1 | import 'use-intl';
2 | import messages from './messages/en.json';
3 | import {locales} from './src/config';
4 |
5 | declare module 'use-intl' {
6 | interface AppConfig {
7 | Locale: (typeof locales)[number];
8 | Messages: typeof messages;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/example-use-intl/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | example-use-intl
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/example-use-intl/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "App": {
3 | "hello": "Hello {username}!"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/example-use-intl/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-use-intl",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "lint": "tsc && prettier src --check",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^19.0.0",
13 | "react-dom": "^19.0.0",
14 | "use-intl": "^3.0.0"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^19.0.0",
18 | "@types/react-dom": "^19.0.0",
19 | "@vitejs/plugin-react": "^4.3.4",
20 | "prettier": "^3.3.3",
21 | "typescript": "^5.5.3",
22 | "vite": "^6.2.1"
23 | },
24 | "prettier": {
25 | "singleQuote": true,
26 | "bracketSpacing": false,
27 | "trailingComma": "none"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/example-use-intl/src/App.tsx:
--------------------------------------------------------------------------------
1 | import {useTranslations} from 'use-intl';
2 |
3 | type Props = {
4 | user: {
5 | name: string;
6 | };
7 | };
8 |
9 | export default function App({user}: Props) {
10 | const t = useTranslations('App');
11 | return {t('hello', {username: user.name})}
;
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-use-intl/src/config.tsx:
--------------------------------------------------------------------------------
1 | export const locales = ['en'] as const;
2 |
--------------------------------------------------------------------------------
/examples/example-use-intl/src/main.tsx:
--------------------------------------------------------------------------------
1 | import {StrictMode} from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import {IntlProvider} from 'use-intl';
4 | import en from '../messages/en.json';
5 | import App from './App.tsx';
6 |
7 | // You can get the messages from anywhere you like. You can also
8 | // fetch them from within a component and then render the provider
9 | // along with your app once you have the messages.
10 | const messages = en;
11 |
12 | const node = document.getElementById('root');
13 |
14 | ReactDOM.createRoot(node!).render(
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/examples/example-use-intl/src/vite-env.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/example-use-intl/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "strict": true,
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 | "moduleResolution": "bundler",
10 | "allowImportingTsExtensions": true,
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "noEmit": true,
14 | "jsx": "react-jsx"
15 | },
16 | "include": ["src", "global.ts"],
17 | "references": [{"path": "./tsconfig.node.json"}]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-use-intl/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-use-intl/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react';
2 | import {defineConfig} from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [react()]
6 | });
7 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "node_modules/@lerna-lite/cli/schemas/lerna-schema.json",
3 | "version": "4.1.0",
4 | "packages": [
5 | "packages/*"
6 | ],
7 | "npmClient": "pnpm",
8 | "changelogPreset": "conventional-changelog-conventionalcommits",
9 | "command": {
10 | "publish": {
11 | "removePackageFields": [
12 | "devDependencies",
13 | "prettier"
14 | ],
15 | "yes": true
16 | },
17 | "version": {
18 | "changelogIncludeCommitsClientLogin": " – by @%l",
19 | "conventionalCommits": true,
20 | "createRelease": "github",
21 | "syncWorkspaceLock": true
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/media/assets.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/media/assets.sketch
--------------------------------------------------------------------------------
/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/media/logo.png
--------------------------------------------------------------------------------
/media/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/media/og-image.png
--------------------------------------------------------------------------------
/media/twitter-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amannn/next-intl/9c837d654703507a510278a616490db4bdd0469b/media/twitter-image.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "scripts": {
5 | "postinstall": "turbo run build --filter './packages/**'",
6 | "publish": "lerna publish"
7 | },
8 | "devDependencies": {
9 | "@lerna-lite/cli": "3.9.0",
10 | "@lerna-lite/publish": "3.9.0",
11 | "conventional-changelog-conventionalcommits": "7.0.2",
12 | "turbo": "^2.4.4"
13 | },
14 | "pnpm": {
15 | "overrides": {
16 | "@babel/parser": "7.21.9"
17 | }
18 | },
19 | "packageManager": "pnpm@9.11.0"
20 | }
21 |
--------------------------------------------------------------------------------
/packages/next-intl/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/packages/next-intl/config.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | import config from './dist/types/config';
3 |
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/next-intl/middleware.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | import createMiddleware from './dist/types/middleware';
3 |
4 | export default createMiddleware;
5 |
--------------------------------------------------------------------------------
/packages/next-intl/navigation.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | export * from './dist/types/navigation.react-client';
3 |
--------------------------------------------------------------------------------
/packages/next-intl/next-env.d.ts:
--------------------------------------------------------------------------------
1 | // Normally Next.js creates this file when the build runs. Seems like from
2 | // Next.js 13.2 on this is mandatory to be able to import `usePathname`.
3 |
4 | ///
5 | ///
6 | ///
7 |
--------------------------------------------------------------------------------
/packages/next-intl/plugin.d.cts:
--------------------------------------------------------------------------------
1 | import createNextIntlPlugin from './dist/types/plugin.ts';
2 |
3 | export = createNextIntlPlugin;
4 |
--------------------------------------------------------------------------------
/packages/next-intl/plugin.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | import plugin from './dist/types/plugin';
3 |
4 | export default plugin;
5 |
--------------------------------------------------------------------------------
/packages/next-intl/routing.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | export * from './dist/types/routing';
3 |
--------------------------------------------------------------------------------
/packages/next-intl/server.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | export * from './dist/types/server/react-server';
3 |
--------------------------------------------------------------------------------
/packages/next-intl/src/config.tsx:
--------------------------------------------------------------------------------
1 | export default function getConfig() {
2 | throw new Error(
3 | "Couldn't find next-intl config file. Please follow the instructions at https://next-intl.dev/docs/getting-started/app-router"
4 | );
5 | }
6 |
--------------------------------------------------------------------------------
/packages/next-intl/src/index.react-client.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the default entry file when consumers import from
3 | * 'next-intl'. We use the client APIs in this case.
4 | *
5 | * Note that the `react-server` environment (i.e. RSC) imports
6 | * from `./react-server` instead.
7 | */
8 |
9 | export * from './react-client/index.js';
10 |
--------------------------------------------------------------------------------
/packages/next-intl/src/index.react-server.tsx:
--------------------------------------------------------------------------------
1 | export * from './react-server/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/middleware.tsx:
--------------------------------------------------------------------------------
1 | export {default} from './middleware/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/middleware/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * The middleware, available as `next-intl/middleware`.
3 | */
4 |
5 | export {default} from './middleware.js';
6 |
--------------------------------------------------------------------------------
/packages/next-intl/src/navigation.react-client.tsx:
--------------------------------------------------------------------------------
1 | export * from './navigation/react-client/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/navigation.react-server.tsx:
--------------------------------------------------------------------------------
1 | export * from './navigation/react-server/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/navigation/react-client/index.tsx:
--------------------------------------------------------------------------------
1 | export {default as createNavigation} from './createNavigation.js';
2 | export type {QueryParams} from '../shared/utils.js';
3 |
--------------------------------------------------------------------------------
/packages/next-intl/src/navigation/react-server/getServerLocale.tsx:
--------------------------------------------------------------------------------
1 | import getConfig from '../../server/react-server/getConfig.js';
2 |
3 | /**
4 | * This is only moved to a separate module for easier mocking in
5 | * `../createNavigatoin.test.tsx` in order to avoid suspending.
6 | */
7 | export default async function getServerLocale() {
8 | const config = await getConfig();
9 | return config.locale;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/next-intl/src/navigation/react-server/index.tsx:
--------------------------------------------------------------------------------
1 | export {default as createNavigation} from './createNavigation.js';
2 | export type {Pathnames} from '../../routing/types.js';
3 |
--------------------------------------------------------------------------------
/packages/next-intl/src/plugin.tsx:
--------------------------------------------------------------------------------
1 | export {default} from './plugin/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/plugin/hasStableTurboConfig.tsx:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/order
2 | import {createRequire} from 'module';
3 |
4 | const require = createRequire(import.meta.url);
5 | const pkg = require('next/package.json');
6 |
7 | function compareVersions(version1: string, version2: string) {
8 | const v1Parts = version1.split('.').map(Number);
9 | const v2Parts = version2.split('.').map(Number);
10 |
11 | for (let i = 0; i < 3; i++) {
12 | const v1 = v1Parts[i] || 0;
13 | const v2 = v2Parts[i] || 0;
14 | if (v1 > v2) return 1;
15 | if (v1 < v2) return -1;
16 | }
17 | return 0;
18 | }
19 |
20 | const hasStableTurboConfig = compareVersions(pkg.version, '15.3.0') >= 0;
21 | export default hasStableTurboConfig;
22 |
--------------------------------------------------------------------------------
/packages/next-intl/src/plugin/index.tsx:
--------------------------------------------------------------------------------
1 | export {default} from './createNextIntlPlugin.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/plugin/types.tsx:
--------------------------------------------------------------------------------
1 | export type PluginConfig = {
2 | requestConfig?: string;
3 | experimental?: {
4 | /** A path to the messages file that you'd like to create a declaration for. In case you want to consider multiple files, you can pass an array of paths. */
5 | createMessagesDeclaration?: string | Array;
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/packages/next-intl/src/plugin/utils.tsx:
--------------------------------------------------------------------------------
1 | function formatMessage(message: string) {
2 | return `\n[next-intl] ${message}\n`;
3 | }
4 |
5 | export function throwError(message: string): never {
6 | throw new Error(formatMessage(message));
7 | }
8 |
9 | export function warn(message: string) {
10 | console.warn(formatMessage(message));
11 | }
12 |
--------------------------------------------------------------------------------
/packages/next-intl/src/plugin/watchFile.tsx:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 |
4 | /**
5 | * Wrapper around `fs.watch` that provides a workaround
6 | * for https://github.com/nodejs/node/issues/5039.
7 | */
8 | export default function watchFile(filepath: string, callback: () => void) {
9 | const directory = path.dirname(filepath);
10 | const filename = path.basename(filepath);
11 |
12 | return fs.watch(
13 | directory,
14 | {persistent: false, recursive: false},
15 | (event, changedFilename) => {
16 | if (changedFilename === filename) {
17 | callback();
18 | }
19 | }
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-client/useFormatter.test.tsx:
--------------------------------------------------------------------------------
1 | import {render, screen} from '@testing-library/react';
2 | import {expect, it} from 'vitest';
3 | import {NextIntlClientProvider, useFormatter} from './index.js';
4 |
5 | function Component() {
6 | const format = useFormatter();
7 | return <>{format.number(1)}>;
8 | }
9 |
10 | it('provides a helpful error message when no provider is found', () => {
11 | expect(() => render()).toThrow(
12 | /Failed to call `useFormatter` because the context from `NextIntlClientProvider` was not found\./
13 | );
14 | });
15 |
16 | it('works with a provider', () => {
17 | render(
18 |
19 |
20 |
21 | );
22 | screen.getByText('1');
23 | });
24 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-client/useNow.test.tsx:
--------------------------------------------------------------------------------
1 | import {render, screen} from '@testing-library/react';
2 | import {it} from 'vitest';
3 | import {NextIntlClientProvider, useNow} from './index.js';
4 |
5 | function Component() {
6 | const now = useNow();
7 | return <>{now.toISOString()}>;
8 | }
9 |
10 | it('works without a provider', () => {
11 | render(
12 |
13 |
14 |
15 | );
16 | });
17 |
18 | it('works with a provider', () => {
19 | render(
20 |
24 |
25 |
26 | );
27 | screen.getByText('2021-01-01T00:00:00.000Z');
28 | });
29 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-client/useTimeZone.test.tsx:
--------------------------------------------------------------------------------
1 | import {render, screen} from '@testing-library/react';
2 | import {it} from 'vitest';
3 | import {NextIntlClientProvider, useTimeZone} from './index.js';
4 |
5 | function Component() {
6 | const timeZone = useTimeZone();
7 | return <>{timeZone}>;
8 | }
9 |
10 | it('works without a provider', () => {
11 | render(
12 |
13 |
14 |
15 | );
16 | });
17 |
18 | it('works with a provider', () => {
19 | render(
20 |
21 |
22 |
23 | );
24 | screen.getByText('America/New_York');
25 | });
26 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-server/useFormatter.tsx:
--------------------------------------------------------------------------------
1 | import type {useFormatter as useFormatterType} from 'use-intl';
2 | import getServerFormatter from '../server/react-server/getServerFormatter.js';
3 | import useConfig from './useConfig.js';
4 |
5 | export default function useFormatter(): ReturnType {
6 | const config = useConfig('useFormatter');
7 | return getServerFormatter(config);
8 | }
9 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-server/useLocale.tsx:
--------------------------------------------------------------------------------
1 | import type {useLocale as useLocaleType} from 'use-intl';
2 | import useConfig from './useConfig.js';
3 |
4 | export default function useLocale(): ReturnType {
5 | const config = useConfig('useLocale');
6 | return config.locale;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-server/useMessages.tsx:
--------------------------------------------------------------------------------
1 | import type {useMessages as useMessagesType} from 'use-intl';
2 | import {getMessagesFromConfig} from '../server/react-server/getMessages.js';
3 | import useConfig from './useConfig.js';
4 |
5 | export default function useMessages(): ReturnType {
6 | const config = useConfig('useMessages');
7 | return getMessagesFromConfig(config);
8 | }
9 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-server/useNow.tsx:
--------------------------------------------------------------------------------
1 | import type {useNow as useNowType} from 'use-intl';
2 | import getDefaultNow from '../server/react-server/getDefaultNow.js';
3 | import useConfig from './useConfig.js';
4 |
5 | export default function useNow(
6 | options?: Parameters[0]
7 | ): ReturnType {
8 | if (options?.updateInterval != null) {
9 | console.error(
10 | "`useNow` doesn't support the `updateInterval` option in Server Components, the value will be ignored. If you need the value to update, you can convert the component to a Client Component."
11 | );
12 | }
13 |
14 | const config = useConfig('useNow');
15 | return config.now ?? getDefaultNow();
16 | }
17 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-server/useTimeZone.tsx:
--------------------------------------------------------------------------------
1 | import type {useTimeZone as useTimeZoneType} from 'use-intl';
2 | import useConfig from './useConfig.js';
3 |
4 | export default function useTimeZone(): ReturnType {
5 | const config = useConfig('useTimeZone');
6 | return config.timeZone;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/next-intl/src/react-server/useTranslations.tsx:
--------------------------------------------------------------------------------
1 | import type {useTranslations as useTranslationsType} from 'use-intl';
2 | import getServerTranslator from '../server/react-server/getServerTranslator.js';
3 | import useConfig from './useConfig.js';
4 |
5 | export default function useTranslations(
6 | ...[namespace]: Parameters
7 | ): ReturnType {
8 | const config = useConfig('useTranslations');
9 | return getServerTranslator(config, namespace);
10 | }
11 |
--------------------------------------------------------------------------------
/packages/next-intl/src/routing.tsx:
--------------------------------------------------------------------------------
1 | export * from './routing/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/routing/index.tsx:
--------------------------------------------------------------------------------
1 | export type {
2 | Pathnames,
3 | LocalePrefix,
4 | DomainsConfig,
5 | LocalePrefixMode
6 | } from './types.js';
7 | export {default as defineRouting} from './defineRouting.js';
8 | export type {RoutingConfig} from './config.js';
9 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server.react-client.tsx:
--------------------------------------------------------------------------------
1 | export * from './server/react-client/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server.react-server.tsx:
--------------------------------------------------------------------------------
1 | export * from './server/react-server/index.js';
2 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/RequestLocaleCache.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 | import type {Locale} from 'use-intl';
3 |
4 | // See https://github.com/vercel/next.js/discussions/58862
5 | function getCacheImpl() {
6 | const value: {locale?: Locale} = {locale: undefined};
7 | return value;
8 | }
9 |
10 | const getCache = cache(getCacheImpl);
11 |
12 | export function getCachedRequestLocale() {
13 | return getCache().locale;
14 | }
15 |
16 | export function setCachedRequestLocale(locale: Locale) {
17 | getCache().locale = locale;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/createRequestConfig.tsx:
--------------------------------------------------------------------------------
1 | import getRuntimeConfig from 'next-intl/config';
2 | import type {
3 | GetRequestConfigParams,
4 | RequestConfig
5 | } from './getRequestConfig.js';
6 |
7 | export default getRuntimeConfig as unknown as (
8 | params: GetRequestConfigParams
9 | ) => RequestConfig | Promise;
10 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getConfigNow.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 | import type {Locale} from 'use-intl';
3 | import getConfig from './getConfig.js';
4 |
5 | async function getConfigNowImpl(locale?: Locale) {
6 | const config = await getConfig(locale);
7 | return config.now;
8 | }
9 | const getConfigNow = cache(getConfigNowImpl);
10 |
11 | export default getConfigNow;
12 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getDefaultNow.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 |
3 | function defaultNow() {
4 | // See https://next-intl.dev/docs/usage/dates-times#relative-times-server
5 | return new Date();
6 | }
7 |
8 | const getDefaultNow = cache(defaultNow);
9 |
10 | export default getDefaultNow;
11 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getFormats.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 | import getConfig from './getConfig.js';
3 |
4 | async function getFormatsCachedImpl() {
5 | const config = await getConfig();
6 | return config.formats;
7 | }
8 | const getFormats = cache(getFormatsCachedImpl);
9 |
10 | export default getFormats;
11 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getLocale.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 | import type {Locale} from 'use-intl';
3 | import getConfig from './getConfig.js';
4 |
5 | async function getLocaleCachedImpl(): Promise {
6 | const config = await getConfig();
7 | return config.locale;
8 | }
9 | const getLocaleCached = cache(getLocaleCachedImpl);
10 |
11 | export default getLocaleCached;
12 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getNow.tsx:
--------------------------------------------------------------------------------
1 | import type {Locale} from 'use-intl';
2 | import getConfigNow from './getConfigNow.js';
3 | import getDefaultNow from './getDefaultNow.js';
4 |
5 | export default async function getNow(opts?: {locale?: Locale}): Promise {
6 | return (await getConfigNow(opts?.locale)) ?? getDefaultNow();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getServerFormatter.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 | import {createFormatter} from 'use-intl/core';
3 | import getDefaultNow from './getDefaultNow.js';
4 |
5 | function getFormatterCachedImpl(config: Parameters[0]) {
6 | return createFormatter({
7 | ...config,
8 | // Only init when necessary to avoid triggering a `dynamicIO` error
9 | // unnecessarily (`now` is only needed for `format.relativeTime`)
10 | get now() {
11 | return config.now ?? getDefaultNow();
12 | }
13 | });
14 | }
15 | const getFormatterCached = cache(getFormatterCachedImpl);
16 |
17 | export default getFormatterCached;
18 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getServerTranslator.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 | import {
3 | type Messages,
4 | type NamespaceKeys,
5 | type NestedKeyOf,
6 | createTranslator
7 | } from 'use-intl/core';
8 |
9 | function getServerTranslatorImpl<
10 | NestedKey extends NamespaceKeys> = never
11 | >(
12 | config: Parameters[0],
13 | namespace?: NestedKey
14 | ): ReturnType> {
15 | return createTranslator({
16 | ...config,
17 | namespace
18 | });
19 | }
20 |
21 | export default cache(getServerTranslatorImpl);
22 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/getTimeZone.tsx:
--------------------------------------------------------------------------------
1 | import {cache} from 'react';
2 | import type {Locale} from 'use-intl';
3 | import getConfig from './getConfig.js';
4 |
5 | async function getTimeZoneCachedImpl(locale?: Locale) {
6 | const config = await getConfig(locale);
7 | return config.timeZone;
8 | }
9 | const getTimeZoneCached = cache(getTimeZoneCachedImpl);
10 |
11 | export default async function getTimeZone(opts?: {
12 | locale?: Locale;
13 | }): Promise {
14 | return getTimeZoneCached(opts?.locale);
15 | }
16 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Server-only APIs available via `next-intl/server`.
3 | */
4 |
5 | export {
6 | default as getRequestConfig,
7 | type GetRequestConfigParams,
8 | type RequestConfig
9 | } from './getRequestConfig.js';
10 | export {default as getFormatter} from './getFormatter.js';
11 | export {default as getNow} from './getNow.js';
12 | export {default as getTimeZone} from './getTimeZone.js';
13 | export {default as getTranslations} from './getTranslations.js';
14 | export {default as getMessages} from './getMessages.js';
15 | export {default as getLocale} from './getLocale.js';
16 |
17 | export {setCachedRequestLocale as setRequestLocale} from './RequestLocaleCache.js';
18 |
--------------------------------------------------------------------------------
/packages/next-intl/src/server/react-server/validateLocale.tsx:
--------------------------------------------------------------------------------
1 | export default function validateLocale(locale: string) {
2 | try {
3 | const constructed = new Intl.Locale(locale);
4 | if (!constructed.language) {
5 | throw new Error('Language is required');
6 | }
7 | } catch {
8 | console.error(
9 | `An invalid locale was provided: "${locale}"\nPlease ensure you're using a valid Unicode locale identifier (e.g. "en-US").`
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/next-intl/src/shared/constants.tsx:
--------------------------------------------------------------------------------
1 | // Used to read the locale from the middleware
2 | export const HEADER_LOCALE_NAME = 'X-NEXT-INTL-LOCALE';
3 |
--------------------------------------------------------------------------------
/packages/next-intl/src/shared/types.tsx:
--------------------------------------------------------------------------------
1 | export type ParametersExceptFirst = Fn extends (
2 | arg0: any,
3 | ...rest: infer R
4 | ) => any
5 | ? R
6 | : never;
7 |
8 | export type ParametersExceptFirstTwo = Fn extends (
9 | arg0: any,
10 | arg1: any,
11 | ...rest: infer R
12 | ) => any
13 | ? R
14 | : never;
15 |
16 | // https://www.totaltypescript.com/concepts/the-prettify-helper
17 | export type Prettify = {
18 | [Key in keyof Type]: Type[Key];
19 | } & {};
20 |
--------------------------------------------------------------------------------
/packages/next-intl/src/shared/use.tsx:
--------------------------------------------------------------------------------
1 | import * as react from 'react';
2 |
3 | // @ts-expect-error -- Ooof, Next.js doesn't make this easy.
4 | // `use` is only available in React 19 canary, but we can
5 | // use it in Next.js already as Next.js "vendors" a fixed
6 | // version of React. However, if we'd simply put `use` in
7 | // ESM code, then the build doesn't work since React does
8 | // not export `use` officially. Therefore, we have to use
9 | // something that is not statically analyzable. Once React
10 | // 19 is out, we can remove this in the next major version.
11 | export default react['use'.trim()] as typeof react.use;
12 |
--------------------------------------------------------------------------------
/packages/next-intl/test/setup.tsx:
--------------------------------------------------------------------------------
1 | import {cleanup} from '@testing-library/react';
2 | import {afterEach} from 'vitest';
3 |
4 | afterEach(cleanup);
5 |
--------------------------------------------------------------------------------
/packages/next-intl/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["src/**/*.test.tsx", "test", "__mocks__"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "noEmit": false,
7 | "outDir": "dist/types",
8 | "emitDeclarationOnly": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/next-intl/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-molindo/tsconfig.json",
3 | "include": ["src", "test", "__mocks__", "types", "next-env.d.ts"],
4 | "compilerOptions": {
5 | "lib": ["dom", "esnext"],
6 | "target": "ES2020",
7 | "declaration": true,
8 | "rootDir": ".",
9 | "module": "NodeNext",
10 | "moduleResolution": "NodeNext",
11 | "jsx": "react-jsx",
12 | "skipLibCheck": true,
13 | "noEmit": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/next-intl/vitest.config.mts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: 'jsdom',
6 | setupFiles: './test/setup.tsx'
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/packages/use-intl/.size-limit.ts:
--------------------------------------------------------------------------------
1 | import type {SizeLimitConfig} from 'size-limit';
2 |
3 | const config: SizeLimitConfig = [
4 | {
5 | name: "import * from 'use-intl' (production)",
6 | import: '*',
7 | path: 'dist/esm/production/index.js',
8 | limit: '13.015 kB'
9 | },
10 | {
11 | name: "import {IntlProvider, useLocale, useNow, useTimeZone, useMessages, useFormatter} from 'use-intl' (production)",
12 | path: 'dist/esm/production/index.js',
13 | import:
14 | '{IntlProvider, useLocale, useNow, useTimeZone, useMessages, useFormatter}',
15 | limit: '2.025 kB'
16 | }
17 | ];
18 |
19 | module.exports = config;
20 |
--------------------------------------------------------------------------------
/packages/use-intl/core.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | export * from './dist/types/core.d.ts';
3 |
--------------------------------------------------------------------------------
/packages/use-intl/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {getPresets} from 'eslint-config-molindo';
2 | import reactCompilerPlugin from 'eslint-plugin-react-compiler';
3 |
4 | export default (await getPresets('typescript', 'react', 'vitest')).concat({
5 | plugins: {
6 | 'react-compiler': reactCompilerPlugin
7 | },
8 | rules: {
9 | 'react-compiler/react-compiler': 'error',
10 |
11 | // Strict type imports to avoid side effects
12 | '@typescript-eslint/consistent-type-imports': 'error',
13 | '@typescript-eslint/consistent-type-exports': 'error',
14 | '@typescript-eslint/no-import-type-side-effects': 'error',
15 | 'import/no-duplicates': ['error', {'prefer-inline': true}],
16 | 'import/extensions': 'error'
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/packages/use-intl/react.d.ts:
--------------------------------------------------------------------------------
1 | // Needed for projects with `moduleResolution: 'node'`
2 | export * from './dist/types/react.d.ts';
3 |
--------------------------------------------------------------------------------
/packages/use-intl/rollup.config.js:
--------------------------------------------------------------------------------
1 | import {getBuildConfig} from 'tools';
2 | import pkg from './package.json' with {type: 'json'};
3 |
4 | export default getBuildConfig({
5 | input: {
6 | index: 'src/index.tsx',
7 | core: 'src/core.tsx',
8 | react: 'src/react.tsx'
9 | },
10 | external: [
11 | ...Object.keys(pkg.dependencies),
12 | ...Object.keys(pkg.peerDependencies),
13 | 'react/jsx-runtime'
14 | ]
15 | });
16 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core.tsx:
--------------------------------------------------------------------------------
1 | export * from './core/index.js';
2 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/AbstractIntlMessages.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * A generic type that describes the shape of messages.
3 | *
4 | * Optionally, messages can be strictly-typed in order to get type safety for message
5 | * namespaces and keys. See https://next-intl.dev/docs/usage/typescript
6 | */
7 | type AbstractIntlMessages = {
8 | [id: string]: AbstractIntlMessages | string;
9 | };
10 |
11 | export default AbstractIntlMessages;
12 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/Formats.tsx:
--------------------------------------------------------------------------------
1 | import type DateTimeFormatOptions from './DateTimeFormatOptions.js';
2 | import type NumberFormatOptions from './NumberFormatOptions.js';
3 |
4 | type Formats = {
5 | number?: Record;
6 | dateTime?: Record;
7 | list?: Record;
8 | };
9 |
10 | export default Formats;
11 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/ICUArgs.tsx:
--------------------------------------------------------------------------------
1 | // schummar is the best, he published his ICU type parser for next-intl:
2 | // https://github.com/schummar/schummar-translate/issues/28
3 | import type {GetICUArgs, GetICUArgsOptions} from '@schummar/icu-type-parser';
4 |
5 | type ICUArgs =
6 | // This is important when `t` is returned from a function and there's no
7 | // known `Message` yet. Otherwise, we'd run into an infinite loop.
8 | string extends Message ? {} : GetICUArgs;
9 |
10 | export default ICUArgs;
11 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/ICUTags.tsx:
--------------------------------------------------------------------------------
1 | type ICUTags<
2 | MessageString extends string,
3 | TagsFn
4 | > = MessageString extends `${infer Prefix}<${infer TagName}>${infer Content}${string}>${infer Tail}`
5 | ? Record & ICUTags<`${Prefix}${Content}${Tail}`, TagsFn>
6 | : {};
7 |
8 | export default ICUTags;
9 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/IntlError.tsx:
--------------------------------------------------------------------------------
1 | import type IntlErrorCode from './IntlErrorCode.js';
2 |
3 | export default class IntlError extends Error {
4 | public readonly code: IntlErrorCode;
5 | public readonly originalMessage: string | undefined;
6 |
7 | constructor(code: IntlErrorCode, originalMessage?: string) {
8 | let message: string = code;
9 | if (originalMessage) {
10 | message += ': ' + originalMessage;
11 | }
12 | super(message);
13 |
14 | this.code = code;
15 | if (originalMessage) {
16 | this.originalMessage = originalMessage;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/IntlErrorCode.tsx:
--------------------------------------------------------------------------------
1 | enum IntlErrorCode {
2 | MISSING_MESSAGE = 'MISSING_MESSAGE',
3 | MISSING_FORMAT = 'MISSING_FORMAT',
4 | ENVIRONMENT_FALLBACK = 'ENVIRONMENT_FALLBACK',
5 | INSUFFICIENT_PATH = 'INSUFFICIENT_PATH',
6 | INVALID_MESSAGE = 'INVALID_MESSAGE',
7 | INVALID_KEY = 'INVALID_KEY',
8 | FORMATTING_ERROR = 'FORMATTING_ERROR'
9 | }
10 |
11 | export default IntlErrorCode;
12 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/NumberFormatOptions.tsx:
--------------------------------------------------------------------------------
1 | import type {Formats} from 'intl-messageformat';
2 |
3 | // Use the already bundled version of `NumberFormat` from `@formatjs/ecma402-abstract`
4 | // that comes with `intl-messageformat`
5 | type NumberFormatOptions = Formats['number'][string];
6 |
7 | export default NumberFormatOptions;
8 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/RelativeTimeFormatOptions.tsx:
--------------------------------------------------------------------------------
1 | type RelativeTimeFormatOptions = {
2 | now?: number | Date;
3 | unit?: Intl.RelativeTimeFormatUnit;
4 | numberingSystem?: string;
5 | style?: Intl.RelativeTimeFormatStyle;
6 | // We don't support the `numeric` property by design (see https://github.com/amannn/next-intl/pull/765)
7 | };
8 |
9 | export default RelativeTimeFormatOptions;
10 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/defaults.tsx:
--------------------------------------------------------------------------------
1 | import type IntlError from './IntlError.js';
2 | import joinPath from './joinPath.js';
3 |
4 | /**
5 | * Contains defaults that are used for all entry points into the core.
6 | * See also `InitializedIntlConfiguration`.
7 | */
8 |
9 | export function defaultGetMessageFallback(props: {
10 | error: IntlError;
11 | key: string;
12 | namespace?: string;
13 | }) {
14 | return joinPath(props.namespace, props.key);
15 | }
16 |
17 | export function defaultOnError(error: IntlError) {
18 | console.error(error);
19 | }
20 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/hasLocale.tsx:
--------------------------------------------------------------------------------
1 | import type {Locale} from './AppConfig.js';
2 |
3 | /**
4 | * Checks if a locale exists in a list of locales.
5 | *
6 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale
7 | */
8 | export default function hasLocale(
9 | locales: ReadonlyArray,
10 | candidate: unknown
11 | ): candidate is LocaleType {
12 | return locales.includes(candidate as LocaleType);
13 | }
14 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/joinPath.tsx:
--------------------------------------------------------------------------------
1 | export default function joinPath(...parts: Array) {
2 | return parts.filter(Boolean).join('.');
3 | }
4 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/resolveNamespace.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * For the strictly typed messages to work we have to wrap the namespace into
3 | * a mandatory prefix. See https://stackoverflow.com/a/71529575/343045
4 | */
5 | export default function resolveNamespace(
6 | namespace: string,
7 | namespacePrefix: string
8 | ) {
9 | return namespace === namespacePrefix
10 | ? undefined
11 | : namespace.slice((namespacePrefix + '.').length);
12 | }
13 |
--------------------------------------------------------------------------------
/packages/use-intl/src/core/types.tsx:
--------------------------------------------------------------------------------
1 | // https://www.totaltypescript.com/concepts/the-prettify-helper
2 | export type Prettify = {
3 | [K in keyof T]: T[K];
4 | } & {};
5 |
6 | export type DeepPartial = {
7 | [Key in keyof Type]?: Type[Key] extends object
8 | ? DeepPartial
9 | : Type[Key];
10 | };
11 |
--------------------------------------------------------------------------------
/packages/use-intl/src/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './core.js';
2 | export * from './react.js';
3 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react.tsx:
--------------------------------------------------------------------------------
1 | export * from './react/index.js';
2 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/IntlContext.tsx:
--------------------------------------------------------------------------------
1 | import {createContext} from 'react';
2 | import type {InitializedIntlConfig} from '../core/IntlConfig.js';
3 | import type {Formatters, IntlCache} from '../core/formatters.js';
4 |
5 | export type IntlContextValue = InitializedIntlConfig & {
6 | formatters: Formatters;
7 | cache: IntlCache;
8 | };
9 |
10 | const IntlContext = createContext(undefined);
11 |
12 | export default IntlContext;
13 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/index.tsx:
--------------------------------------------------------------------------------
1 | export {default as IntlProvider} from './IntlProvider.js';
2 | export {default as useTranslations} from './useTranslations.js';
3 | export {default as useLocale} from './useLocale.js';
4 | export {default as useNow} from './useNow.js';
5 | export {default as useTimeZone} from './useTimeZone.js';
6 | export {default as useMessages} from './useMessages.js';
7 | export {default as useFormatter} from './useFormatter.js';
8 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/useFormatter.tsx:
--------------------------------------------------------------------------------
1 | import {useMemo} from 'react';
2 | import createFormatter from '../core/createFormatter.js';
3 | import useIntlContext from './useIntlContext.js';
4 |
5 | export default function useFormatter(): ReturnType {
6 | const {
7 | formats,
8 | formatters,
9 | locale,
10 | now: globalNow,
11 | onError,
12 | timeZone
13 | } = useIntlContext();
14 |
15 | return useMemo(
16 | () =>
17 | createFormatter({
18 | formats,
19 | locale,
20 | now: globalNow,
21 | onError,
22 | timeZone,
23 | _formatters: formatters
24 | }),
25 | [formats, formatters, globalNow, locale, onError, timeZone]
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/useIntlContext.tsx:
--------------------------------------------------------------------------------
1 | import {useContext} from 'react';
2 | import IntlContext, {type IntlContextValue} from './IntlContext.js';
3 |
4 | export default function useIntlContext(): IntlContextValue {
5 | const context = useContext(IntlContext);
6 |
7 | if (!context) {
8 | throw new Error(
9 | process.env.NODE_ENV !== 'production'
10 | ? 'No intl context found. Have you configured the provider? See https://next-intl.dev/docs/usage/configuration#server-client-components'
11 | : undefined
12 | );
13 | }
14 |
15 | return context;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/useLocale.test.tsx:
--------------------------------------------------------------------------------
1 | import {render, screen} from '@testing-library/react';
2 | import {it} from 'vitest';
3 | import IntlProvider from './IntlProvider.js';
4 | import useLocale from './useLocale.js';
5 |
6 | it('returns the current locale', () => {
7 | function Component() {
8 | return <>{useLocale()}>;
9 | }
10 |
11 | render(
12 |
13 |
14 |
15 | );
16 |
17 | screen.getByText('en');
18 | });
19 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/useLocale.tsx:
--------------------------------------------------------------------------------
1 | import type {Locale} from '../core.js';
2 | import useIntlContext from './useIntlContext.js';
3 |
4 | export default function useLocale(): Locale {
5 | return useIntlContext().locale;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/useMessages.tsx:
--------------------------------------------------------------------------------
1 | import type {Messages} from '../core/AppConfig.js';
2 | import useIntlContext from './useIntlContext.js';
3 |
4 | export default function useMessages(): Messages {
5 | const context = useIntlContext();
6 |
7 | if (!context.messages) {
8 | throw new Error(
9 | process.env.NODE_ENV !== 'production'
10 | ? 'No messages found. Have you configured them correctly? See https://next-intl.dev/docs/configuration#messages'
11 | : undefined
12 | );
13 | }
14 |
15 | return context.messages;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/use-intl/src/react/useTimeZone.tsx:
--------------------------------------------------------------------------------
1 | import useIntlContext from './useIntlContext.js';
2 |
3 | export default function useTimeZone() {
4 | return useIntlContext().timeZone;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/use-intl/test/setup.tsx:
--------------------------------------------------------------------------------
1 | import {cleanup} from '@testing-library/react';
2 | import {afterEach} from 'vitest';
3 |
4 | afterEach(cleanup);
5 |
--------------------------------------------------------------------------------
/packages/use-intl/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["src/**/*.test.tsx", "test"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "noEmit": false,
7 | "outDir": "dist/types",
8 | "emitDeclarationOnly": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/use-intl/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-molindo/tsconfig.json",
3 | "include": ["src", "test", "types"],
4 | "compilerOptions": {
5 | "lib": ["dom", "esnext"],
6 | "target": "ES2020",
7 | "declaration": true,
8 | "rootDir": ".",
9 | "module": "NodeNext",
10 | "moduleResolution": "NodeNext",
11 | "jsx": "react-jsx",
12 | "skipLibCheck": true,
13 | "noEmit": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/use-intl/vitest.config.mts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: 'jsdom',
6 | setupFiles: './test/setup.tsx'
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
3 | - 'examples/*'
4 | - 'docs'
5 | - 'tools'
6 |
--------------------------------------------------------------------------------
/tools/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import {getPresets} from 'eslint-config-molindo';
2 | import globals from 'globals';
3 |
4 | export default (await getPresets('javascript')).concat({
5 | languageOptions: {
6 | globals: globals.node
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/tools/src/index.js:
--------------------------------------------------------------------------------
1 | export {default as getBuildConfig} from './getBuildConfig.js';
2 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "outputs": ["dist/**", ".next/**", "!.next/cache/**", "build/**"]
7 | },
8 | "lint": {
9 | "dependsOn": ["^build"]
10 | },
11 | "example-app-router-playground#lint": {
12 | "dependsOn": ["example-app-router-playground#build"]
13 | },
14 | "test": {
15 | "dependsOn": ["build"]
16 | },
17 | "size": {
18 | "dependsOn": ["build"]
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------