Skip to main content

Internationalization Specification

Flow

A notification template is changed and submitted for translation. Upon submission, an outbound webhook call will be made that will notify of a pending change submission. Upon receipt of the webhook event, the content for the notification template can be fetched and the translation process begun.

Diagram

{
type: "notification:submitted",
data: {
id: "NOTIFICATION_ID_HERE",
// ...
}

Fetching Content

When the "notification:submitted" is received

GET /notifications/:notification_id/draft/content

Sample Template

Sample Template Response

{
"blocks": [
{
"alias": "Greetings",
"context": "This is where we greet a new user",
"id": "block_43c114d9-9cfd-4340-808f-17e2fc7a4c87",
"type": "text",
"checksum": "fb60f2098fa407a4ff8d48e3e908d889",
"content": "Hello <variable id=\"3\">{name}</variable>, Welcome to <highlight id=\"6\">Courier</highlight>!",
"locales": {
"fr_FR": "Bonjour <variable id=\"3\">{name}</variable>, bienvenu a <highlight id=\"6\">Courier</highlight>!"
}
},
{
"id": "block_f19dd58f-d32c-41b8-911c-239053d34803",
"type": "markdown",
"content": "Ready, <variable id=\"3\">{name}</variable>? Get started [here](http://daringfireball.net/projects/markdown/).",
"checksum": "d06665ec3f663789db81474bc1a82fc5",
"locales": {
"fr_FR": "prêt, <variable id=\"3\">{name}</variable>? Commencer [ici](http://daringfireball.net/projects/markdown/)."
}
},
{
"id": "block_78863d66-54f2-4032-a897-cbed3abb2538",
"type": "quote",
"content": "<highlight id=\"3\">Marvel</highlight> is better than DC, do you agree <variable id=\"6\">{name}</variable>?",
"checksum": "d6e5df4e00b54703e7048cbc5276863e",
"locales": {
"fr_FR": "<highlight id=\"3\">Marvel</highlight> mieux que DC, êtes-vous d'accord <variable id=\"3\">{name}</variable>?"
}
},
{
"id": "block_106b5136-1bf9-4842-bff7-cbae140fd3a3",
"type": "action",
"content": "Share",
"checksum": "67561c1170434dcbfd9becb827672495",
"locales": {
"fr_FR": "Partagez"
}
},
{
"id": "block_9ec5eba6-8ab1-4938-96b0-0f6b63439619",
"type": "list",
"content": {
"children": "<variable id=\"3\">{userName}</variable>",
"parent": "<variable id=\"3\">{blogTitle}</variable> liked by"
},
"checksum": "a4f817cd1a363e95ce1400095937cad8",
"locales": {
"fr_FR": {
"children": "<variable id=\"3\">{userName}</variable>",
"parent": "<variable id=\"3\">{blogTitle}</variable> aimé par"
}
}
},
{
"id": "block_31500deb-de21-436f-9ae6-98f08d569148",
"type": "template",
"content": "\n\n<div class=\"notes\">Thanks!</div>",
"checksum": "c1a6639e8a83a4aba68004ad1a06f124",
"locales": {
"fr_FR": "\n\n<div class=\"notes\">Merci!</div>"
}
}
],
"channels": [
{
"id": "channel_f2e7b2e9-187f-40d9-9725-636d6c59833a",
"type": "email",
"content": {
"subject": "New Subject"
},
"checksum": "96bcf212afa5cae1c7918280743aec71",
"locales": {
"fr_FR": {
"subject": "French New Subject"
}
}
}
],
"checksum": "22d224a20345f1e3d3cf4c231243a747"
}

Block Alias and Context

Every block can optionally have an alias and a context. This can be used to have supplementary block information for organizing blocks better.

Retrieval: GET content endpoints can be used to retrieve alias and context per block

Updates: Can only be changed from the Courier UI by clicking on the block info(i) icon

Block types supported

Text Block

Example: Text block with a variable {name} and a highlight

Here's how the block JSON would look like

{
"alias": "Greetings",
"context": "This is where we greet a new user",
"id": "block_43c114d9-9cfd-4340-808f-17e2fc7a4c87",
"type": "text",
"content": "Hello <variable id=\"3\">{name}</variable>, Welcome to <highlight id=\"6\">Courier</highlight>!",
"locales": {
"fr_FR": "Bonjour <variable id=\"3\">{name}</variable>, bienvenu a <highlight id=\"6\">Courier</highlight>!"
}
}

Let's talk IDs

Block ID: This is the top level ID which every block gets and is prefixed with block.

Variable ID: Unique IDs that a variable inside a block content gets

Highlight ID: Unique IDs that a highlight inside a block content gets

Note - All the IDs are auto-generated by Courier and are used in resolving the contents for the block. The locale variations PUT should also have exactly same IDs for accurate consolidation and resolution.

Quote Block

Example: Quote block with a variable {name} and a highlight

{
"id": "block_78863d66-54f2-4032-a897-cbed3abb2538",
"type": "quote",
"content": "<highlight id=\"3\">Marvel</highlight> is better than DC, do you agree <variable id=\"6\">{name}</variable>?",
"locales": {
"fr_FR": "<highlight id=\"3\">Marvel</highlight> mieux que DC, êtes-vous d'accord <variable id=\"3\">{name}</variable>?"
}
}

Markdown Block

Example: Markdown block with a variable {name}

{
"id": "block_f19dd58f-d32c-41b8-911c-239053d34803",
"type": "markdown",
"content": "Ready, <variable id=\"3\">{name}</variable>? Get started [here](http://daringfireball.net/projects/markdown/).",
"locales": {
"fr_FR": "prêt, <variable id=\"3\">{name}</variable>? Commencer [ici](http://daringfireball.net/projects/markdown/)."
}
}

Action Block

{
"id": "block_106b5136-1bf9-4842-bff7-cbae140fd3a3",
"type": "action",
"content": "Share it",
"locales": {
"fr_FR": "Partagez-le"
}
}

List Block

List blocks can get a bit confusing - an article that can help an article that can help 🙂

{
"id": "block_9ec5eba6-8ab1-4938-96b0-0f6b63439619",
"type": "list",
"content": {
"children": "<variable id=\"3\">{userName}</variable>",
"parent": "<variable id=\"3\">{blogTitle}</variable> liked by"
},
"locales": {
"fr_FR": {
"children": "<variable id=\"3\">{userName}</variable>",
"parent": "<variable id=\"3\">{blogTitle}</variable> aimé par"
}
}
}

Template Block

{
"id": "block_31500deb-de21-436f-9ae6-98f08d569148",
"type": "template",
"content": "\n\n<div class=\"notes\">Thanks!</div>"
},
"locales": {
"fr_FR": "\n\n<div class=\"notes\">Merci!</div>"
}
}

