You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat!: Stricter config for domains to improve handling of localePrefix: 'as-needed' (#1734)
So far, when using
[`domains`](https://next-intl.dev/docs/routing#domains) in combination
with `localePrefix: 'as-needed'`, `next-intl` had to make some
[tradeoffs](https://next-intl-docs-6wwcmwb9a-next-intl.vercel.app/docs/routing#domains-localeprefix-asneeded).
Now, `next-intl` comes with stricter requirements when `domains` is
used:
1. A locale can now only be used for a single domain
2. Each domain now must specify its `locales`
By introducing these constraints, the mentioned tradeoffs now can be
removed altogether, resulting in a simplified model.
If you previously used locales across multiple domains, you now have to
be more specific—typically by introducing a regional variant for a base
language. You can additionally customize the prefixes if desired.
**Before**
```tsx
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['sv', 'en', 'no', 'fr'],
defaultLocale: 'en',
localePrefix: 'as-needed',
domains: [
{
domain: 'domain.se',
defaultLocale: 'sv',
locales: ['sv', 'en']
},
{
domain: 'domain.no',
defaultLocale: 'no',
locales: ['no', 'en']
},
{
domain: 'domain.com',
defaultLocale: 'en',
locales: ['en', 'fr']
},
]
});
```
**After**
```tsx
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['sv-SE', 'en-SE', 'no-NO', 'en-NO', 'fr-FR', 'en-US'],
defaultLocale: 'en-US',
localePrefix: {
mode: 'as-needed',
prefixes: {
'en-SE': '/en',
'en-NO': '/en',
'en-US': '/en',
'fr-FR': '/fr'
}
},
domains: [
{
domain: 'domain.se',
defaultLocale: 'sv-SE',
locales: ['sv-SE', 'en-SE']
},
{
domain: 'domain.no',
defaultLocale: 'no-NO',
locales: ['no-NO', 'en-NO']
},
{
domain: 'domain.com',
defaultLocale: 'en-US',
locales: ['en-US', 'fr-FR']
},
]
});
```
Learn more in the updated docs for
[`domains`](https://v4.next-intl.dev/docs/routing#domains).
Resolves#1733
**TODO**
- [x] Docs
- [x] Implementation
- [x] Real world test and make sure we don't have any problems with
prefixes
- [ ] When users have more locales,
#990 would be really handy
- [ ] Back-port warning to v3 (also that locales are becoming unique per
domain)
- [ ] Update blog post
Copy file name to clipboardexpand all lines: docs/src/pages/docs/routing.mdx
+30-54
Original file line number
Diff line number
Diff line change
@@ -321,37 +321,49 @@ If you want to serve your localized content based on different domains, you can
321
321
322
322
**Examples:**
323
323
324
-
-`us.example.com/en`
325
-
-`ca.example.com/en`
326
-
-`ca.example.com/fr`
324
+
-`us.example.com`: `en-US`
325
+
-`ca.example.com`: `en-CA`
326
+
-`ca.example.com/fr`: `fr-CA`
327
+
-`fr.example.com`: `fr-FR`
328
+
329
+
In many cases, `domains` are combined with a [`localePrefix`](#locale-prefix) setting to achieve results as shown above. Also [custom prefixes](#locale-prefix-custom) can be used to customize the user-facing prefix per locale.
327
330
328
331
```tsx filename="routing.ts"
329
332
import {defineRouting} from'next-intl/routing';
330
333
331
334
exportconst routing =defineRouting({
332
-
locales: ['en', 'fr'],
333
-
defaultLocale: 'en',
335
+
locales: ['en-US', 'en-CA', 'fr-CA', 'fr-FR'],
336
+
defaultLocale: 'en-US',
334
337
domains: [
335
338
{
336
339
domain: 'us.example.com',
337
-
defaultLocale: 'en',
338
-
// Optionally restrict the locales available on this domain
339
-
locales: ['en']
340
+
defaultLocale: 'en-US',
341
+
locales: ['en-US']
340
342
},
341
343
{
342
344
domain: 'ca.example.com',
343
-
defaultLocale: 'en'
344
-
// If there are no `locales` specified on a domain,
345
-
// all available locales will be supported here
345
+
defaultLocale: 'en-CA',
346
+
locales: ['en-CA', 'fr-CA']
347
+
},
348
+
{
349
+
domain: 'fr.example.com',
350
+
defaultLocale: 'fr-FR',
351
+
locales: ['fr-FR']
346
352
}
347
-
]
353
+
],
354
+
localePrefix: {
355
+
mode: 'as-needed',
356
+
prefixes: {
357
+
// Cleaner prefix for `ca.example.com/fr`
358
+
'fr-CA': '/fr'
359
+
}
360
+
}
348
361
});
349
362
```
350
363
351
-
**Note that:**
364
+
Locales are required to be unique across domains, therefore regional variants are typically used to avoid conflicts. Note however that you don't necessarily need to [provide messages for each locale](/docs/usage/configuration#messages-per-locale) if the overall language is sufficient for your use case.
352
365
353
-
1. You can optionally remove the locale prefix in pathnames by changing the [`localePrefix`](#locale-prefix) setting. E.g. [`localePrefix: 'never'`](#locale-prefix-never) can be helpful in case you have unique domains per locale.
354
-
2. If no domain matches, the middleware will fall back to the [`defaultLocale`](/docs/routing/middleware#default-locale) (e.g. on `localhost`).
366
+
If no domain matches, the middleware will fall back to the general [`defaultLocale`](/docs/routing/middleware#default-locale) (e.g. on `localhost`).
355
367
356
368
<Detailsid="domains-testing">
357
369
<summary>How can I locally test if my setup is working?</summary>
@@ -393,9 +405,7 @@ PORT=3001 npm run dev
393
405
<Detailsid="domains-localeprefix-individual">
394
406
<summary>Can I use a different `localePrefix` setting per domain?</summary>
395
407
396
-
Since such a configuration would require reading the domain at runtime, this would prevent the ability to render pages statically. Due to this, `next-intl` doesn't support this configuration out of the box.
397
-
398
-
However, you can still achieve this by building the app for each domain separately, while injecting diverging routing configuration via an environment variable.
408
+
While this is currently not supported out of the box, you can still achieve this by building the app for each domain separately while injecting diverging routing configuration via an environment variable.
<summary>Special case: Using `domains` with `localePrefix: 'as-needed'`</summary>
419
-
420
-
Since domains can have different default locales, this combination requires some tradeoffs that apply to the [navigation APIs](/docs/routing/navigation) in order for `next-intl` to avoid reading the current host on the server side (which would prevent the usage of static rendering).
421
-
422
-
1.[`<Link />`](/docs/routing/navigation#link): This component will always render a locale prefix on the server side, even for the default locale of a given domain. However, during hydration on the client side, the prefix is potentially removed, if the default locale of the current domain is used. Note that the temporarily prefixed pathname will always be valid, however the middleware will potentially clean up a superfluous prefix via a redirect if the user clicks on a link before hydration.
423
-
2.[`redirect`](/docs/routing/navigation#redirect): When calling this function, a locale prefix is always added, regardless of the provided locale. However, similar to the handling with `<Link />`, the middleware will potentially clean up a superfluous prefix.
424
-
3.[`getPathname`](/docs/routing/navigation#getpathname): This function requires that a `domain` is passed as part of the arguments in order to avoid ambiguity. This can either be provided statically (e.g. when used in a sitemap), or read from a header like [`x-forwarded-host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host).
425
-
426
-
```tsx
427
-
import {getPathname} from'@/i18n/routing';
428
-
import {headers} from'next/headers';
429
-
430
-
// Case 1: Statically known domain
431
-
const domain ='ca.example.com';
432
-
433
-
// Case 2: Read at runtime (dynamic rendering)
434
-
const domain =headers().get('x-forwarded-host');
435
-
436
-
// Assuming the current domain is `ca.example.com`,
437
-
// the returned pathname will be `/about`
438
-
const pathname =getPathname({
439
-
href: '/about',
440
-
locale: 'en',
441
-
domain
442
-
});
443
-
```
444
-
445
-
A `domain` can optionally also be passed to `redirect` in the same manner to ensure that a prefix is only added when necessary. Alternatively, you can also consider redirecting in the middleware or via [`useRouter`](/docs/routing/navigation#usrouter) on the client side.
446
-
447
-
If you need to avoid these tradeoffs, you can consider building the same app for each domain separately, while injecting diverging routing configuration via an [environment variable](#domains-localeprefix-individual).
448
-
449
-
</Details>
450
-
451
427
### Turning off locale detection [#locale-detection]
452
428
453
429
The middleware will [detect a matching locale](/docs/routing/middleware#locale-detection) based on your routing configuration and the incoming request.
Copy file name to clipboardexpand all lines: docs/src/pages/docs/usage/configuration.mdx
+24
Original file line number
Diff line number
Diff line change
@@ -358,6 +358,30 @@ Note that [the VSCode integration for `next-intl`](/docs/workflows/vscode-integr
358
358
359
359
</Details>
360
360
361
+
<Detailsid="messages-per-locale">
362
+
<summary>Do I need separate messages for each locale that my app supports?</summary>
363
+
364
+
Since you have full control over how messages are loaded, you can choose to load messages for example merely based on the overall language, ignoring any regional variants:
0 commit comments