添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Skip to content

Rendering i18n messages

Prefer to watch a video?

Terminology

  • Locale : We use this term to describe an identifier that contains the language and formatting preferences of users. Apart from the language, a locale can include optional regional information (e.g. en-US ).
  • Messages : These are collections of namespace-label pairs that are grouped by locale (e.g. en-US.json ).
  • Structuring messages

    Messages are typically defined in JSON files:

    en.json
    {
      "About": {
        "title": "About us"
    

    You can render messages from within a React component with the useTranslations hook:

    About.tsx
    import {useTranslations} from 'next-intl';
    function About() {
      const t = useTranslations('About');
      return <h1>{t('title')}</h1>;
    

    To retrieve all available messages in a component, you can omit the namespace path:

    const t = useTranslations();
    t('About.title');

    Learn more:

    💡

    Translators can collaborate on messages by using a localization management solution like Crowdin.

    How can I provide more structure for messages?

    Optionally, you can structure your messages as nested objects.

    en.json
    {
      "auth": {
        "SignUp": {
          "title": "Sign up",
          "form": {
            "placeholder": "Please enter your name",
            "submit": "Submit"
    }
    SignUp.tsx
    import {useTranslations} from 'next-intl';
    function SignUp() {
      // Provide the lowest common denominator that contains
      // all messages this component needs to consume.
      const t = useTranslations('auth.SignUp');
      return (
          <h1>{t('title')}</h1>
            <input
              // The remaining hierarchy can be resolved by
              // using `.` to access nested messages.
              placeholder={t
    
    
    
    
        
    ('form.placeholder')}
            <button type="submit">{t('form.submit')}</button>
          </form>
    
    
    

    ICU messages

    next-intl uses ICU message syntax that allows you to express language nuances and separates state handling within messages from your app code.

    Static messages

    Static messages will be used as-is:

    en.json
    "message": "Hello world!"
    t('message'); // "Hello world!"

    Interpolation of arguments

    Dynamic values can be inserted into messages by using curly braces:

    en.json
    "message": "Hello {name}!"
    t('message', {name: 'Jane'}); // "Hello Jane!"

    Cardinal pluralization

    To express the pluralization of a given number of items, the plural argument can be used:

    en.json
    "message": "You have {count, plural, =0 {no followers yet} =1 {one follower} other {# followers}}."
    t('message', {count: 3580}); // "You have 3,580 followers."

    Note that by using the # marker, the value will be formatted as a number.

    Ordinal pluralization

    To apply pluralization based on an order of items, the selectordinal argument can be used:

    en.json
    "message": "It's your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!"
    Which plural forms are supported?

    Depending on the language, different forms of ordinal pluralization are supported.

    For example, English has four forms: “th”, “st”, “nd” and “rd” (e.g. 1st, 2nd, 3rd, 4th … 11th, 12th, 13th … 21st, 22nd, 23rd, 24th, etc.). In contrast, both Chinese and Arabic use only one form for ordinal numbers.

    next-intl uses Intl.PluralRules to detect the correct tag that should be used for a given number.

    These tags are supported:

    • zero: For languages with zero-item grammar (e.g., Latvian, Welsh).
    • one: For languages with singular-item grammar (e.g., English, German).
    • two: For languages with dual-item grammar (e.g., Arabic, Welsh).
    • few: For languages with grammar specific to a small number of items (e.g., Arabic, Polish, Croatian).
    • many: For languages with grammar specific to a larger number of items (e.g., Arabic, Polish, Croatian).
    • other: Used when the value doesn’t match other plural categories.

    The exact range that few and many applies to varies depending on the locale (see the language plural rules table in the Unicode CLDR).

    To match a specific number, next-intl additionally supports the special =value syntax (e.g. =3) that always takes precedence.

    Selecting enum-based values

    To map identifiers to human readable labels, you can use the select argument that works similar to the switch statement in JavaScript:

    en.json
    "message": "{gender, select, female {She} male {He} other {They}} is online."
    t('message', {gender: 'female'}); // "She is online."

    Note: The other case is required and will be used when none of the specific values match.

    Escaping

    Since curly braces are used for interpolating arguments, you can escape them with the ' marker to use the actual symbol in messages:

    en.json
    "message": "Escape curly braces with single quotes (e.g. '{name'})"
    t('message'); // "Escape curly braces with single quotes (e.g. {name})"

    Rich text

    You can format rich text with custom tags and map them to React components via t.rich:

    en.json
    {
      "message": "Please refer to <guidelines>the guidelines</guidelines>."
    
    // Returns `<>Please refer to <a href="/guidelines">the guidelines</a>.</>`
    t.rich('message', {
      guidelines: (chunks) => <a href="/guidelines">{chunks}</a>
    

    Tags can be arbitrarily nested (e.g. This is <important><very>very</very> important</important>).

    How can I reuse tags across my app?

    Common tags for rich text that you want to share across your app can be defined in a shared module and imported where relevant for usage with t.rich.

    A convenient pattern is to use a component that provides common tags via a render prop:

    import {useTranslations} from 'next-intl';
    import RichText from '@/components/RichText';
    function AboutPage() {
      const t = useTranslations('AboutPage');
      return <RichText>{(tags) => t.rich('description', tags)}</RichText>;
    }

    In this case, the RichText component can provide styled tags and also a general layout for the text:

    components/RichText.tsx
    import
    
    
    
    
        
     {ReactNode} from 'react';
    // These tags are available
    type Tag = 'p' | 'b' | 'i';
    type Props = {
      children(tags: Record<Tag, (chunks: ReactNode) => ReactNode>): ReactNode
    export default function RichText({children}: Props) {
      return (
        <div className="prose">
          {children({
            p: (chunks: ReactNode) => <p>{chunks}</p>,
            b: (chunks: ReactNode) => <b className="font-semibold">{chunks}</b>,
            i: (chunks: ReactNode) => <i className="italic">{chunks}</i>
        </div>
    }

    If you need to combine shared tags with values from your component, you can merge them accordingly by using the spread operator:

    function UserPage({username}) {
      const t = useTranslations('UserPage');
      return (
        <RichText>{(tags) => t.rich('description', {...tags, username})}</RichText>
    
    How can I use “self-closing” tags without any chunks?

    Messages can use tags without any chunks as children, but syntax-wise, a closing tag is required by the ICU parser:

    en.json
    {
      "message": "Hello,<br></br>how are you?"
    }
    t.rich('message', {
      br: () => <br />
    
    

    HTML markup

    To render rich text, you typically want to use rich text formatting. However, if you have a use case where you need to emit raw HTML markup, you can use the t.markup function:

    en.json
    {
      "markup": "This is <important>important</important>"
    
    // Returns 'This is <b>important</b>'
    t.
    
    
    
    
        
    markup('markup', {
      important: (chunks) => `<b>${chunks}</b>`
    

    Note that unlike t.rich, the provided markup functions accept chunks as a string and also return a string where the chunks are wrapped accordingly.

    Raw messages

    Messages are always parsed and therefore e.g. for rich text formatting you need to supply the necessary tags. If you want to avoid the parsing, e.g. because you have raw HTML stored in a message, there’s a separate API for this use case:

    en.json
    {
      "content": "<h1>Headline</h1><p>This is raw HTML</p>"
    
    <div dangerouslySetInnerHTML={{__html: t.raw('content')}} />

    The value of a raw message can be any valid JSON value: strings, booleans, objects and arrays.

    Optional messages

    If you have messages that are only available for certain locales, you can use the t.has function to check whether a message is available for the current locale:

    const t = useTranslations('About');
    t.has('title'); // true
    t.has('unknown'); // false

    Note that separately from this, you can also provide fallback messages, e.g. from the default locale, in case you have incomplete messages for certain locales.

    Arrays of messages

    If you need to render a list of messages, the recommended approach is to map message keys to an array within your React component:

    en.json
    {
      "CompanyStats": {
        "yearsOfService": {
          "title": "Years of service",
          "value": "34"
        "happyClients": {
          "title": "Happy clients",
          "value": "1.000+"
        "partners": {
          "title": "Products",
          "value": "5.000+"
    
    CompanyStats.tsx
    import {useTranslations} from 'next-intl';
    function
    
    
    
    
        
     CompanyStats() {
      const t = useTranslations('CompanyStats');
      const items = [
          title: t('yearsOfService.title'),
          value: t('yearsOfService.value')
          title: t('happyClients.title'),
          value: t('happyClients.value')
          title: t('partners.title'),
          value: t('partners.value')
      return (
          {items.map((item, index) => (
            <li key={index}>
              <h2>{item.title}</h2>
              <p>{item.value}</p>
    

    This approach ensures that you can use ICU features while also enabling static validation of messages.

    What if the amount of items varies depending on the locale?

    To dynamically iterate over all keys of a namespace, you can use the useMessages hook to retrieve all messages of a given namespace and extract the keys from there:

    CompanyStats.tsx
    import {useTranslations, useMessages} from 'next-intl';
    function CompanyStats() {
      const t = useTranslations('CompanyStats');
      const messages = useMessages();
      const keys = Object.keys(messages.CompanyStats);
      return (
          {keys.map((key) => (
            <li key={key}>
              <h2>{t(`${key}.title`)}</h2>
              <p>{t(`${key}.value`)}</p>
    

    Right-to-left languages

    Languages such as Arabic, Hebrew and Persian use right-to-left script (often abbreviated as RTL). For these languages, writing begins on the right side of the page and continues to the left.

    Example:

    النص في اللغة العربية _مثلا_ يُقرأ من اليمين لليسار

    In addition to providing translated messages, proper RTL localization requires: