Skip to content

Custom formatters for numbers, dates etc.Β #2279

@DeyLak

Description

@DeyLak

Problem Description

I'm migrating my current project with no i18n library to Lingui and I've faced an issue, that I can't use a custom format for my numbers inside a message. I'm already relying on Intl APIs, but I have some custom functions with additional logic (like special handling of NaNs or some tweaks using DateTimeFormat.formatToParts) and tunned to my use cases.

Now I can't use them as custom formatters.

Proposed Solution

I want to have an opportunity to pass a custom format function into my macros calls (or even config it globally for the i18n instance).

I think, that can look smth like this in the code (modified docs example):

import { plural } from "@lingui/core/macro";
import { memoizedNumberFormat } from '@lingui/formatters'

const message = plural(numBooks, {
  one: plural(numArticles, {
    one: `1 book and 1 article`,
    other: `1 book and ${numArticles} articles`,
  }),
  other: plural(numArticles, {
    one: `${numBooks} books and 1 article`,
    other: `${numBooks} books and ${numArticles} articles`,
  }),
}, {
  format: {
    numBooks: num => num.toString(),
    numArticles: memoizedNumberFormat,
  }
  // or like this for simple cases
  // format: memoizedNumberFormat,
});

// ↓ ↓ ↓ ↓ ↓ ↓
// Generated message was wrapped for better readability

import { i18n } from "@lingui/core";
const message = i18n._(
  /*i18n*/ {
    id: "XnUh4j",
    message: `{numBooks, plural,
         one {{numArticles, plural,
            one {1 book and 1 article}
            other {1 book and {numArticles} articles}
         }}
         other {{numArticles, plural,
            one {{numBooks} books and 1 article}
            other {{numBooks} books and {numArticles} articles}
         }}
      }`,
    values: { numBooks, numArticles },
    format: {   
      numBooks: num => num.toString(),
      numArticles: memoizedNumberFormat,
    }
  }
);

Also may be add some default configuration options for i18n instance, like:

import { setupI18n } from "@lingui/core";
import { memoizedNumberFormat, memoizedDateFormat } from '@lingui/formatters'

const i18n = setupI18n({
  numberFormatter: memoizedNumberFormat,
  dateFormatter: memoizedDateFormat,
});

Alternatives Considered

For now, i've ended up providing 2 variables to my message instead of 1. It works, but that adds overhead for both translators and developers.

const formattedValue = formatInt(defaultValue);
const feedback = t`Default value: ${plural(defaultValue, {
        one: `${formattedValue} day`,
        other: `${formattedValue} days`,
      })}`

I've also though of how can I change my existing codebase to match the library formatting, but that way I don't see a way to handle my additional formatting logic and although Intl methods are the best we ever had for the dates and numbers localisation, they're still not always flexible enough to satisfy the design requirements.

Additional Context

I saw this issue #2265 about making intl formatters deprecated and I agree with the idea there, but I think it can't be done without a more general approach to how values formatting is handled inside the library.

Both i18n.number method and plural macro should have similar api and configuration opportunities. That way, we maybe can move the current Intl formatters implementations into a separate tree-shakable package for those, who need an easier adoption path.

I'm not sure, if I'm already have a good enough codebase knowledge for such a significant change. But we can discuss possible solutions and I can try making a PR for that with some assistance, if you agree with the idea itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions