Skip to content

Commit

Permalink
feat: Support providing a locale in i18n.ts instead of reading it f…
Browse files Browse the repository at this point in the history
…rom the pathname (#1017)
  • Loading branch information
amannn committed May 15, 2024
1 parent 6c32f87 commit 5c968b2
Show file tree
Hide file tree
Showing 68 changed files with 1,842 additions and 674 deletions.
39 changes: 39 additions & 0 deletions docs/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import cx from 'clsx';
import NextLink from 'next/link';
import {ReactNode} from 'react';

type Props = {
children?: ReactNode;
title: string;
href: string;
};

export default function Card({children, href, title, ...props}: Props) {
return (
<NextLink
className={cx(
'group flex flex-col justify-start overflow-hidden rounded-lg border border-gray-200',
'text-current no-underline dark:shadow-none',
'hover:shadow-gray-100 dark:hover:shadow-none shadow-gray-100',
'active:shadow-sm active:shadow-gray-200',
'transition-all duration-200 hover:border-gray-300',
'bg-transparent shadow-sm dark:border-neutral-800 hover:bg-slate-50 hover:shadow-md dark:hover:border-neutral-700 dark:hover:bg-neutral-900 p-4'
)}
href={href}
{...props}
>
<span
className={cx(
'flex font-semibold items-start gap-2 text-gray-700 hover:text-gray-900',
'dark:text-neutral-200 dark:hover:text-neutral-50 flex items-center'
)}
>
{title}
<span className="transition-transform duration-75 group-hover:translate-x-[2px]">
</span>
</span>
{children && <div className="mt-3">{children}</div>}
</NextLink>
);
}
12 changes: 12 additions & 0 deletions docs/components/Cards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import cx from 'clsx';
import type {ComponentProps} from 'react';

type Props = ComponentProps<'div'>;

export default function Cards({children, className, ...props}: Props) {
return (
<div className={cx('flex flex-col gap-4', className)} {...props}>
{children}
</div>
);
}
4 changes: 3 additions & 1 deletion docs/components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ export default function Hero({
</p>
<div className="mt-8 flex flex-col gap-4 md:flex-row lg:mt-10">
<div>
<LinkButton href="/docs">{getStarted}</LinkButton>
<LinkButton href="/docs/getting-started">
{getStarted}
</LinkButton>
</div>
<div>
<LinkButton
Expand Down
67 changes: 67 additions & 0 deletions docs/components/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
export default function Logo() {
return (
<svg
aria-label="next-intl logo"
className="h-8"
viewBox="0 0 1663.8 399"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<path
d="M811.8,483V346.8c2-.8,6.4-1.7,13-2.7a164.81,164.81,0,0,1,23.9-1.5c8.6,0,15.8,1,21.5,3.1A28.62,28.62,0,0,1,884,356.1q5.25,7.2,7.5,18.6c1.5,7.6,2.2,16.9,2.2,27.9h0V483h19.5V396.3a131.41,131.41,0,0,0-3-29.1,61.1,61.1,0,0,0-10-22.7A45.29,45.29,0,0,0,881.3,330c-7.9-3.4-17.8-5.1-29.5-5.1q-21.3,0-36,3t-23.4,5.4h0V483Zm217.1,3.3a100.28,100.28,0,0,0,27.2-3.3c7.7-2.2,12.9-4.1,15.8-5.7h0l-3.6-16.8a105.18,105.18,0,0,1-13.5,5.1q-9.3,3-25.2,3-28.8,0-42-15.3t-14.1-43.8h108.9c.2-1.4.3-2.9.4-4.5s.2-3.2.2-4.8q0-38.7-17-57.3t-45.4-18.6a65.24,65.24,0,0,0-25,5A61.35,61.35,0,0,0,974,344.4a77.5,77.5,0,0,0-15.3,25.4c-3.9,10.1-5.8,22-5.8,35.5a119.68,119.68,0,0,0,4.2,32.6,66.21,66.21,0,0,0,13.5,25.6,62.29,62.29,0,0,0,23.7,16.8C1003.7,484.3,1015.3,486.3,1028.9,486.3Zm33.9-93.3H974a70.46,70.46,0,0,1,4-19.5,51.72,51.72,0,0,1,9.6-16.4,47.46,47.46,0,0,1,14.4-11.2,41,41,0,0,1,18.8-4.2q19.8,0,30.6,13.5t11.4,37.8Zm65.3,90c2.4-4.4,5.3-9.5,8.7-15.1s7.1-11.6,11.2-17.9,8.3-12.5,12.8-18.9,8.8-12.5,13.2-18.3c4.2,5.8,8.6,11.9,13.1,18.3s8.8,12.7,12.9,18.9,7.9,12.1,11.2,17.9,6.3,10.8,8.7,15.1h21c-3.8-7.4-8-14.9-12.5-22.5s-9.1-15-13.9-22-9.6-14-14.4-20.6-9.3-12.7-13.5-18.3h0l49.5-71.7h-21.3L1174,387.3l-40.2-59.4H1111l50.4,72.9q-14.1,19.2-27.6,39.2a474.65,474.65,0,0,0-25.8,43h20.1Zm195.2,3.3a72.65,72.65,0,0,0,22.8-3.4c7-2.3,11.9-4.4,14.7-6.1h0l-4.8-16.2a75.87,75.87,0,0,1-11.7,5.2c-5,1.8-11.6,2.9-19.8,2.9a54.4,54.4,0,0,1-13.2-1.5,20.45,20.45,0,0,1-10.2-6c-2.8-3-4.9-7.2-6.4-12.8s-2.2-12.6-2.2-21.5h0V344.4h61.8V327.9h-61.8V278.7l-19.5,3.6V425.1a132.63,132.63,0,0,0,2.4,27c1.6,7.6,4.3,13.9,8.2,19a34.92,34.92,0,0,0,15.6,11.4C1305.7,485,1313.7,486.3,1323.3,486.3Zm107.9-80.7V386.7h-67.8v18.9ZM1474,296.1a14.2,14.2,0,0,0,10.2-4.1c2.8-2.7,4.2-6.3,4.2-10.9s-1.4-8.2-4.2-11a14.11,14.11,0,0,0-10.2-4,14.53,14.53,0,0,0-10.2,4c-2.8,2.7-4.2,6.4-4.2,11s1.4,8.2,4.2,10.9A13.8,13.8,0,0,0,1474,296.1Zm9.9,186.9V327.9h-19.5V483Zm70.1,0V346.8c2-.8,6.3-1.7,13.1-2.7a163.34,163.34,0,0,1,23.8-1.5c8.6,0,15.8,1,21.4,3.1a28.62,28.62,0,0,1,13.8,10.4q5.25,7.2,7.5,18.6c1.5,7.6,2.2,16.9,2.2,27.9h0V483h19.5V396.3a131.41,131.41,0,0,0-3-29.1,58.17,58.17,0,0,0-10.1-22.7,45.29,45.29,0,0,0-18.9-14.5c-7.9-3.4-17.8-5.1-29.5-5.1q-21.3,0-36,3t-23.4,5.4h0V483Zm199.4,3.3a72.65,72.65,0,0,0,22.8-3.4c7-2.3,11.9-4.4,14.7-6.1h0l-4.8-16.2a75.87,75.87,0,0,1-11.7,5.2c-5,1.8-11.6,2.9-19.8,2.9a54.4,54.4,0,0,1-13.2-1.5,20.45,20.45,0,0,1-10.2-6c-2.8-3-4.9-7.2-6.4-12.8s-2.2-12.6-2.2-21.5h0V344.4h61.8V327.9h-61.8V278.7l-19.5,3.6V425.1a132.63,132.63,0,0,0,2.4,27c1.6,7.6,4.3,13.9,8.2,19a34.92,34.92,0,0,0,15.6,11.4C1735.8,485,1743.8,486.3,1753.4,486.3Zm105.2-.6,2.7-16.2a90.28,90.28,0,0,1-10.4-2.1,15.3,15.3,0,0,1-7-3.8,15.7,15.7,0,0,1-4.1-6.9,40.44,40.44,0,0,1-1.3-11.5h0v-195l-19.5,3.6v192c0,7.4.8,13.6,2.6,18.5a29.28,29.28,0,0,0,7.5,12,30.81,30.81,0,0,0,12.3,6.9,65.79,65.79,0,0,0,17.2,2.5Z"
fill="currentColor"
transform="translate(-197.5 -171.5)"
/>
<g>
<path
d="M528.8,509q-6.3,6-13.2,11.4a190,190,0,1,1,38-40.2"
fill="none"
stroke="#5fc3e7"
strokeWidth="18"
transform="translate(-197.5 -171.5)"
/>
<circle cx="329.2" cy="339.5" fill="#5fc3e7" r="9" />
<line
fill="none"
stroke="#5fc3e7"
strokeLinecap="square"
strokeWidth="18"
x1="40"
x2="353.5"
y1="105"
y2="105.5"
/>
<ellipse
cx="202"
cy="199.5"
fill="none"
rx="93.5"
ry="190.5"
stroke="#5fc3e7"
strokeWidth="18"
/>
<line
fill="none"
stroke="#5fc3e7"
strokeLinecap="square"
strokeWidth="18"
x1="49.5"
x2="353.5"
y1="304.5"
y2="304.5"
/>
<line
fill="none"
stroke="#5fc3e7"
strokeLinecap="square"
strokeWidth="18"
x1="9"
x2="389"
y1="206"
y2="206"
/>
</g>
</g>
</svg>
);
}
6 changes: 5 additions & 1 deletion docs/pages/docs/design-principles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ This page also links to planned improvements in order to act as a transparent re
Internationalization clearly requires flexibility from your codebase. However, even implementing a single language properly can already be a challenge by itself.

Using **dynamic text labels** is the most obvious aspect of internationalization, but supporting a language well also includes many other aspects:

1. **Pluralization rules**: While a language like English has only two plural forms (singular and plural), other languages have up to six different forms.
2. **Date and time formatting**: Different languages have different conventions for formatting dates and times. Even the year displayed can vary from country to country; for example, Thailand uses the Buddhist calendar, which is 543 years ahead of the Gregorian one.
3. **Number formatting**: Formatting conventions for numbers vary across different languages. For instance, when comparing English and German, the separators for thousands and decimals are flipped.
4. **List formatting**: Formatting lists like "HTML, CSS, and JavaScript" is not only a matter of assembling strings in the right order, but also of using language-specific conjunctions and punctuation marks.
5. **Text direction**: While most languages are written from left to right, some languages like Arabic are written from right to left and require a mirrored layout.

On top of this come typical app problems like:

1. **Rich text formatting**: Many apps need to support some way of rich text, e.g. to embed links into text labels ("Learn more in [the rich text docs](/docs/usage/messages#rich-text).").
2. **Time zones**: Displaying dates requires consistent handling of time zones across the server and client, potentially even customized based on a preference of the user.
3. **Relative time formatting**: Displaying relative times like "5 minutes ago" or "in 2 hours" requires special care to get the formatting right, and also to make sure the rendered result is in sync across the server and client. Potentially, you also need a mechanism to update the displayed time regularly.
Expand Down Expand Up @@ -91,6 +93,7 @@ Next.js has a rich ecosystem of libraries that can be used alongside `next-intl`
`next-intl` was designed with high-traffic sites in mind that need to deliver a fast and reliable user experience and has proven to work on complex e-commerce pages with outstanding Core Web Vitals.

To achieve this, `next-intl` primarly relies on these techniques currently:

1. **Splitting of messages**: By splitting messages by locale, and optionally also by server, client and component, we can reduce the amount of messages that are sent to the client. This is especially important for apps that support many languages and have a large amount of messages.
2. **Shortcuts**: By detecting plain messages, these messages can be returned immediately without having to parse them first.
3. **Caching**: Parsing and formatting of messages is cached across your app, therefore reducing the amount of necessary computation.
Expand All @@ -109,6 +112,7 @@ That being said, `next-intl` has a Next.js-agnostic core that can be used in any
We've all been there, technology moves on, and sometimes you need to move on as well. `next-intl` is designed to be a good citizen in your codebase, and to make it possible to migrate away from certain parts of your stack in case this becomes necessary.

If you ever feel like Next.js or `next-intl` is not the right fit for your project anymore, you have multiple options here:

1. **Moving away from Next.js**: If you decide to migrate away from Next.js, you can continue to use the core library [`use-intl`](/docs/environments/core-library) in any React app, e.g. allowing you to reuse existing components in [a Remix app](/examples#remix).
2. **Moving away from `next-intl`**: If you find that `next-intl` doesn't fit your needs anymore, you'll have to adapt app code that references the library, but you can still reuse your [standards-based](#standards-based) ICU messages and replace formatting APIs e.g. with direct calls to the [ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl).

Expand All @@ -118,4 +122,4 @@ That being said, we're doing our best to make `next-intl` a great fit for your p

---

Woah, this was a long read. Did you really read all of this? `next-intl` was created out of a lot of curiosity and passion for internationalization, seems like we share that. We're always curious to hear how `next-intl` is working out for you. If you have any feedback or questions, please don't hesitate to [reach out](https://github.com/amannn/next-intl/discussions)!
Woah, this was a long read. Did you really read all of this? `next-intl` was created out of a lot of curiosity and passion for internationalization, seems like we share that! We're always curious to hear how `next-intl` is working out for you. If you have any feedback or questions, please don't hesitate to [reach out](https://github.com/amannn/next-intl/discussions)!
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import {Card} from 'nextra-theme-docs';
import Card from 'components/Card';
import Cards from 'components/Cards';

# Environments

The `next-intl` APIs are available in the following environments:

<div className="mt-8 flex flex-col gap-4 md:w-2/3">
<Cards className="mt-8 lg:w-1/2">
<Card
arrow
title="Server & Client Components"
href="/docs/environments/server-client-components"
/>
<Card
arrow
title="Metadata API & Route Handlers"
href="/docs/environments/metadata-route-handlers"
/>
<Card
arrow
title="Error files (e.g. not-found)"
href="/docs/environments/error-files"
/>
<Card
arrow
title="Core library (agnostic)"
href="/docs/environments/core-library"
/>
</div>
</Cards>

While modern browsers and server runtimes typically support all necessary JavaScript APIs that are required for `next-intl`, you can double check [the runtime requirements](/docs/environments/runtime-requirements).
1 change: 0 additions & 1 deletion docs/pages/docs/environments/_meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"index": "Overview",
"server-client-components": "Server & Client Components",
"metadata-route-handlers": "Metadata & Route Handlers",
"error-files": "Error files (e.g. not-found)",
Expand Down
10 changes: 6 additions & 4 deletions docs/pages/docs/environments/core-library.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ While `next-intl` is primarily intended to be used in Next.js apps, the core is
`next-intl` is based on a library called [`use-intl`](https://www.npmjs.com/package/use-intl) that is developed in parallel.

This core library contains most features of `next-intl`, but lacks the following Next.js-specific features:

1. [Routing APIs](/docs/routing)
2. [Awaitable APIs for the Metadata API and Route Handlers](/docs/environments/metadata-route-handlers)
3. [Server Components integration](/docs/environments/server-client-components) along with `i18n.ts`

In case Server Components establish themselves in React apps outside of Next.js, the support for Server Components might be moved to the core library in the future.

In contrast, `use-intl` contains all APIs that are necessary for handling i18n in regular React apps:

- [`useTranslations`](/docs/usage/messages) for translating messages
- `useFormatter` for formatting of [numbers](/docs/usage/numbers), [dates & times](/docs/usage/dates-times) and [lists](/docs/usage/lists)
- `useFormatter` for formatting [numbers](/docs/usage/numbers), [dates & times](/docs/usage/dates-times) and [lists](/docs/usage/lists)
- [Configuration APIs](/docs/usage/configuration) (note however that `NextIntlProvider` is called `IntlProvider` in `use-intl`)

This allows you to use the same APIs that you know from `next-intl` in other environments:
Expand All @@ -32,11 +34,11 @@ This allows you to use the same APIs that you know from `next-intl` in other env
import {IntlProvider, useTranslations} from 'use-intl';

// You can get the messages from anywhere you like. You can also
// fetch them from within a component and then render the provider
// 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}!'
App: {
hello: 'Hello {username}!'
}
};

Expand Down
27 changes: 14 additions & 13 deletions docs/pages/docs/environments/error-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ The Next.js App Router's file convention provides two files that can be used for

This page provides practical guides for these cases.

<Callout>
Have a look at [the App Router
example](https://next-intl-example-app-router.vercel.app) to explore a working
app with error handling.
</Callout>
**Tip:** You can have a look at [the App Router example](/examples#app-router) to explore a working app with error handling.

## `not-found.js`

<Callout>
This section is only relevant if you're using [i18n
routing](/docs/getting-started/app-router).
</Callout>

Next.js renders the closest `not-found` page when a route segment calls the [`notFound` function](https://nextjs.org/docs/app/api-reference/functions/not-found). We can use this mechanism to provide a localized 404 page by adding a `not-found` file within the `[locale]` folder.

```tsx filename="app/[locale]/not-found.tsx"
Expand Down Expand Up @@ -104,11 +105,11 @@ When an `error` file is defined, Next.js creates [an error boundary within your
<figure>
<div className="w-full lg:w-[500px]">
```tsx
<LocaleLayout>
<RootLayout>
<ErrorBoundary fallback={<Error />}>
<Page />
</ErrorBoundary>
</LocaleLayout>
</RootLayout>
```
</div>

Expand All @@ -120,20 +121,20 @@ When an `error` file is defined, Next.js creates [an error boundary within your

Since the `error` file must be defined as a Client Component, you have to use [`NextIntlClientProvider`](/docs/usage/configuration#nextintlclientprovider) to provide messages in case the `error` file renders.

```tsx filename="app/[locale]/layout.tsx"
```tsx filename="layout.tsx"
import pick from 'lodash/pick';
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';

export default async function LocaleLayout({children}) {
// ...
export default async function RootLayout(/* ... */) {
const messages = await getMessages();

return (
<html lang={locale}>
<body>
<NextIntlClientProvider
locale={locale}
// Make sure to provide at least the messages for `Error`
messages={pick(messages, 'Error')}
>
{children}
Expand All @@ -146,7 +147,7 @@ export default async function LocaleLayout({children}) {

Once `NextIntlClientProvider` is in place, you can use functionality from `next-intl` in the `error` file:

```tsx filename="app/[locale]/error.tsx"
```tsx filename="error.tsx"
'use client';

import {useTranslations} from 'next-intl';
Expand All @@ -163,9 +164,9 @@ export default function Error({error, reset}) {
}
```

Note that `error.tsx` is loaded right after your app has initialized. If your app is performance-senstive and you want to avoid loading translation functionality from `next-intl` as part of the initial bundle, you can export a lazy reference from your `error` file:
Note that `error.tsx` is loaded right after your app has initialized. If your app is performance-senstive and you want to avoid loading translation functionality from `next-intl` as part of this bundle, you can export a lazy reference from your `error` file:

```tsx filename="app/[locale]/error.tsx"
```tsx filename="error.tsx"
'use client';

import {lazy} from 'react';
Expand Down
5 changes: 3 additions & 2 deletions docs/pages/docs/environments/metadata-route-handlers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ There are a few places in Next.js apps where you can apply internationalization

To internationalize metadata like the page title, you can use functionality from `next-intl` in the [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function) function that can be exported from pages and layouts.

```tsx filename="app/[locale]/layout.tsx"
```tsx filename="layout.tsx"
import {getTranslations} from 'next-intl/server';

export async function generateMetadata({params: {locale}}) {
Expand All @@ -32,7 +32,8 @@ export async function generateMetadata({params: {locale}}) {
<Callout>
By passing an explicit `locale` to the awaitable functions from `next-intl`,
you can make the metadata handler eligible for [static
rendering](/docs/getting-started/app-router#static-rendering).
rendering](/docs/getting-started/app-router#static-rendering) if you're using
[i18n routing](/docs/getting-started/app-router).
</Callout>

### Open Graph images
Expand Down
9 changes: 5 additions & 4 deletions docs/pages/docs/environments/runtime-requirements.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,21 @@ function IntlPolyfills() {
<Script
strategy="beforeInteractive"
src={
'https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=' + polyfills.join(',')
'https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=' +
polyfills.join(',')
}
/>
);
}
```

<Callout type="warning">
Note that the polyfill service doesn't support every locale. You can find a list of the
available polyfills in the [`polyfill-service` GitHub
Note that the polyfill service doesn't support every locale. You can find a
list of the available polyfills in the [`polyfill-service`
repository](https://github.com/cdnjs/polyfill-service/tree/main/polyfill-libraries/3.101.0/polyfills/__dist)
(e.g. search for `Intl.DateTimeFormat.~locale.de-AT`).
</Callout>

## Node
## Node.js

The minimum version to support all relevant `Intl` APIs is **Node.js 13**. Starting from this version, all required APIs are available.

0 comments on commit 5c968b2

Please sign in to comment.