Skip to content

Commit b90b303

Browse files
authored
docs: i18n type narrowing w/ runtime guards (#86871)
Closes: #86864
1 parent 347c8df commit b90b303

File tree

1 file changed

+27
-20
lines changed

1 file changed

+27
-20
lines changed

docs/01-app/02-guides/internationalization.mdx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,7 @@ Finally, ensure all special files inside `app/` are nested under `app/[lang]`. T
7272
```tsx filename="app/[lang]/page.tsx" switcher
7373
// You now have access to the current locale
7474
// e.g. /en-US/products -> `lang` is "en-US"
75-
export default async function Page({
76-
params,
77-
}: {
78-
params: Promise<{ lang: string }>
79-
}) {
75+
export default async function Page({ params }: PageProps<'/[lang]'>) {
8076
const { lang } = await params
8177
return ...
8278
}
@@ -91,6 +87,8 @@ export default async function Page({ params }) {
9187
}
9288
```
9389

90+
> **Good to know:** `PageProps` and `LayoutProps` are globally available TypeScript helpers that provide strong typing for route parameters. See [PageProps](/docs/app/api-reference/file-conventions/page#page-props-helper) and [LayoutProps](/docs/app/api-reference/file-conventions/layout#layout-props-helper) for more details.
91+
9492
The root layout can also be nested in the new folder (e.g. `app/[lang]/layout.js`).
9593

9694
## Localization
@@ -125,8 +123,12 @@ const dictionaries = {
125123
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
126124
}
127125

128-
export const getDictionary = async (locale: 'en' | 'nl') =>
129-
dictionaries[locale]()
126+
export type Locale = keyof typeof dictionaries
127+
128+
export const hasLocale = (locale: string): locale is Locale =>
129+
locale in dictionaries
130+
131+
export const getDictionary = async (locale: Locale) => dictionaries[locale]()
130132
```
131133

132134
```js filename="app/[lang]/dictionaries.js" switcher
@@ -137,31 +139,39 @@ const dictionaries = {
137139
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
138140
}
139141

142+
export const hasLocale = (locale) => locale in dictionaries
143+
140144
export const getDictionary = async (locale) => dictionaries[locale]()
141145
```
142146

143147
Given the currently selected language, we can fetch the dictionary inside of a layout or page.
144148

149+
Since `lang` is typed as `string`, using `hasLocale` narrows the type to your supported locales. It also ensures a 404 is returned if a translation is missing, rather than a runtime error.
150+
145151
```tsx filename="app/[lang]/page.tsx" switcher
146-
import { getDictionary } from './dictionaries'
152+
import { notFound } from 'next/navigation'
153+
import { getDictionary, hasLocale } from './dictionaries'
147154

148-
export default async function Page({
149-
params,
150-
}: {
151-
params: Promise<{ lang: 'en' | 'nl' }>
152-
}) {
155+
export default async function Page({ params }: PageProps<'/[lang]'>) {
153156
const { lang } = await params
154-
const dict = await getDictionary(lang) // en
157+
158+
if (!hasLocale(lang)) notFound()
159+
160+
const dict = await getDictionary(lang)
155161
return <button>{dict.products.cart}</button> // Add to Cart
156162
}
157163
```
158164

159165
```jsx filename="app/[lang]/page.js" switcher
160-
import { getDictionary } from './dictionaries'
166+
import { notFound } from 'next/navigation'
167+
import { getDictionary, hasLocale } from './dictionaries'
161168

162169
export default async function Page({ params }) {
163170
const { lang } = await params
164-
const dict = await getDictionary(lang) // en
171+
172+
if (!hasLocale(lang)) notFound()
173+
174+
const dict = await getDictionary(lang)
165175
return <button>{dict.products.cart}</button> // Add to Cart
166176
}
167177
```
@@ -180,10 +190,7 @@ export async function generateStaticParams() {
180190
export default async function RootLayout({
181191
children,
182192
params,
183-
}: Readonly<{
184-
children: React.ReactNode
185-
params: Promise<{ lang: 'en-US' | 'de' }>
186-
}>) {
193+
}: LayoutProps<'/[lang]'>) {
187194
return (
188195
<html lang={(await params).lang}>
189196
<body>{children}</body>

0 commit comments

Comments
 (0)