Note: We intentionally omit styles and comments in the API response to optimize the translation payload

Checksum

We md5 hash the contents for each block, channel and notification so you could track if contents have ever changed in order to manage translations workflow as needed.

Channels

We expose the email (subject) and push (title) to be localized (assuming you have those channels configured with your notification)

Updating Content

When translated content is received, template content should be fetched and the new content should be overlaid. This can be repeated each time a piece of translated content is received.

PUT /notifications/:notification_id/draft/locales

{
"blocks": [
{
"id": "block_43c114d9-9cfd-4340-808f-17e2fc7a4c87",
"type": "text",
"locales": {
"fr_FR": "Bonjour <variable id=\"3\">{name}</variable>, bienvenu a <highlight id=\"6\">Courier</highlight>!"
}
},
{
"id": "block_78863d66-54f2-4032-a897-cbed3abb2538",
"type": "quote",
"locales": {
"fr_FR": "<highlight id=\"3\">Marvel</highlight> mieux que DC, êtes-vous d'accord <variable id=\"3\">{name}</variable>?"
}
},
{
"id": "block_f19dd58f-d32c-41b8-911c-239053d34803",
"type": "markdown",
"locales": {
"fr_FR": "prêt, <variable id=\"3\">{name}</variable>? Commencer [ici](http://daringfireball.net/projects/markdown/)."
}
},
{
"id": "block_106b5136-1bf9-4842-bff7-cbae140fd3a3",
"type": "action",
"locales": {
"fr_FR": "Partagez-le"
}
},
{
"id": "block_9ec5eba6-8ab1-4938-96b0-0f6b63439619",
"type": "list",
"locales": {
"fr_FR": {
"children": "<variable id=\"3\">{userName}</variable>",
"parent": "<variable id=\"3\">{blogTitle}</variable> aimé par"
}
}
},
{
"id": "block_31500deb-de21-436f-9ae6-98f08d569148",
"type": "template",
"locales": {
"fr_FR": "\n\n<div class=\"notes\">Merci!</div>"
}
}
]
}

