Skip to content

Public value types for core and react are too loose #2508

@yslpn

Description

@yslpn

Some public types in the translation API are much broader than the runtime behavior they describe:

// core
export type Values = Record<string, unknown>

// react
values?: Record<string, unknown>
components?: { [key: string]: React.ElementType | any }

At the type level, this means "almost anything is allowed". In practice, that is not true.

For example, this looks valid:

<Trans id="message.id" values={{ someDate: new Date() }} />

TypeScript accepts it because Date fits into unknown. But that does not mean the value is safe for the React layer. A Date object is not a valid React child, so this kind of input can still fail at runtime depending on how Trans handles it.

In practice, the failure looks like this:

Objects are not valid as a React child (found: [object Date]).
If you meant to render a collection of children, use an array instead.

The confusing part is that new Date() is a reasonable value in core, where date-like values can be passed into message formatting. On the React side, though, there is an explicit negative test for passing Date through <Trans values={...}>. So the same value is acceptable in one place and rejected in another, while both surfaces still expose very loose public types.

That makes the API hard to understand. The real contract is not obvious from the types. You have to infer it from implementation details, runtime behavior, and scattered tests.

It also makes these areas hard to refactor. When the public types allow almost anything, it is hard to answer simple questions with confidence:

  • Which values are meant for message formatting?
  • Which values are meant to become React children?
  • Which values should be rejected at compile time?

Without clear answers, tightening the implementation becomes risky. It is too easy to break a case that was intentionally supported, or to preserve a case that only happened to work by accident.

Runtime checks are not enough here. They can tell us what happens today, but they do not clearly define what should be supported. The types need to carry more of that meaning, especially where core and react intentionally differ.

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