How to format numbers for different locales in Next.js (Pages Router) v16

Display numbers with locale-specific separators

Problem

Numbers are written differently around the world. What appears as 10,000.5 in the United States becomes 10.000,5 in Germany—commas and periods swap roles entirely. This is not a matter of preference or style, but of legibility. A German user seeing 10,000.5 might read it as ten, ignoring the grouping separators. An American user seeing 10.000,5 might read it as ten thousand, ignoring the decimal separator. The same digits, opposite meanings.

When applications display numbers without locale-aware formatting, they risk confusing users or conveying incorrect information. Currency amounts, percentages, measurements, and statistics all depend on regional conventions that users have learned since childhood.

Solution

Format numbers based on the user's locale, using regional rules for decimal and grouping separators. This turns numeric values into strings that follow the formatting rules familiar to users in their region.

react-intl provides two approaches: the <FormattedNumber> component for declarative formatting in JSX, and the formatNumber method from the useIntl hook for imperative formatting. Both rely on the browser's Intl.NumberFormat API and automatically apply the locale configured in your IntlProvider. The formatted output respects regional conventions for thousands separators, decimal points, and digit grouping.

Steps

1. Create a component that formats numbers declaratively

The <FormattedNumber> component uses the formatNumber and Intl.NumberFormat APIs and accepts a value prop along with options that correspond to Intl.NumberFormatOptions.

import { FormattedNumber } from "react-intl";

export default function ProductPrice({ price }: { price: number }) {
  return (
    <div>
      <FormattedNumber value={price} />
    </div>
  );
}

By default, <FormattedNumber> renders the formatted number into a React.Fragment. The component reads the locale from the nearest IntlProvider and applies the appropriate formatting rules.

2. Format numbers with specific options

Pass formatting options to control how numbers appear. Common options include minimumFractionDigits, maximumFractionDigits, and style.

import { FormattedNumber } from "react-intl";

export default function Statistics({ value }: { value: number }) {
  return (
    <dl>
      <dt>Total Users</dt>
      <dd>
        <FormattedNumber
          value={value}
          minimumFractionDigits={0}
          maximumFractionDigits={0}
        />
      </dd>
      <dt>Conversion Rate</dt>
      <dd>
        <FormattedNumber
          value={value / 100}
          style="percent"
          minimumFractionDigits={2}
        />
      </dd>
    </dl>
  );
}

These props correspond to Intl.NumberFormatOptions, giving you control over precision and presentation while maintaining locale-specific separators.

3. Format numbers imperatively with the useIntl hook

When a component can be expressed as a function component, the useIntl hook provides access to the intl object, which includes a formatNumber method.

import { useIntl } from "react-intl";

export default function DataTable({ rows }: { rows: number[] }) {
  const intl = useIntl();

  return (
    <table>
      <tbody>
        {rows.map((value, index) => (
          <tr key={index}>
            <td>{intl.formatNumber(value)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

The formatNumber function returns a formatted number string and accepts a value which can be parsed as a number, along with options that conform to NumberFormatOptions.

4. Format numbers in non-JSX contexts

Use formatNumber when you need formatted numbers in attributes, variables, or other contexts where components cannot be used.

import { useIntl } from "react-intl";

export default function Chart({ dataPoints }: { dataPoints: number[] }) {
  const intl = useIntl();

  const formattedLabel = intl.formatNumber(dataPoints[0], {
    notation: "compact",
    maximumFractionDigits: 1,
  });

  return (
    <div>
      <img
        src="/chart.png"
        alt={`Chart showing ${formattedLabel} items`}
        title={formattedLabel}
      />
    </div>
  );
}

The imperative API is essential for setting text attributes like title, aria-label, or alt, where React components cannot be rendered.