next-intl

repository·main·Indexed 26 days ago

https://github.com/amannn/next-intl

A specialized internationalization library for Next.js providing type-safe, high-performance translation capabilities. It supports ICU message syntax for interpolation, date formatting, and pluralization, and includes features for internationalized routing. The library is compatible with both the Next.js App Router and Pages Router, and is powered by the core use-intl library.

Tokens
50.7K
Snippets
174
Records
243
Agent score
87%

What's inside next-intl

  1. Overview of next-intl features

    main

    next-intl is a library for internationalization (i18n) in Next.js applications. It provides a minimalistic, hooks-based API designed to work seamlessly with Next.js App Router, Server Components, and static rendering.

    Key Features:

    • ICU Message Syntax: Supports complex localization including interpolation, cardinal & ordinal plurals, enum-based label selection, and rich text.
    • Formatting: Built-in support for formatting dates, times, numbers, and more, handling server/client differences like time zones automatically.
    • Type-safety: Provides autocompletion for message keys and compile-time checks to catch typos.
    • Hooks-based API: A unified API to transform translations into plain strings or rich text across your codebase.
    • Next.js Integration: Optimized for performance and native support for App Router and Server Components.
    • Internationalized Routing: Enables unique pathnames per language and supports localized pathnames for SEO.
  2. Understand the design principles of next-intl

    main

    The next-intl library is built on several core philosophies designed to make internationalization (i18n) seamless and efficient in Next.js applications:

    • Holistic: Addresses the full spectrum of i18n, including pluralization, date/time/number/list formatting, text direction (RTL/LTR), rich text, time zones, localized URLs, and SEO.
    • Ergonomic: Aims to simplify app code. Once configured, adding a new language typically only requires adding a new JSON translation file.
    • Standards-based: Built on the ECMAScript Internationalization API (Intl) and uses ICU (International Components for Unicode) message syntax for text formatting. It uses a nested style for message structuring to enable advanced features like TypeScript type-safety.
    • Compatible: Designed to work with Translation Management Systems (TMS) like Crowdin (via ICU and JSON), Content Management Systems (CMS), and backend services by providing the negotiated locale via getLocale.
    • Performance-obsessed: Optimized for high-traffic sites using React Server Components (RSC) to reduce client-side footprint, message splitting to minimize payload size, and caching for message parsing and Intl constructors.
  3. Get started with next-intl for Next.js

    main

    next-intl is an internationalization toolkit for Next.js designed to provide a foundation for building localized applications. It enables you to:

    1. Render localized translations: Display text in different languages.
    2. Format data: Handle localized formatting for dates, numbers, and other types.
    3. Handle internationalized routing: Manage URL structures that include locale information.

    Depending on your Next.js architecture, follow the specific setup guide for either the App Router or the Pages Router.

  4. Understand message extraction implementation details

    main

    The message extraction system uses autogenerated, non-descriptive keys to identify messages. To provide context for translators (especially for AI translation), the system supports different catalog formats.

    Key implementation details include:

    • Key Generation: Keys are generated by hashing the message content using SHA-512 and taking the first 6 characters of a URL-safe Base64 string. File paths and names are not included in the hash to prevent key invalidation when files are moved.
    • Catalog Formats:
      • Portable object catalog (.po): The preferred default. It allows attaching contextual descriptions and automatically includes file paths/locations.
      • Structured JSON: Uses a format similar to chrome.i18n (e.g., {"key": {"description": "...", "message": "..."}}).
      • Simple JSON: The standard {"key": "message"} format, which does not support contextual descriptions.
    • Bundler Integration: Extraction is designed to work with a running dev server via a Turbopack plugin. The plugin analyzes code, extracts messages, and transforms source files to use generated keys. A Turbopack loader then transforms catalogs into simple JSON for use in i18n/request.ts behind the scenes.
  5. Features of icu-minify

    main

    The icu-minify library provides:

    • Build-time compilation: Converts ICU messages to a compact JSON intermediate representation.
    • Minimal runtime: A 650 bytes (minified + compressed) runtime with zero dependencies.
    • Full ICU support: Handles {arguments}, plural, select, selectordinal, date, time, number, and <tags>.
  6. Benefits of server-side internationalization

    main

    Moving internationalization to the server side via React Server Components offers several advantages:

    1. Reduced Payload: Messages never leave the server and don't need to be passed to the client.
    2. Smaller Client Bundles: Library code for internationalization doesn't need to be loaded on the client side.
    3. Simplified Management: No need to split messages based on routes or components.
    4. Performance: Zero runtime cost for internationalization on the client side.
  7. Understand the design rationale for message extraction in next-intl

    main

    The next-intl message extraction strategy is designed to favor statically analyzable, plain strings over direct string concatenation or interpolation. This approach ensures that the library can accurately identify which messages are used across the module graph, support ICU features (like date and number formatting) via TypeScript analysis, and provide type-safety for arguments.

    Why direct concatenation is avoided

    Using patterns like t("Hello ${name}!") is discouraged because:

    • Rich Text Complexity: Concatenating strings with JSX elements (e.g., t("This is " + <b>{userName}</b> + ".")) is not supported and becomes syntactically difficult.
    • Static Analysis Failure: Interpolated strings make it difficult for extractors to guess variable names or identify ICU formatters (like number or date) used within the message.
    • Type Safety: Plain strings allow next-intl to validate that required arguments are passed, preventing undefined values from breaking translations.
    • Module Graph Tracking: Defining messages via macros (e.g., const msg = msg"Hello {name}") prevents the library from statically analyzing which modules depend on which messages.
  8. Configure message extraction for monorepos and external packages

    main

    When using external modules or sibling packages in a monorepo, you have two configuration strategies:

    1. Extract messages into the main app

    If you want your main Next.js app to own all messages, configure srcPath in createNextIntlPlugin to include the source directories of your external packages. This will extract their messages into your app's message directory.

    2. Ship messages with the external package

    If your shared package is used by multiple apps, follow these steps:

    1. Extract messages during the package build: Use unstable_extractMessages from next-intl/extractor in your package's build process.
    2. Configure the consuming app:
      • Set extract.path to only extract first-party messages.
      • Include the external package's messages in the messages.path array.
      • Use Next.js transpilePackages to ensure useExtracted is compiled to useTranslations in the external package.
      • Merge the messages in getRequestConfig (e.g., in i18n/request.ts).
    // Strategy 1: Include external paths in srcPath
    const withNextIntl = createNextIntlPlugin({
      experimental: {
        extract: true,
        messages: {
          path: './messages',
          format: 'json',
          locales: 'infer',
          sourceLocale: 'en'
        },
        srcPath: [
          './src',
          '../ui/src',
          './node_modules/@acme/components'
        ]
      }
    });
    
    // Strategy 2: Consuming app configuration
    // next.config.ts
    const withNextIntl = createNextIntlPlugin({
      experimental: {
        extract: {
          path: './messages'
        },
        messages: {
          path: [
            './messages',
            '../ui/messages',
            './node_modules/@acme/components/messages'
          ],
          format: 'po',
          locales: 'infer',
          sourceLocale: 'en'
        },
        srcPath: './src'
      }
    });
    
    const nextConfig = {
      transpilePackages: ['@acme/ui', '@acme/components']
    };
    
    // i18n/request.ts
    const messages = {
      ...(await import(`@acme/ui/messages/${locale}.po`)).default,
      ...(await import(`@acme/components/messages/${locale}.po`)).default,
      ...(await import(`../../messages/${locale}.po`)).default
    };