Internationalization (I18N)

Textual descriptions part of a resource may need to be offered in the language of a user.

One of following strategies may be chosen for internationalization:

  • Do not offer textual descriptions in the API response. Offer them only separate from the data, for example as a dedicated collection resource in the API returning a code list with descriptions. This list can be cached by a client.

  • Only include descriptions in a single language, determined by language negotiation (Accept-Language header) or by the language in the user’s profile.

  • Offer descriptions in all applicable languages, i.e. for APIs offered by the Belgian Government in French, Dutch, German and sometimes English as well. This approach is not scalable to a high number of languages. The descriptions may be filtered by a lang query parameter.

Rule: Content-Language [int-conlang]

For internationalized content, the Content-Language response header MUST be used to indicate the natural language(s) of the intended audience of the response content. For content that isn’t translated, or returned in all supported languages, this header MAY be included.

If no Content-Language is specified, the default is that the content is intended for all language audiences. This might mean that the sender does not consider it to be specific to any natural language, or that the sender does not know for which language it is intended.

Accept-Language

As for HTTP, HTTP supports content negotiation for the language used in response content.

Rule: Accept-Language header [int-negot]

When API responses include textual descriptions in a single language, users can indicate their preferred language by adding the Accept-Language HTTP request header (RFC 9110). Alternatively, the language may also be retrieved from the user’s profile.

The value of Accept-Language is a language tag (RFC 5646), consisting of a ISO 639-1 lowercase language code, optionally followed by an uppercase country code indicating a country-specific variant. Internationalized APIs of Belgian Federal Government organizations MUST also support the variants for the official Belgian languages ending on -BE.

A client may specify multiple languages in Accept-Language, with a q parameter to indicate their priority. A wildcard * matches any language, but is not always sent by HTTP client implementations.

For best interoperability, clients are recommended to also include language tags without a country suffix, and servers to fall back to the general language or a supported country-specific variant if the requested variant isn’t supported.

In case the server could not honor any of the requested languages, it SHOULD return the resource in a default language.

If the resource supports caching, the Vary: Accept-Language MUST be included in the response.

Example 1. Language negotiation
Request
Accept-Language: nl-BE, nl;q=0.9, fr-BE;q=0.8, fr;q=0.7, en;q=0.6, *;q=0.5

GET /countries/BE
Response
Content-Language: nl

{
  "code": "BE",
  "name": "Belgiƫ"
}

Multi-language descriptions

It may be decided to include all supported translations for descriptions in a resource’s representation.

Rule: Multi-language descriptions [multi-lang]

A multi-language description SHOULD be represented by a JSON object with ISO 639-1 language codes as property names and the corresponding textual description as values.

An API MAY offer to filter the representation to a single language by using the reserved query parameter lang.

A LocalizedString type is defined supporting the three official Belgian languages.

LocalizedString JSON Schema (from common-v1.yaml)
LocalizedString:
  description: A description specified in multiple languages
  type: object
  properties:
    fr:
      type: string
    nl:
      type: string
    de:
      type: string
Example 2. Multi-language descriptions

Including a Content-Language response header is optional for multi-language descriptions.

Request

GET /countries/BE

Response
Content-Language: fr,nl,de

{
  "code": "BE",
  "name": {
   "fr": "Belgique",
   "nl": "Belgiƫ",
   "de": "Belgien"
  }
}
Filtering a single language
GET /countries/BE?lang=fr

Response:

Content-Language: fr

{
  "code": "BE",
  "name": {
   "fr": "Belgique"
  }
}