Planned changes to the FluentBundle API

I was traveling last week and it was a good opportunity to take a step back and think about the larger picture. I’ve also had a chance to talk to @Pike and @zbraniecki in person about this topic; thanks for offering your feedback.

tl;dr: I’m proposing another alternative of the API design. It’s almost the same as the current API, and based on the realization that hiding the runtime representation of message is no longer a relevant goal.

The goal of the API re-design has been to hide the internal representation of messages as stored in a FluentBundle. But that already sounds like a solution, rather than the problem to solve. The actual problem is that for me, as the maintainer of fluent.js, the way FluentBundle stores messages is an implementation detail. However that’s not clear to the consumers of the API. We’ve already seen people create tools and scripts which run on the runtime AST.

As an implementation detail, I’d like to be able to change the internal representation of messages when working on the runtime parser and the runtime resolver. The representation is not intended to be human readable; it’s a derivative of the parser/resolver duo.

So far, I’ve presented two alternatives to achieve this goal:

  • Introduce FluentBundle.compound(id). Issue #208, PR #360.
  • Store messages as FluentMessage in the runtime. Issue #365, PR #366.

They both offer different approaches to hiding the internal representation from consumers, and they both come with different trade-offs.

Last week I started to think that perhaps rather than looking for a technical solution, it would be worthwhile to reconsider the original goal and intent. Do we actually gain that much by hiding the internal representation? And what happens if we don’t?

A hidden internal representation makes it possible to iterate on the parser/resolver code faster. This was a reasonable goal a year ago, when I first proposed changing the API. The syntax spec was still in flux, and Syntax 1.0 was still many moths away. With the stable 1.0 release of the syntax earlier this year, the need to change the internal representation is now much less pronounced: we know that the syntax won’t be changing as fast as it used to. Furthermore, FluentBundle is close to a 1.0 as well, and any changes to the internal representation would at this point likely come from changes to the parser/resolver code which should probably be reflected in a major version bump anyways.

OTOH, if the internal representation was public and documented as such, it would open up a whole new world of possibilities. There are many good things that can happen when data is just data, and when logic is separated from it. The runtime representation can be easily serialized and structure-cloned. The implementation stays simple, because it doesn’t have to worry about hiding things anymore. Testing becomes trivial with fixtures. Etc.

This is how I’ve arrived at yet another proposal for the API design:

  • Clean-up the runtime representation, and keep FluentBundle.getMessage and FluentBundle.format (renamed to .formatPattern). Work-in-progress PR #380.

The proposal could be viewed as a fix to #358 which @eemeli filed to document the access to values and attributes of messages stored in a FluentBundle. Rather than come up with elaborate schemes of encapsulating messages, it doubles down on the fact that one can getMessage a message and look inside of it. Here’s how I’d imagine a canonical way of formatting a message value:

let message = bundle.getMessage(id);
if (message.value) {
    bundle.formatPattern(message.value, args, errors);
}

The proposal also has the benefit of changing very little of how FluentBundle works today, which removes much of the friction related to migrating consumers to the new API. I’d still consider removing addMessages to keep the API lean, so at least some slight work would be required.

Thanks to everyone for your continued interest in this discussion. I know it’s taking a while and I hope to be able, with your help, to settle on the final design soon.

1 Like

The discussion about the formatPattern proposal happened largely on GitHub: #380. It is my understanding that we’re now in agreement to move forward with this design.

The discussion on GitHub focused on how much of the message (as returned by getMessage) should be considered public API.

The outcome is that the {value: Pattern, attributes: Record<string, Pattern>} shape will be part of the public API of @fluent/bundle. The Pattern type will remain “private” and implementation-specific, and other implementations will be free to use any structure they see fit for the purpose. This should enable much more experimentation and creativity wrt. the resolver’s implementation.

Thanks, everyone, for your feedback and patience. I’ll update the PR and close the other ones, corresponding to the previous iterations of the redesign. My plan is prepare the changes for a review by the end of this week, and merge and publish next week.