Translations

Multilingual is included in Nick. It is not enabled by default.

You can enable the multilingual support by adding the multilingual profile to your configuration file. You can also replace the default profile with the multilingualcontent profile if you want to your initial content to be multilingual.

Nick provides a @translations endpoint to handle the translation information of the content objects.

Once we enabled more than one language, we can link two content items of different languages to be the translation of each other issuing a POST query to the @translations endpoint, including the id of the content to which it should be linked. The id of the content must be a full URL of the content object:

POST /en/events/@translations HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Content-Type: application/json

{
  "id": "http://localhost:8080/nl/evenementen"
}

The API will return a 201 Created response, if the linking was successful:

HTTP/1.1 204 No Content

We can also use the object’s path to link the translation instead of the full URL:

POST /en/events/@translations HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Content-Type: application/json

{
  "id": "/nl/evenementen"
}

HTTP/1.1 204 No Content

We can also use the object’s UID to link the translation:

POST /en/events/@translations HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Content-Type: application/json

{
  "id": "495efb73-cbdd-4bef-935a-a56f70a20854"
}

HTTP/1.1 204 No Content

After linking the contents, we can get the list of the translations of that content item by issuing a GET request on the @translations endpoint of that content item:

GET /en/events/@translations HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk

HTTP/1.1 200 OK
Content-Type: application/json

{
  "@id": "http://localhost:8080/en/events/@translations",
  "items": [
    {
      "@id": "http://localhost:8080/nl/evenementen",
      "language": "nl"
    }
  ],
  "root": {
    "en": "http://localhost:8080/en",
    "nl": "http://localhost:8080/nl"
  }
}

To unlink the content, issue a DELETE request on the @translations endpoint of the content item, and provide the language code you want to unlink:

DELETE /en/events/@translations HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk

{
  "language": "nl"
}
HTTP/1.1 204 No Content

Creating a translation from an existing content

The POST content endpoint to a folder is also capable of linking this new content with an existing translation using two parameters: translationOf and language.

POST /nl HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Content-Type: application/json

{
  "@type": "Page",
  "id": "evenementen",
  "language": "nl",
  "title": "Evenementen",
  "translation_of": "495efb73-cbdd-4bef-935a-a56f70a20854"
}

HTTP/1.1 201 Created
Content-Type: application/json

{
  "title": "Evenementen",
  "@components": {
    "actions": {
      "@id": "http://localhost:8080/nl/evenementen/@actions"
    },
    "breadcrumbs": {
      "@id": "http://localhost:8080/nl/evenementen/@breadcrumbs"
    },
    "catalog": {
      "@id": "http://localhost:8080/nl/evenementen/@catalog"
    },
    "navigation": {
      "@id": "http://localhost:8080/nl/evenementen/@navigation"
    },
    "navroot": {
      "@id": "http://localhost:8080/nl/evenementen/@navroot"
    },
    "translations": {
      "@id": "http://localhost:8080/nl/evenementen/@translations"
    },
    "types": {
      "@id": "http://localhost:8080/nl/evenementen/@types"
    },
    "workflow": {
      "@id": "http://localhost:8080/nl/evenementen/@workflow"
    }
  },
  "@id": "http://localhost:8080/nl/evenementen/my-news-item",
  "@type": "Page",
  "id": "evenementen",
  "created": "2022-04-08T16:00:00.000Z",
  "modified": "2022-04-08T16:00:00.000Z",
  "UID": "a95388f2-e4b3-4292-98aa-62656cbd5b9c",
  "is_folderish": true,
  "layout": "view",
  "owner": "admin",
  "review_state": "private",
  "language": {
    "title": "Nederlands",
    "token": "nl"
  },
  "lock": {
    "locked": false,
    "stealable": true
  }
}

Get location in the tree for new translations

When you create a translation in Plone, there are policies in place for finding a suitable placement for it. This endpoint returns the proper placement for the newly created translation:

GET /en/events/@translation-locator?target_language=nl HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk

HTTP/1.1 200 OK
Content-Type: application/json

{
    "@id": "http://localhost:8080/nl"
}

Expansion

This service can be used with the Expansion mechanism which allows getting additional information about a content item in one query, avoiding additional requests.

Translation information can be provided by the API expansion for translatable content items.

If a simple GET request is done on the content item, a new entry will be shown on the @components entry, with the URL of the @translations endpoint:

GET /en/events HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk

HTTP/1.1 201 Created
Content-Type: application/json

{
  "title": "Events",
  "@components": {
    "actions": {
      "@id": "http://localhost:8080/en/events/@actions"
    },
    "breadcrumbs": {
      "@id": "http://localhost:8080/en/events/@breadcrumbs"
    },
    "catalog": {
      "@id": "http://localhost:8080/en/events/@catalog"
    },
    "navigation": {
      "@id": "http://localhost:8080/en/events/@navigation"
    },
    "navroot": {
      "@id": "http://localhost:8080/en/events/@navroot"
    },
    "translations": {
      "@id": "http://localhost:8080/en/events/@translations"
    },
    "types": {
      "@id": "http://localhost:8080/en/events/@types"
    },
    "workflow": {
      "@id": "http://localhost:8080/en/events/@workflow"
    }
  },
  "@id": "http://localhost:8080/en/events/my-news-item",
  "@type": "Page",
  "id": "events",
  "created": "2022-04-08T16:00:00.000Z",
  "modified": "2022-04-08T16:00:00.000Z",
  "UID": "a95388f2-e4b3-4292-98aa-62656cbd5b9c",
  "is_folderish": true,
  "layout": "view",
  "owner": "admin",
  "review_state": "private",
  "language": {
    "title": "English",
    "token": "en"
  },
  "lock": {
    "locked": false,
    "stealable": true
  }
}

In order to expand and embed the translations component, use the GET parameter expand with the value translations.

GET /en/events/?expand=translations HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk

HTTP/1.1 201 Created
Content-Type: application/json

{
  "title": "Events",
  "@components": {
    "actions": {
      "@id": "http://localhost:8080/en/events/@actions"
    },
    "breadcrumbs": {
      "@id": "http://localhost:8080/en/events/@breadcrumbs"
    },
    "catalog": {
      "@id": "http://localhost:8080/en/events/@catalog"
    },
    "navigation": {
      "@id": "http://localhost:8080/en/events/@navigation"
    },
    "navroot": {
      "@id": "http://localhost:8080/en/events/@navroot"
    },
    "translations": {
      "@id": "http://localhost:8080/en/events/@translations",
      "items": [
        {
          "@id": "http://localhost:8080/nl/evenementen",
          "language": "nl"
        }
      ],
      "root": {
        "en": "http://localhost:8080/en",
        "nl": "http://localhost:8080/nl"
      }
    },
    "types": {
      "@id": "http://localhost:8080/en/events/@types"
    },
    "workflow": {
      "@id": "http://localhost:8080/en/events/@workflow"
    }
  },
  "@id": "http://localhost:8080/en/events/my-news-item",
  "@type": "Page",
  "id": "events",
  "created": "2022-04-08T16:00:00.000Z",
  "modified": "2022-04-08T16:00:00.000Z",
  "UID": "a95388f2-e4b3-4292-98aa-62656cbd5b9c",
  "is_folderish": true,
  "layout": "view",
  "owner": "admin",
  "review_state": "private",
  "language": {
    "title": "English",
    "token": "en"
  },
  "lock": {
    "locked": false,
    "stealable": true
  }
}