RESTful API and i18n: how to design the response

apiapi-designhttprest

We are designing a RESTful API that is mainly intended to meet the needs of a single client. Because of its very particular circumstances, this client has to make as few requests as possible.

The API handles i18n through an Accept-Language header in the requests. This works for all things that the client needs to do except for one feature, in which client needs to store the responses of a request to a single endpoint in all available locales.

Can we somehow design the API in a way that allows the client to grab all this information with a single request and without breaking a consistent, well-structured RESTful API design?

Options we have considered so far:

  • Allowing the inclusion of multiple locales in the Accept-Language header and adding localized versions for all requested locales in the response, each one identified by its ISO 639-1 language code as the key.
  • Creating something like an "?all_languages=true" parameter to that endpoint and returning localized versions for all available locales in the response if that parameter is present.
  • (If none of the above works for us) making multiple requests to grab all localized versions from client.

Which one is the best alternative?

Best Answer

You've described two effective ways of asking for multiple languages. Either should work fine. I would choose the explicit language request parameter for my own code.

TL;DR Backstory

There is an Accept-Language header. Note, Accept not Accepted. It's a standard part of HTTP content negotiation. The response usually sets a Content-Language header back.

Accept-Language is the opening bid, offering a set of options; Content-Language is the resolution, stating what language was chosen. Most Content-Language answers return a single language, but there is an option to provide a comma-separated list of response languages. Usually that would be mixed-content, but there's no reason it could not signal multiple disjoint alternatives. If you wanted the client to request all available languages, there is already a wildcard request option, *.

So there is already an HTTP header mechanism you can use. However, beware that you'd be piggybacking a negotiation process that more often presents an array of possible options, and gets back a single option. You'd be shifting the sense to "here is a list of options, give me all of them!" If you're okay with that, you've got a solution.

There is, however, considerable debate as to the suitability of signaling REST API parameters in HTTP headers. It's a bit like entering a restaurant and blurting out your detailed order to the host or maƮtre d' rather than waiting for the waiter or waitress to appear. It can work, and may work well e.g. if the order directed at the host relates to drinks or appetizers--things the host can quickly see to, or quickly communicate to your server. But it can also be seen as a breach of protocol, addressed at the wrong level/layer or to the wrong player.

A second alternative would be an explicit request parameter. You suggest ?all_languages=true. That seems overly specific. Something like lang=en,fr,es (allow multiple listed languages) or lang=* or lang=all (specify every available language) seems more general. This could be expressed either in the URL or request body.

Either way, your multi-lingual response can be easily encoded into returned JSON payload:

[ { "lang": "en", "content": "As Gregor Samsa awoke one morning..." },
  { "lang": "de", "content": "Als Gregor Samsa eines Morgens..." },
  ...
]

In the end, either of these approaches should work well for you. Either could be viewed as a "consistent, well-structured RESTful API design." The determination as to which is better rests mostly on your attitude toward the appropriateness of piggybacking (and slightly altering the typical sense of) HTTP content negotiation headers.

My own preference is to not intermix headers and other parameters as equal parts of an API request. The explicit lang or language parameter seems cleaner to me. But since the HTTP verb (e.g. GET, PUT, POST, PATCH, ...) is part of the header, and also critical to / intermixed with the interpretation of the request, I admit the envelope vs. contents distinction is a bit artificial and fuzzy. As with most design decisions, genuine experts answer it differently, and YMMV.