Rule Cascade
LearnMessages

Locales and fallback

Translate messages per locale, and know which text a user gets when a translation is missing.

The messages catalog has one map of templates per locale. A request names the user's locale. The engine looks for the template from the most to the least specific tag: fr-CA, then fr, then the ruleset's defaultLocale (en unless you set it), then the key itself.

Tags are compared exactly, including case. Only the default locale must have every key.

Syntax

a default locale and three catalogs
defaultLocale: en
messages:
  en:    { order.tooMany: "You can order at most {max} items." }
  fr:    { order.tooMany: "Vous pouvez commander au plus {max} articles." }
  fr-CA: { order.tooMany: "Vous pouvez commander au maximum {max} articles." }
a request in Swiss French
{ "entity": "Order", "operation": "create", "locale": "fr-CH", "data": { "quantity": 11 } }

Example

There is no fr-CH catalog, so the engine drops the last subtag and uses fr.

locales.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.locales, version: 1.0.0, title: Locales }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
defaultLocale: en
rules:
  - id: order.quantity.max
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: lte, args: [{ var: data.quantity }, 10] }
    severity: error
    finding: { code: LRN-LOC-001, message: order.tooMany, args: { max: 10 } }
messages:
  en:
    order.tooMany: "You can order at most {max} items."
  fr:
    order.tooMany: "Vous pouvez commander au plus {max} articles."
  fr-CA:
    order.tooMany: "Vous pouvez commander au maximum {max} articles."
tests:
  - name: fr-CH has no catalog, so fr is used
    entity: Order
    operation: create
    given:
      data: { quantity: 11 }
      locale: fr-CH
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.max, message: Vous pouvez commander au plus 10 articles. }
  - name: fr-CA has its own catalog
    entity: Order
    operation: create
    given:
      data: { quantity: 11 }
      locale: fr-CA
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.max, message: Vous pouvez commander au maximum 10 articles. }
  - name: de falls back to the default locale
    entity: Order
    operation: create
    given:
      data: { quantity: 11 }
      locale: de
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.max, message: You can order at most 10 items. }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "quantity": 11
  },
  "locale": "fr-CH"
}

Result, from the engine

Decisiondeny1 finding, server channel

  • LRN-LOC-001errorblockingVous pouvez commander au plus 10 articles./quantity
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

The other golden tests show fr-CA, which has its own catalog, and de, which falls back to the default locale.

Common mistakes

  • Writing fr_CA. Locale tags use a hyphen, like fr-CA. An underscore is not a tag.
  • Different case. fr-ca does not find the fr-CA catalog. It falls back to fr.
  • A key only in a translation. The default locale must have every key, or the ruleset fails to load.

Exercise

Make French the default language. Remove the fr-CA catalog. A request in Spanish (es) must then show the French text.

Hint

Set defaultLocale: fr. Keep the en catalog for English users.

Show answer
locales.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.locales, version: 1.0.0, title: Locales }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
defaultLocale: fr
rules:
  - id: order.quantity.max
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: lte, args: [{ var: data.quantity }, 10] }
    severity: error
    finding: { code: LRN-LOC-001, message: order.tooMany, args: { max: 10 } }
messages:
  en:
    order.tooMany: "You can order at most {max} items."
  fr:
    order.tooMany: "Vous pouvez commander au plus {max} articles."
tests:
  - name: es falls back to the new default locale, fr
    entity: Order
    operation: create
    given:
      data: { quantity: 11 }
      locale: es
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.max, message: Vous pouvez commander au plus 10 articles. }
  - name: en still has its own catalog
    entity: Order
    operation: create
    given:
      data: { quantity: 11 }
      locale: en
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.max, message: You can order at most 10 items. }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "quantity": 11
  },
  "locale": "es"
}

Result, from the engine

Decisiondeny1 finding, server channel

  • LRN-LOC-001errorblockingVous pouvez commander au plus 10 articles./quantity
Course overview

On this page