Updating partial contents

In case you'd like to update a specific block or a channel's locales, or all blocks and channels for a specific locale (for ex: Chinese translations for a notification), we've got you covered. You can use the following endpoints -

Update locales for a specific block

POST /notifications/:notification_id/blocks/:block_id/locales

or POST /notifications/:id/draft/blocks/:blockId/locales

{
"fr_FR": "French Lorem Ipsum",
"it_IT": "Italian Lorem Ipsum"
}

Update locales for a specific channel

POST /notifications/:id/channels/:channelId/locales

or POST /notifications/:id/draft/channels/:channelId/locales

{
"fr_FR": "French Subject",
"it_IT": "Italian Subject"
}

Update block and channel contents for a specific locale

PUT /notifications/:id/locales/:localeid

or PUT /notifications/:id/draft/locales/:localeid

{
"blocks": [
{
"id": "block_f4c81f75-8edb-4c8f-a50b-37193a580b7f",
"content": "Italian text block content"
},
{
"id": "block_fcb968bf-b0d1-48e0-b1b2-e23963de8479",
"content": {
"children": "Italian List block children",
"parent": "Italian List block parent"
}
}
],
"channels": [
{
"id": "channel_9f24f9a9-c597-4330-b357-7929a6dc8f33",
"content": "Italian Subject"
},
{
"id": "channel_20dda4a6-5439-4a71-953a-bc70a90e39f5",
"content": "Italian Push Title"
}
]

Completing The Process

Once all content has been updated, Courier can be notified that the process is completed and release the template to be published.

PUT /notifications/:notification_id/:submission_id/checks

{
"checks": [
{
"id": "B5BYH93H4D4XRPJBMZJGB43TJEZ3", // check ID
"status": "RESOLVED",
"type": "custom"
}
]
}

Fetching Checks

Checks API GET endpoint

GET /notifications/:notification_id/:submission_id/checks

Example - GET /notifications/SFTYJKSF0241SVH2TWY97TTFFTQG/1630424150210/checks

{
"checks":[
{
"id":"B5BYH93H4D4XRPJBMZJGB43TJEZ3", // check ID
"status":"PENDING",
"type":"custom",
"updated":1630424150210
}
]
}

Locale based preview

Notification Preview section reflects localized content if there's a locale associated with the profile in your test-event. Here's an example -

I have "locale": "fr_FR" in the profile object of a test event

Localized Preview

Default Preview (no locale in the profile object of the test event)

Using locales while sending messages

You can use locale as a profile attribute while invoking Courier's /send endpoint. Here's an example -

{
"event": "<your-notification-or-event>",
"recipient": "<your-recipient>",
"profile": {
"locale": "en_US"
}
}

Webhooks

As mentioned earlier in the doc, you could configure the webhooks in Settings → Webhooks and we'd call those when a notification was submitted for a review, when it was canceled and also when it got published. Please note: At this point we do not have a granular way to allocate webhooks so you could get other events (like message sent events) for the webhooks you add to the Settings, feel free to ignore/not handle those. Here's a couple of screenshots -

Submitted Keys

Submitted keys are used when you have to send a notification that was submitted for a review but maybe not ready to be published. Basically, submitted is an interim state between Draft and Published. Here's how it works -

→ If notification was published, a submitted key send operation uses published notification contents

→ If notification was submitted for review, a submitted key send operation uses latest draft notification content

Submitted keys can be retrieved, updated or deleted from the Settings → API Keys UI.

Block overrides

We also expose blocks via overrides object in case you need to override the contents on the fly during the send operations.