Skip to content

Commit

Permalink
docs: Design principles (#961)
Browse files Browse the repository at this point in the history
Inspired by [the StyleX Core
Principles](https://stylexjs.com/docs/learn/thinking-in-stylex/).
  • Loading branch information
amannn authored Apr 3, 2024
1 parent 5db8a1d commit e04c75d
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 57 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

## Features

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.
Internationalization (i18n) is an essential part of the user experience, therefore `next-intl` gives you all the parts you need to get language nuances right.

- 🌟 **ICU message syntax**: Localize your messages with interpolation, cardinal & ordinal plurals, enum-based label selection and rich text.
- 📅 **Dates, times & numbers**: Apply appropriate formatting without worrying about server/client differences like time zones.
Expand All @@ -32,8 +32,6 @@ Internationalization is an essential part of the user experience. `next-intl` gi

## What does it look like?

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

```jsx
// UserProfile.tsx
import {useTranslations} from 'next-intl';
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/docs/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"environments": "Environments",
"routing": "Routing",
"workflows": "Workflows & integrations",
"faq": "FAQ"
"design-principles": "Design principles"
}
119 changes: 119 additions & 0 deletions docs/pages/docs/design-principles.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import PartnerContentLink from 'components/PartnerContentLink';

# Design principles

This page provides a deep dive about the design principles that `next-intl` is based on. These principles are meant to give you a better understanding of the philosophy behind `next-intl` and can help you evaluate if this library is a good fit for your project.

This page also links to planned improvements in order to act as a transparent reference for enhancements to expect from `next-intl` in the future.

## Holistic

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.
4. **Localized URLs**: URLs should be localized to match the user's language preference (e.g. `/en/about-us` for English and `/es/sobre-nosotros` for Spanish). Implementation-wise this requires mapping incoming requests to the right pages and providing streamlined APIs for developers to link between pages in a locale-agnostic way.
5. **Language negotiation**: The user's language preference should be detected based on browser settings, but should also be manually configurable—even if a user hasn't signed in.
6. **SEO**: Search engines need to be informed about [localized versions of your pages](https://developers.google.com/search/docs/specialty/international/localized-versions) in order to present the best-matching content to your users.
7. **Country-specifics**: If you provide services or products in different countries, you might need to consider country-specifics like different currencies.

Considering this list, it might be apparent how large the problem space is and how interconnected different pieces of internationalization are.

`next-intl` takes a holistic approach to internationalization that nudges you towards best practices for handling languages and country-specific conventions. This way, you have more time to focus on what makes your app unique.

[Planned enhancements](https://github.com/amannn/next-intl/labels/area%3A%20routing)

## Ergonomic

The bold claim of this library is that your app code will become simpler instead of more complex when you implement internationalization.

If you consider [the holistic picture](/docs/design-principles#holistic) of what internationalization entails, it's clear that a majority of your components will be involved with internationalization in one way or another. Due to this, `next-intl` makes it a priority to provide convenient APIs that ensure you feel productive and in control. As a developer, you should be able to focus solely on solving practical problems, with internationalization seamlessly integrated as a side effect of your efforts.

Once internationalization is set up, adding a new language is in the simplest case only a matter of adding a new JSON file with translations—everything else is being taken care of by `next-intl`.

[Planned enhancements](https://github.com/amannn/next-intl/labels/area%3A%20ergonomics)

## Standards-based

With the introduction of [the ECMAScript Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl), JavaScript has gotten very capable in the recent years in regard to formatting of dates, times, numbers, lists and pluralization. `next-intl` builds on top of this API and provides an ergonomic interface to work with these features, while considering your app-specic configuration & needs.

For text formatting, `next-intl` is based on [International Components for Unicode (ICU)](https://unicode-org.github.io/icu/userguide/format_parse/). ICU is a mature and widely used standard for internationalization that is supported by many programming languages and frameworks. `next-intl` uses the ICU message syntax for defining text labels, which allows to express complex formatting requirements like interpolating variables and pluralization in a concise and readable way—also for non-developers like translators.

By being based on standards, `next-intl` ensures that your internationalization code is future-proof and feels familiar to developers who have exisiting experience with internationalization. Additionally, relying on standards ensures that `next-intl` integrates well with translation management systems like <PartnerContentLink href="https://crowdin.com/">Crowdin</PartnerContentLink>.

`next-intl` uses a [nested style](/docs/usage/messages#structuring-messages) to provide structure to messages, allowing to express hierarchies of messages without redundancy. By supporting only a single style, we can offer advanced features that rely on these assumptions like [type-safety for messages](/docs/workflows/typescript). If you're coming from a different style, you can consider migrating to the nested style (see "Can I use a different style for structuring my messages?" in [the structuring messages docs](/docs/usage/messages#structuring-messages)).

As standards can change, `next-intl` is expected to keep up with the latest developments in the ECMAScript standard (e.g. [`Temporal`](https://tc39.es/proposal-temporal/docs/) and [`Intl.MessageFormat`](https://github.com/tc39/proposal-intl-messageformat)).

[Planned enhancements](https://github.com/amannn/next-intl/labels/area%3A%20standards)

## Compatible

While `next-intl` is designed to tackle internationalization in [a holistic way](#holistic), it's important to consider that internationalization might require integration with other tools and services, enabling you to use the best tools for the job and to collaborate with non-developers.

Typical apps require some of the following integrations:

**Translation Management Systems (TMS)**

These are typically used to manage translations and to [collaborate with translators](/docs/workflows/localization-management). Services like <PartnerContentLink href="https://crowdin.com/">Crowdin</PartnerContentLink> provide a wide range of features, allowing translators to work in a web-based interface on translations, while providing different mechanisms to sync translations with your app.

`next-intl` integrates well with these services as it uses ICU message syntax for defining text labels, which is a widely supported standard. The recommended way to store messages is in JSON files that are structured by locale since this is a popular format that can be imported into a TMS. While it's recommended to have at least the messages for the default locale available locally (e.g. for [type-safe messages](/docs/workflows/typescript)), you can also load messages dynamically, e.g. from a CDN that your TMS provides.

**Content Management Systems (CMS)**

If your app uses content from a CMS, you can use this alongside `next-intl`. For instance, you might want to manage content like blog posts or marketing copy in a CMS, while managing UI labels in `next-intl`.

A CMS typically provides a way to define content for multiple languages, and to fetch this content via a REST API. By using the negotiated app locale that is returned from `next-intl` via [`getLocale`](/docs/environments/server-client-components#async-components) and passing it to requests to your CMS API, you can retrieve content for the user's preferred language.

**Backend data**

Similar to a CMS, you might have data in a backend service or database that needs to be queried based on the language and country of the user. For instance, you might want to show different prices & currencies for different countries, or you might want to show localized product names. By using the negotiated app locale from `next-intl` for requests to your backend, you can ensure localization reaches all parts of your app.

**Other libraries**

Next.js has a rich ecosystem of libraries that can be used alongside `next-intl`, e.g. to handle authentication. By providing [documentation and examples for common integrations](/docs/routing/middleware#composing-other-middlewares), `next-intl` ensures that you'll have a frictionless experience when integrating with other libraries.

[Planned enhancements](https://github.com/amannn/next-intl/labels/area%3A%20integrations)

## Performance-obsessed

`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.
4. **RSC-first**: By integrating deeply with React Server Components, we can offload work to a build step or a capable server, therefore reducing the runtime footprint of your app on the client side.

[Planned enhancements](https://github.com/amannn/next-intl/labels/area%3A%20performance)

## Next.js-first

Next.js comes with a lot of bells and whistles, but at the same time there are a number of aspects that need to be handled carefully to enable a reliable internationalization integration. `next-intl`, as the name implies, is primarly designed to work well with Next.js. Rather than trying to be a one-size-fits-all solution, `next-intl` integrates with Next.js as deeply as necessary and makes it a priority to stay on top of the latest developments in the Next.js ecosystem.

That being said, `next-intl` has a Next.js-agnostic core that can be used in any React app, or even in plain JavaScript: [`use-intl`](/docs/environments/core-library). This core library contains most features of `next-intl`, but lacks Next.js-specific integrations like routing APIs. The goal of this library is to make it possibly to use familiar APIs in other parts of your stack (e.g. React Native) and to provide a straightforward way to [migrate away from Next.js](#migration-friendly), in case you ever decide to do so.

## Migration-friendly

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).

We wont hold you back, but if you like, we can stay friends.

That being said, we're doing our best to make `next-intl` a great fit for your project. If you ever feel like something is not working as expected or if you have ideas for improvements, please let us know on the [issue tracker](https://github.com/amannn/next-intl/issues) and we'll do our best to help you out.

---

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)!
73 changes: 65 additions & 8 deletions docs/pages/docs/environments/core-library.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,72 @@
# Core library

While `next-intl` is primarily intended to be used within React components, the core is agnostic and can be used independently of React.
While `next-intl` is primarily intended to be used in Next.js apps, the core is agnostic and can be used independently either in React apps or any other JavaScript environment.

## React apps

`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)
- [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:

1. React Apps ([example](/examples#use-intl))
2. React Native ([example](/examples#react-native))
3. Remix ([example](/examples#remix))
4. Testing environments like Jest ([example](https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/components/Navigation.spec.tsx))
5. Storybook (by using a [global decorator](https://storybook.js.org/docs/writing-stories/decorators#global-decorators))

**Basic usage:**

```tsx
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
// along with your app once you have the messages.
const messages = {
"App": {
"hello": 'Hello {username}!'
}
};

function Root() {
return (
<IntlProvider messages={messages} locale="en">
<App user={{name: 'Jane'}} />
</IntlProvider>
);
}

function App({user}) {
const t = useTranslations('App');
return <h1>{t('hello', {username: user.name})}</h1>;
}
```

## Non-React apps

Besides the React-specific APIs, `use-intl` also exports two low-level functions that can be used in any JavaScript environment:

1. `createTranslator` for translating messages
2. `createFormatter` for formatting numbers, dates & times and lists

These APIs receive all relevant configuration directly and don't rely on global configuration.

You can use these APIs as follows:

```tsx
import {createTranslator} from 'next-intl';
import {createTranslator, createFormatter} from 'use-intl';

const messages = {
basic: 'Hello {name}!',
Expand All @@ -24,12 +87,6 @@ t.markup('rich', {
name: 'world',
b: (chunks) => `<b>${chunks}</b>`
});
```

For date, time and number formatting, the `format` object can be created outside of React as well:

```js
import {createFormatter} from 'next-intl';

// Creates the same object that is returned by `useFormatter`.
const format = createFormatter({locale: 'en'});
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/docs/environments/server-client-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ export default function Counter() {
}
```

If you only have a few large messages that you'd like to exclude from the client side, you can also use `lodash/omit` instead of `lodash/pick`.

<details>
<summary>How can I know the messages I need to provide to the client side?</summary>

Expand Down
Loading

0 comments on commit e04c75d

Please sign in to comment.