Internationalization

The i18n key formats for app settings pages, widgets, workflow actions, action inputs and triggers, with locale fallback rules.

The i18n object holds every string your app shows in Kustomer's own UI: settings page labels, widget titles, and the names of your actions, their inputs and your triggers in the workflow builder. Keys are grouped by locale; Kustomer shows the agent's locale when you provide it and falls back to en_us.

i18n (shape)
{
  "i18n": {
    "en_us": { "<key>": "<text>" },
    "fr_fr": { "<key>": "<texte>" }
  }
}

Always provide en_us. Missing keys fall back to en_us, and with no en_us entry Kustomer shows the raw key or system name.

Strings inside your KViews, widgets and custom settings pages are yours to translate; i18n covers only the UI Kustomer renders for you.

Settings page strings

<app> is your app name as written in the definition.

KeyLabels
<app>.settings.page.titleThe settings page heading and menu entry
<app>.settings.page.descriptionThe text under the heading
<app>.settings.path.<category>.<name>.displayNameA setting's label
<app>.settings.path.<category>.<name>.descriptionA setting's help text. Markdown is supported.
Settings strings
{
  "en_us": {
    "acme_orders.settings.page.title": "Acme Orders",
    "acme_orders.settings.page.description": "Connect your Acme store.",
    "acme_orders.settings.path.default.apiKey.displayName": "API key",
    "acme_orders.settings.path.default.apiKey.description": "Create one in Acme under **Settings › API**."
  }
}

Widget strings

KeyLabels
<app>.widgets.<name>.titleThe title of the widget with that name

Workflow builder strings

Actions and triggers are labelled under an app.<app>.workflow.builder prefix followed by their full, prefixed name:

KeyLabels
app.<app>.workflow.builder.actions.<action name>.displayNameThe action in the step picker
app.<app>.workflow.builder.actions.<action name>.descriptionIts description
app.<app>.workflow.builder.actions.<action name>.properties.<input>.displayNameAn input field's label
app.<app>.workflow.builder.actions.<action name>.properties.<input>.exampleTextAn input's placeholder
app.<app>.workflow.builder.triggers.<event name>.displayNameThe trigger in the trigger picker
app.<app>.workflow.builder.triggers.<event name>.descriptionIts description, shown when chosen

<action name> and <event name> are the full names, including kustomer.app.<app>., so the app name appears twice:

Workflow builder strings
{
  "en_us": {
    "app.acme_orders.workflow.builder.actions.kustomer.app.acme_orders.refund.displayName": "Refund order",
    "app.acme_orders.workflow.builder.actions.kustomer.app.acme_orders.refund.description": "Issue a full or partial refund in Acme.",
    "app.acme_orders.workflow.builder.actions.kustomer.app.acme_orders.refund.properties.orderId.displayName": "Order ID",
    "app.acme_orders.workflow.builder.actions.kustomer.app.acme_orders.refund.properties.orderId.exampleText": "4417",
    "app.acme_orders.workflow.builder.actions.kustomer.app.acme_orders.refund.properties.amount.displayName": "Amount",
    "app.acme_orders.workflow.builder.triggers.kustomer.app.acme_orders.order-updated.displayName": "Acme order updated",
    "app.acme_orders.workflow.builder.triggers.kustomer.app.acme_orders.order-updated.description": "Runs whenever Acme sends an order update. Later steps can use the order ID, customer email, status and total."
  }
}

Keep keys in sync

Keys are long and easy to mistype, and a typo fails silently: the builder just shows the raw name. Generate them from the definition in your build step instead of writing them by hand:

build-i18n.mjs
const app = 'acme_orders'
const prefix = `app.${app}.workflow.builder`

export function actionStrings(actions, labels) {
  const out = {}
  for (const action of actions) {
    const l = labels[action.name] ?? {}
    out[`${prefix}.actions.${action.name}.displayName`] = l.displayName ?? action.description
    if (l.description) out[`${prefix}.actions.${action.name}.description`] = l.description
    for (const input of Object.keys(action.inputSchema?.properties ?? {})) {
      const p = l.properties?.[input] ?? {}
      out[`${prefix}.actions.${action.name}.properties.${input}.displayName`] = p.displayName ?? input
      if (p.exampleText) out[`${prefix}.actions.${action.name}.properties.${input}.exampleText`] = p.exampleText
    }
  }
  return out
}

And check for gaps before you register. This prints the label key of every setting that doesn't have one:

Shell
jq -r '
  .app as $app | .i18n.en_us as $t
  | .settings | to_entries[] | .key as $category
  | .value | keys[]
  | "\($app).settings.path.\($category).\(.).displayName"
  | select($t[.] == null)
' dist/acme_orders.json