Replies: 1 comment 3 replies
-
Hey @debel27, thanks for taking the time to write up this RFC! On a first look, it seems like this use case is already supported to some extend, see e.g.: #1575 (comment). Separating data from translations is quite common and good practice IMO. E.g. in a codebase I'm currently working on I have the rule that any "service" works purely on data and translations must only be applied in components (with Server Actions that are defined along with components also counting as part of that). E.g. Regarding your examples, it seems like these can already be realized currently: const topBarEntries = [
{
id: "profile",
link: "/profile",
},
{
id: "settings",
link: "/settings"
}
];
function TopBar() {
const t = useTranslations();
return (
<div className="top-bar">
{topBarEntries.map(entry => {
const {id, link} = entry;
return <a key={id} href={link}>{t(`topBarItem.${id}`)}</a>
})}
</div>
);
} import { useTranslations } from 'next-intl';
function Component() {
const t = useTranslations();
return (
<Input label={t("field.firstName")} />
);
} (Benefit: Any label can be accepted, translated or not) So as things stand currently, I have to say that don't really see a large benefit of introducing this. Does that make sense to you? I tend to be quite conservative with APIs that are added to |
Beta Was this translation helpful? Give feedback.
-
Introduction
next-intl promotes translating messages within React components. This is a great design decision, because messages depend on reactive data that can change across renders (such as the user locale).
However, like many internationalization libraries, next-intl expects developers to identify the message to translate directly within the call site of
t
. As explained later, this approach results in usability issues.I would like to introduce the concept "message references" to next-intl, which gives the ability to identify messages without immediately translating them.
Motivations
Giving the ability to reference a message separately from the translation operation introduces a useful decoupling, improving flexibility and ergonomics:
t
function is not available.useTranslations
every time they need to translate something. They can simply declare a message reference and pass it to translation-aware components/hooks, which will deal with the actual translation operation.Those benefits are particularly appreciated in data-driven codebases, as the upcoming examples will illustrate.
Before getting to the examples, I will formalize the proposal a bit.
Proposal
Basic principle
next-intl will introduce an
IntlMessage
type, defined as follows:The signature of the
t
function will be updated to acceptIntlMessage
as an argument.Here is a simple example to illustrate the idea:
Creating
IntlMessage
objectsDevelopers will not create
IntlMessage
objects directly. They will do it with the help of a utilityi18n
function.i18n
accept the exact same parameters ast
:i18n
is not to be confused witht
!i18n
can be called outside React components and does not translate anything. It only allows declaring a reference to a message, to be translated later byt
.i18n
makes declaring references easier and will play nicer with TypeScript (as shown later). To make sure developers use it, we can makeIntlMessage
branded:TypeScript augmentation
i18n
accepts the exact same parameters ast
. Therefore, it can benefit from the same TypeScript APIs, includng the strict typing of ICU arguments. For this reason, I'm assuming type safety will be trivial to implement.Examples
These examples illustrate the possibilities unlocked by the new
IntlMessage
type.Static references
IntlMessage
allows developers to declare messages references statically, outside React space. This gives more flexibility to organize code.topBarEntries.ts
topBar.ts
I18n-first components
Now that message references are first-class citizen of the next-intl package, they can be used to improve developer experience in application space, since they allow writing components with built-in I18n support.
For instance, consider we developed a wrapper on top of
<input/>
to support label internationalization:Our
Input
has an API that improves developer experience, because callers of that component no longer need to calluseTranslations
to pass down a translated label.Given
i18n
can be auto-imported by the IDE, there is less ceremony involved when dealing with internationalization.Additional utilities
We can leverage the
IntlMessage
ecosystem by introducing the following utility functionsi18nIdentity
If an API only accepts an
IntlMessage
as input, the developer needs a workaround when they want to display some text "as is" instead of an internationalized message.The
i18nIdentity
function can be for such case:The utility relies on a built-in message
__identity: "{data}"
, inserted automatically by next-intl.Example:
i18nIdentity("Some text coming from the server")
isIntlMessage
Convenience function to determine if an object is an
IntlMessage
This can be particularly handy for I18n-first components:
Beta Was this translation helpful? Give feedback.
All reactions