diff --git a/docs/src/pages/docs/environments/error-files.mdx b/docs/src/pages/docs/environments/error-files.mdx index 90da28c7c..d3f7566af 100644 --- a/docs/src/pages/docs/environments/error-files.mdx +++ b/docs/src/pages/docs/environments/error-files.mdx @@ -80,11 +80,12 @@ export default function RootLayout({children}) { For the 404 page to render, we need to call the `notFound` function in the root layout when we detect an incoming `locale` param that isn't a valid locale. ```tsx filename="app/[locale]/layout.tsx" +import {isValidLocale} from 'next-intl'; import {notFound} from 'next/navigation'; +import {routing} from '@/i18n/routing'; export default function LocaleLayout({children, params: {locale}}) { - // Ensure that the incoming `locale` is valid - if (!routing.locales.includes(locale as any)) { + if (!isValidLocale(routing.locales, locale)) { notFound(); } diff --git a/docs/src/pages/docs/getting-started/app-router/with-i18n-routing.mdx b/docs/src/pages/docs/getting-started/app-router/with-i18n-routing.mdx index e0d6ae9d1..f2f985ffa 100644 --- a/docs/src/pages/docs/getting-started/app-router/with-i18n-routing.mdx +++ b/docs/src/pages/docs/getting-started/app-router/with-i18n-routing.mdx @@ -295,12 +295,12 @@ export function generateStaticParams() { ```tsx filename="app/[locale]/layout.tsx" import {setRequestLocale} from 'next-intl/server'; +import {isValidLocale} from 'next-intl'; import {notFound} from 'next/navigation'; import {routing} from '@/i18n/routing'; export default async function LocaleLayout({children, params: {locale}}) { - // Ensure that the incoming `locale` is valid - if (!routing.locales.includes(locale as any)) { + if (!isValidLocale(routing.locales, locale)) { notFound(); } diff --git a/examples/example-app-router-migration/src/app/[locale]/layout.tsx b/examples/example-app-router-migration/src/app/[locale]/layout.tsx index 6864db0de..4e1c9cc72 100644 --- a/examples/example-app-router-migration/src/app/[locale]/layout.tsx +++ b/examples/example-app-router-migration/src/app/[locale]/layout.tsx @@ -1,5 +1,5 @@ import {notFound} from 'next/navigation'; -import {NextIntlClientProvider} from 'next-intl'; +import {NextIntlClientProvider, isValidLocale} from 'next-intl'; import {getMessages} from 'next-intl/server'; import {ReactNode} from 'react'; import {routing} from '@/i18n/routing'; @@ -10,8 +10,7 @@ type Props = { }; export default async function LocaleLayout({children, params}: Props) { - // Ensure that the incoming `locale` is valid - if (!routing.locales.includes(params.locale as any)) { + if (!isValidLocale(routing.locales, params.locale)) { notFound(); } diff --git a/examples/example-app-router-migration/src/i18n/request.ts b/examples/example-app-router-migration/src/i18n/request.ts index 70066e964..0534cbacd 100644 --- a/examples/example-app-router-migration/src/i18n/request.ts +++ b/examples/example-app-router-migration/src/i18n/request.ts @@ -1,14 +1,13 @@ +import {isValidLocale} from 'next-intl'; import {getRequestConfig} from 'next-intl/server'; import {routing} from './routing'; export default getRequestConfig(async ({requestLocale}) => { - // This typically corresponds to the `[locale]` segment - let locale = await requestLocale; - - // Ensure that the incoming locale is valid - if (!locale || !routing.locales.includes(locale as any)) { - locale = routing.defaultLocale; - } + // Typically corresponds to the `[locale]` segment + const requested = await requestLocale; + const locale = isValidLocale(routing.locales, requested) + ? requested + : routing.defaultLocale; return { locale, diff --git a/examples/example-app-router-mixed-routing/global.d.ts b/examples/example-app-router-mixed-routing/global.d.ts index b749518b9..dd933f191 100644 --- a/examples/example-app-router-mixed-routing/global.d.ts +++ b/examples/example-app-router-mixed-routing/global.d.ts @@ -1,8 +1,10 @@ +import 'next-intl'; +import {locales} from '@/config'; import en from './messages/en.json'; -type Messages = typeof en; - -declare global { - // Use type safe message keys with `next-intl` - interface IntlMessages extends Messages {} +declare module 'next-intl' { + interface AppConfig { + Locale: (typeof locales)[number]; + Messages: typeof en; + } } diff --git a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx index 45ce1900d..986e6e93d 100644 --- a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx +++ b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx @@ -1,7 +1,6 @@ 'use client'; -import {useLocale} from 'next-intl'; -import {Locale} from '@/config'; +import {Locale, useLocale} from 'next-intl'; import {Link, usePathname} from '@/i18n/routing.public'; export default function PublicNavigationLocaleSwitcher() { diff --git a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/about/page.tsx b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/about/page.tsx index 260a990c6..ffe4b971b 100644 --- a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/about/page.tsx +++ b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/about/page.tsx @@ -1,9 +1,9 @@ -import {useTranslations} from 'next-intl'; +import {Locale, useTranslations} from 'next-intl'; import {setRequestLocale} from 'next-intl/server'; import PageTitle from '@/components/PageTitle'; type Props = { - params: {locale: string}; + params: {locale: Locale}; }; export default function About({params: {locale}}: Props) { diff --git a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/layout.tsx b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/layout.tsx index 258c83e5f..aca5d3544 100644 --- a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/layout.tsx +++ b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/layout.tsx @@ -1,6 +1,6 @@ import {Metadata} from 'next'; import {notFound} from 'next/navigation'; -import {NextIntlClientProvider} from 'next-intl'; +import {Locale, NextIntlClientProvider, isValidLocale} from 'next-intl'; import {getMessages, setRequestLocale} from 'next-intl/server'; import {ReactNode} from 'react'; import Document from '@/components/Document'; @@ -10,7 +10,7 @@ import PublicNavigationLocaleSwitcher from './PublicNavigationLocaleSwitcher'; type Props = { children: ReactNode; - params: {locale: string}; + params: {locale: Locale}; }; export function generateStaticParams() { @@ -25,14 +25,14 @@ export default async function LocaleLayout({ children, params: {locale} }: Props) { - // Enable static rendering - setRequestLocale(locale); - // Ensure that the incoming locale is valid - if (!locales.includes(locale as any)) { + if (!isValidLocale(locales, locale)) { notFound(); } + // Enable static rendering + setRequestLocale(locale); + // Providing all messages to the client // side is the easiest way to get started const messages = await getMessages(); diff --git a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/page.tsx b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/page.tsx index f3ee4cbf4..a12206760 100644 --- a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/page.tsx +++ b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/page.tsx @@ -1,9 +1,9 @@ -import {useTranslations} from 'next-intl'; +import {Locale, useTranslations} from 'next-intl'; import {setRequestLocale} from 'next-intl/server'; import PageTitle from '@/components/PageTitle'; type Props = { - params: {locale: string}; + params: {locale: Locale}; }; export default function Index({params: {locale}}: Props) { diff --git a/examples/example-app-router-mixed-routing/src/app/app/AppNavigationLocaleSwitcher.tsx b/examples/example-app-router-mixed-routing/src/app/app/AppNavigationLocaleSwitcher.tsx index 9101443dc..f85bc3c10 100644 --- a/examples/example-app-router-mixed-routing/src/app/app/AppNavigationLocaleSwitcher.tsx +++ b/examples/example-app-router-mixed-routing/src/app/app/AppNavigationLocaleSwitcher.tsx @@ -1,8 +1,7 @@ 'use client'; import {useRouter} from 'next/navigation'; -import {useLocale} from 'next-intl'; -import {Locale} from '@/config'; +import {Locale, useLocale} from 'next-intl'; import updateLocale from './updateLocale'; export default function AppNavigationLocaleSwitcher() { diff --git a/examples/example-app-router-mixed-routing/src/config.ts b/examples/example-app-router-mixed-routing/src/config.ts index d71700814..e7b729aa0 100644 --- a/examples/example-app-router-mixed-routing/src/config.ts +++ b/examples/example-app-router-mixed-routing/src/config.ts @@ -1,5 +1,5 @@ +import {Locale} from 'next-intl'; + export const locales = ['en', 'de'] as const; export const defaultLocale: Locale = 'en'; - -export type Locale = (typeof locales)[number]; diff --git a/examples/example-app-router-mixed-routing/src/db.ts b/examples/example-app-router-mixed-routing/src/db.ts index 266e9e6a9..44fefcb6e 100644 --- a/examples/example-app-router-mixed-routing/src/db.ts +++ b/examples/example-app-router-mixed-routing/src/db.ts @@ -1,5 +1,6 @@ import {cookies} from 'next/headers'; -import {defaultLocale} from './config'; +import {Locale, isValidLocale} from 'next-intl'; +import {defaultLocale, locales} from './config'; // This cookie name is used by `next-intl` on the public pages too. By // reading/writing to this locale, we can ensure that the user's locale @@ -8,8 +9,9 @@ import {defaultLocale} from './config'; // that instead when the user is logged in. const COOKIE_NAME = 'NEXT_LOCALE'; -export async function getUserLocale() { - return cookies().get(COOKIE_NAME)?.value || defaultLocale; +export async function getUserLocale(): Promise { + const candidate = cookies().get(COOKIE_NAME)?.value; + return isValidLocale(locales, candidate) ? candidate : defaultLocale; } export async function setUserLocale(locale: string) { diff --git a/examples/example-app-router-mixed-routing/src/i18n/request.ts b/examples/example-app-router-mixed-routing/src/i18n/request.ts index ff3845b6c..62f88c2a6 100644 --- a/examples/example-app-router-mixed-routing/src/i18n/request.ts +++ b/examples/example-app-router-mixed-routing/src/i18n/request.ts @@ -1,20 +1,17 @@ +import {isValidLocale} from 'next-intl'; import {getRequestConfig} from 'next-intl/server'; import {defaultLocale, locales} from '../config'; import {getUserLocale} from '../db'; export default getRequestConfig(async ({requestLocale}) => { // Read from potential `[locale]` segment - let locale = await requestLocale; + let candidate = await requestLocale; - if (!locale) { + if (!candidate) { // The user is logged in - locale = await getUserLocale(); - } - - // Ensure that the incoming locale is valid - if (!locales.includes(locale as any)) { - locale = defaultLocale; + candidate = await getUserLocale(); } + const locale = isValidLocale(locales, candidate) ? candidate : defaultLocale; return { locale, diff --git a/examples/example-app-router-next-auth/global.d.ts b/examples/example-app-router-next-auth/global.d.ts index b749518b9..6f30d7d82 100644 --- a/examples/example-app-router-next-auth/global.d.ts +++ b/examples/example-app-router-next-auth/global.d.ts @@ -1,8 +1,10 @@ +import 'next-intl'; +import {routing} from '@/i18n/routing'; import en from './messages/en.json'; -type Messages = typeof en; - -declare global { - // Use type safe message keys with `next-intl` - interface IntlMessages extends Messages {} +declare module 'next-intl' { + interface AppConfig { + Locale: (typeof routing.locales)[number]; + Messages: typeof en; + } } diff --git a/examples/example-app-router-next-auth/src/app/[locale]/layout.tsx b/examples/example-app-router-next-auth/src/app/[locale]/layout.tsx index 0acc48b28..844197840 100644 --- a/examples/example-app-router-next-auth/src/app/[locale]/layout.tsx +++ b/examples/example-app-router-next-auth/src/app/[locale]/layout.tsx @@ -1,20 +1,19 @@ import {notFound} from 'next/navigation'; -import {NextIntlClientProvider} from 'next-intl'; +import {Locale, NextIntlClientProvider, isValidLocale} from 'next-intl'; import {getMessages} from 'next-intl/server'; import {ReactNode} from 'react'; import {routing} from '@/i18n/routing'; type Props = { children: ReactNode; - params: {locale: string}; + params: {locale: Locale}; }; export default async function LocaleLayout({ children, params: {locale} }: Props) { - // Ensure that the incoming `locale` is valid - if (!routing.locales.includes(locale as any)) { + if (!isValidLocale(routing.locales, locale)) { notFound(); } diff --git a/examples/example-app-router-next-auth/src/i18n/request.ts b/examples/example-app-router-next-auth/src/i18n/request.ts index 70066e964..0534cbacd 100644 --- a/examples/example-app-router-next-auth/src/i18n/request.ts +++ b/examples/example-app-router-next-auth/src/i18n/request.ts @@ -1,14 +1,13 @@ +import {isValidLocale} from 'next-intl'; import {getRequestConfig} from 'next-intl/server'; import {routing} from './routing'; export default getRequestConfig(async ({requestLocale}) => { - // This typically corresponds to the `[locale]` segment - let locale = await requestLocale; - - // Ensure that the incoming locale is valid - if (!locale || !routing.locales.includes(locale as any)) { - locale = routing.defaultLocale; - } + // Typically corresponds to the `[locale]` segment + const requested = await requestLocale; + const locale = isValidLocale(routing.locales, requested) + ? requested + : routing.defaultLocale; return { locale, diff --git a/examples/example-app-router-playground/src/app/[locale]/layout.tsx b/examples/example-app-router-playground/src/app/[locale]/layout.tsx index 88935f893..0c8f63791 100644 --- a/examples/example-app-router-playground/src/app/[locale]/layout.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/layout.tsx @@ -1,6 +1,6 @@ import {Metadata} from 'next'; import {notFound} from 'next/navigation'; -import {Locale} from 'next-intl'; +import {Locale, isValidLocale} from 'next-intl'; import { getFormatter, getNow, @@ -36,8 +36,7 @@ export async function generateMetadata({ } export default function LocaleLayout({children, params: {locale}}: Props) { - // Ensure that the incoming `locale` is valid - if (!routing.locales.includes(locale as any)) { + if (!isValidLocale(routing.locales, locale)) { notFound(); } diff --git a/examples/example-app-router/global.d.ts b/examples/example-app-router/global.d.ts index 44f1f8333..9a7bf7165 100644 --- a/examples/example-app-router/global.d.ts +++ b/examples/example-app-router/global.d.ts @@ -5,12 +5,7 @@ import en from './messages/en.json'; declare module 'next-intl' { interface AppConfig { Locale: (typeof routing.locales)[number]; + Messages: typeof en; } } -type Messages = typeof en; - -declare global { - // Use type safe message keys with `next-intl` - interface IntlMessages extends Messages {} -} diff --git a/examples/example-pages-router-advanced/global.d.ts b/examples/example-pages-router-advanced/global.d.ts index b749518b9..094440ccb 100644 --- a/examples/example-pages-router-advanced/global.d.ts +++ b/examples/example-pages-router-advanced/global.d.ts @@ -1,8 +1,8 @@ +import 'next-intl'; import en from './messages/en.json'; -type Messages = typeof en; - -declare global { - // Use type safe message keys with `next-intl` - interface IntlMessages extends Messages {} +declare module 'next-intl' { + interface AppConfig { + Messages: typeof en; + } } diff --git a/examples/example-pages-router-advanced/src/pages/_app.tsx b/examples/example-pages-router-advanced/src/pages/_app.tsx index 14f75d12d..165d5fcbb 100644 --- a/examples/example-pages-router-advanced/src/pages/_app.tsx +++ b/examples/example-pages-router-advanced/src/pages/_app.tsx @@ -1,9 +1,9 @@ import {AppProps} from 'next/app'; import {useRouter} from 'next/router'; -import {NextIntlClientProvider} from 'next-intl'; +import {Messages, NextIntlClientProvider} from 'next-intl'; type PageProps = { - messages: IntlMessages; + messages: Messages; now: number; }; diff --git a/examples/example-pages-router/global.d.ts b/examples/example-pages-router/global.d.ts index b749518b9..094440ccb 100644 --- a/examples/example-pages-router/global.d.ts +++ b/examples/example-pages-router/global.d.ts @@ -1,8 +1,8 @@ +import 'next-intl'; import en from './messages/en.json'; -type Messages = typeof en; - -declare global { - // Use type safe message keys with `next-intl` - interface IntlMessages extends Messages {} +declare module 'next-intl' { + interface AppConfig { + Messages: typeof en; + } } diff --git a/examples/example-use-intl/global.d.ts b/examples/example-use-intl/global.d.ts new file mode 100644 index 000000000..5f39e9b68 --- /dev/null +++ b/examples/example-use-intl/global.d.ts @@ -0,0 +1,10 @@ +import 'use-intl'; +import en from './messages/en.json'; +import {locales} from './src/config'; + +declare module 'use-intl' { + interface AppConfig { + Locale: (typeof locales)[number]; + Messages: typeof en; + } +} diff --git a/examples/example-use-intl/messages/en.json b/examples/example-use-intl/messages/en.json new file mode 100644 index 000000000..51f26812a --- /dev/null +++ b/examples/example-use-intl/messages/en.json @@ -0,0 +1,5 @@ +{ + "App": { + "hello": "Hello {username}!" + } +} diff --git a/examples/example-use-intl/src/config.tsx b/examples/example-use-intl/src/config.tsx new file mode 100644 index 000000000..a8a68c781 --- /dev/null +++ b/examples/example-use-intl/src/config.tsx @@ -0,0 +1 @@ +export const locales = ['en'] as const; diff --git a/examples/example-use-intl/src/main.tsx b/examples/example-use-intl/src/main.tsx index dc772d8ab..d48504350 100644 --- a/examples/example-use-intl/src/main.tsx +++ b/examples/example-use-intl/src/main.tsx @@ -1,16 +1,13 @@ import {StrictMode} from 'react'; import ReactDOM from 'react-dom/client'; import {IntlProvider} from 'use-intl'; +import en from '../messages/en.json'; import App from './App.tsx'; // You can get the messages from anywhere you like. You can also // fetch them from within a component and then render the provider // along with your app once you have the messages. -const messages = { - App: { - hello: 'Hello {username}!' - } -}; +const messages = en; const node = document.getElementById('root'); diff --git a/examples/example-use-intl/tsconfig.json b/examples/example-use-intl/tsconfig.json index ba939e9e6..799ffcd9d 100644 --- a/examples/example-use-intl/tsconfig.json +++ b/examples/example-use-intl/tsconfig.json @@ -13,6 +13,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src"], + "include": ["**/*.ts", "**/*.tsx"], "references": [{"path": "./tsconfig.node.json"}] }