Skip to content

Commit

Permalink
feat: Add useFormatter (replaces useIntl) (#209)
Browse files Browse the repository at this point in the history
Previously:

```tsx
import {useIntl} from 'next-intl';

const intl = useIntl();
intl.formatNumber(…);
intl.formatDateTime(…);
intl.formatRelativeTime(…);
```

Now:

```tsx
import {useFormatter} from 'next-intl';

const format = useFormatter();
format.number(…);
format.dateTime(…);
format.relativeTime(…);
```
  • Loading branch information
amannn authored Mar 6, 2023
1 parent 3bcdee0 commit 021b682
Show file tree
Hide file tree
Showing 27 changed files with 889 additions and 292 deletions.
51 changes: 31 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,62 @@
<br>
</h1>

> A minimal, but complete solution for internationalization in Next.js apps.
> Internationalization for Next.js that gets out of your way.
![Gzipped size](https://badgen.net/bundlephobia/minzip/next-intl) ![Tree shaking supported](https://badgen.net/bundlephobia/tree-shaking/next-intl) [<img src="https://img.shields.io/npm/dw/next-intl.svg" />](https://www.npmjs.com/package/next-intl)

<hr />

**🚀 New ✨**: `next-intl` is adding support for **Next.js 13** and the [`app` directory](https://beta.nextjs.org/docs/routing/fundamentals). A preview version with support for React Server Components is [now available](https://next-intl-docs.vercel.app/docs/next-13) for early adopters.
📣 [Support for Next.js 13 and the app directory is coming →](https://next-intl-docs.vercel.app/docs/next-13)

<hr />

## Features

This library complements the [internationalized routing](https://nextjs.org/docs/advanced-features/i18n-routing) capabilities of Next.js by managing translations and providing them to components.
Internationalization is an essential part of the user experience. next-intl gives you everything you need to get language subtleties right and has always got your back whenever you need to fine-tune a translation.

- 🌟 **Proven ICU syntax**: This covers interpolation, plurals, ordinal pluralization, label selection based on enums and rich text. I18n is an essential part of the user experience, therefore this library doesn't compromise on flexibility and never leaves you behind when you need to fine tune a translation.
- 📅 **Built-in date, time and number formatting**: You can use global formats for a consistent look & feel of your app and integrate them with translations.
- 💡 **Hooks-only API**: This ensures that you can use the same API for `children` as well as for attributes which expect strings.
- **Type-safe**: If you're using TypeScript, you'll benefit from autocompletion for available message keys and compile-time errors for typos.
- ⚔️ **Battle-tested building blocks**: This library is a minimal wrapper around built-in browser APIs and supplemental lower-level APIs from Format.JS.
- 🚀 **Fast**: By integrating with both static as well as server side rendering you always get the best possible performance from your app.
- 🌟 **ICU message syntax**: Localize your messages with interpolation, plurals, ordinal pluralization, enum-based label selection, and rich text.
- 📅 **Dates, times & numbers**: Use global formats for a consistent look & feel of your app and apply fine-tuning as necessary.
- **Type-safe**: Speed up development with autocompletion for message keys and catch typos early with compile-time checks.
- 💡 **Hooks-only API**: Learn a single API that can be used across your code base to turn translations into plain strings or rich text.
- 🚀 **Fast**: Get the best performance from your app by supporting internationalization on both static and dynamic pages.
- ⚔️ **Standards-based**: Use the best parts of built-in JavaScript APIs and supplemental lower-level APIs from Format.JS.

## What does it look like?

This library is based on the premise that messages can be grouped by namespaces (typically a component name).

```jsx
// LatestFollower.js
function LatestFollower({user}) {
const t = useTranslations('LatestFollower');

// UserDetails.tsx
import {useTranslations, useFormatter} from 'next-intl';

function UserDetails({user}) {
const t = useTranslations('UserDetails');
const format = useFormatter();

return (
<>
<Text>{t('latestFollower', {username: user.name})}</Text>
<IconButton aria-label={t('followBack')} icon={<FollowIcon />} />
</>
<section>
<h2>{t('title')}</h2>
<p>{t('followers', {count: user.followers.length})}</p>
<p>{t('lastSeen', {time: format.relativeTime(user.lastSeen)})</p>
<Image alt={t('portrait', {username: user.name})} src={user.portrait} />
</section>
);
}
```
```js
// en.json
{
"LatestFollower": {
"latestFollower": "{username} started following you",
"followBack": "Follow back"
"UserDetails": {
"title": "User details",
"followers": "{count, plural, ↵
=0 {No followers yet} ↵
=1 {One follower} ↵
other {# followers} ↵
}",
"lastSeen": "Last seen {time}",
"portrait": "Portrait of {username}"
}
}
```
Expand Down
1 change: 0 additions & 1 deletion packages/example-advanced/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
},
"dependencies": {
"accept-language-parser": "1.5.0",
"date-fns": "^2.16.1",
"lodash": "^4.17.21",
"next": "^13.0.0",
"next-intl": "^2.10.4",
Expand Down
9 changes: 4 additions & 5 deletions packages/example-advanced/src/pages/about.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import {parseISO} from 'date-fns';
import pick from 'lodash/pick';
import {GetServerSidePropsContext} from 'next';
import {useIntl, useTranslations} from 'next-intl';
import {useFormatter, useTranslations} from 'next-intl';
import PageLayout from 'components/PageLayout';

export default function About() {
const t = useTranslations('About');
const intl = useIntl();
const lastUpdated = parseISO('2021-12-23T10:04:45.567Z');
const format = useFormatter();
const lastUpdated = new Date('2021-12-23T10:04:45.567Z');

return (
<PageLayout title={t('title')}>
<p>
{t('lastUpdated', {
lastUpdated,
lastUpdatedRelative: intl.formatRelativeTime(lastUpdated)
lastUpdatedRelative: format.relativeTime(lastUpdated)
})}
</p>
</PageLayout>
Expand Down
6 changes: 3 additions & 3 deletions packages/example-advanced/src/pages/api/hello.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import acceptLanguageParser from 'accept-language-parser';
import type {NextApiRequest, NextApiResponse} from 'next';
import {createIntl, createTranslator} from 'next-intl';
import {createFormatter, createTranslator} from 'next-intl';
import nextConfig from '../../../next.config';

// This file demonstrates how `next-intl` can
Expand All @@ -27,13 +27,13 @@ export default async function handler(
});

// Creates the same object that is returned by `useIntl`.
const intl = createIntl({locale});
const format = createFormatter({locale});

res.status(200).json({
locale,
key: 'Index.title',
message: t('title'),
date: intl.formatDateTime(new Date(), {dateStyle: 'medium'})
date: format.dateTime(new Date(), {dateStyle: 'medium'})
});
}

Expand Down
52 changes: 32 additions & 20 deletions packages/use-intl/README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,54 @@
# 🌐 use-intl

![Gzipped size](https://badgen.net/bundlephobia/minzip/use-intl) ![Tree shaking supported](https://badgen.net/bundlephobia/tree-shaking/use-intl) ![Build passing](https://img.shields.io/github/workflow/status/amannn/next-intl/main)
![Gzipped size](https://badgen.net/bundlephobia/minzip/use-intl) ![Tree shaking supported](https://badgen.net/bundlephobia/tree-shaking/use-intl) [<img src="https://img.shields.io/npm/dw/use-intl.svg" />](https://www.npmjs.com/package/use-intl)

> A minimal, but complete solution for managing translations, date, time and number formatting in React apps.
> Internationalization for React that gets out of your way.
## Features

- 🌟 **Proven [ICU syntax](https://formatjs.io/docs/core-concepts/icu-syntax)**: This covers interpolation, plurals, ordinal pluralization, label selection based on enums and rich text. I18n is an essential part of the user experience, therefore this library doesn't compromise on flexibility and never leaves you behind when you need to fine tune a translation.
- 📅 **Built-in date, time and number formatting**: You can use global formats for a consistent look & feel of your app and integrate them with translations.
- 💡 **Hooks-only API**: This ensures that you can use the same API for `children` as well as for attributes which expect strings.
-**Type-safe**: If you're using TypeScript, you'll benefit from autocompletion for available message keys and compile-time errors for typos.
- ⚔️ **Battle-tested building blocks**: This library is a minimal wrapper around built-in browser APIs and supplemental lower-level APIs from [Format.JS](https://formatjs.io/) (used by `react-intl`).
Internationalization is an essential part of the user experience. use-intl gives you everything you need to get language subtleties right and has always got your back whenever you need to fine-tune a translation.

- 🌟 **ICU message syntax**: Localize your messages with interpolation, plurals, ordinal pluralization, enum-based label selection and rich text.
- 📅 **Dates, times & numbers**: Use global formats for a consistent look & feel of your app and apply fine-tuning as necessary.
-**Type-safe**: Speed up development with autocompletion for message keys and catch typos early with compile-time checks.
- 💡 **Hooks-only API**: Learn a single API that can be used across your code base to turn translations into plain strings or rich text.
- ⚔️ **Standards-based**: Use the best parts of built-in JavaScript APIs and supplemental lower-level APIs from Format.JS.

## What does it look like?

This library is based on the premise that messages can be grouped by namespaces (typically a component name).

```jsx
// LatestFollower.js
import {useTranslations} from 'use-intl';

function LatestFollower({user}) {
const t = useTranslations('LatestFollower');

// UserDetails.tsx
import {useTranslations, useFormatter} from 'next-intl';

function UserDetails({user}) {
const t = useTranslations('UserDetails');
const format = useFormatter();

return (
<>
<Text>{t('latestFollower', {username: user.name})}</Text>
<IconButton aria-label={t('followBack')} icon={<FollowIcon />} />
</>
<section>
<h2>{t('title')}</h2>
<p>{t('followers', {count: user.followers.length})}</p>
<p>{t('lastSeen', {time: format.relativeTime(user.lastSeen)})</p>
<Image alt={t('portrait', {username: user.name})} src={user.portrait} />
</section>
);
}
```
```js
// en.json
{
"LatestFollower": {
"latestFollower": "{username} started following you",
"followBack": "Follow back"
"UserDetails": {
"title": "User details",
"followers": "{count, plural, ↵
=0 {No followers yet} ↵
=1 {One follower} ↵
other {# followers} ↵
}",
"lastSeen": "Last seen {time}",
"portrait": "Portrait of {username}"
}
}
```
Expand All @@ -46,6 +57,7 @@ function LatestFollower({user}) {
1. `npm install use-intl`
2. Add the provider
```jsx
import {IntlProvider} from 'use-intl';

Expand Down
1 change: 1 addition & 0 deletions packages/use-intl/src/core/createBaseTranslator.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-named-as-default
import IntlMessageFormat from 'intl-messageformat';
import {
cloneElement,
Expand Down
Loading

0 comments on commit 021b682

Please sign in to comment.