Translation Variants

Support for multiple translation variants of the same text within a single language.

A single liturgical text often has more than one accepted translation in the same language. TranslationVariants extends LocalizedText to allow multiple variant translations per language tag, each annotated with provenance and usage context.

Motivation

In liturgical practice a single source utterance — e.g. ኪርያ ኤሌይሶን — may have several sanctioned English renderings:

  • “Lord, have mercy” (EOTC official)
  • “O Lord, show mercy” (diaspora prayer book)
  • “Lord, be merciful to us” (scholarly literal)

Rather than forcing a single canonical choice per language, OLS allows publishers to ship all accepted variants and let consuming applications select the most appropriate one by context.

Schema Pattern

The variants key sits alongside the existing text map inside any LocalizedText block. Each entry under variants is keyed by language tag and holds an array of variant objects.

{
  "text": {
    "gez-Ethi": "ኪርያ ኤሌይሶን",
    "en": "Lord, have mercy"
  },
  "variants": {
    "en": [
      {
        "value": "Lord, have mercy",
        "label": "EOTC Official",
        "source": "EOTC English Missal 2019",
        "default": true
      },
      {
        "value": "O Lord, show mercy",
        "label": "Diaspora Prayer Book",
        "source": "St. Mary Cathedral, Los Angeles, 2021"
      },
      {
        "value": "Lord, be merciful to us",
        "label": "Scholarly Literal",
        "source": "Getatchew Haile, CSCO vol. 542"
      }
    ],
    "am-Ethi": [
      {
        "value": "ጌታ ሆይ ማረን",
        "label": "Traditional",
        "default": true
      },
      {
        "value": "አቤቱ ይቅር በለን",
        "label": "Contemporary"
      }
    ]
  },
  "textMeta": {
    "direction": "ltr",
    "register": "liturgical"
  }
}

Variant Object Fields

FieldTypeRequiredDescription
valuestringThe translated text content.
labelstringShort human-readable name for this variant (e.g. “EOTC Official”).
sourcestringBibliographic or provenance reference.
defaultbooleanWhen true, this variant is the preferred rendering. At most one variant per language may be marked default.
registerstringOverrides the parent textMeta.register for this variant (e.g. “scholarly”, “pastoral”).
audiencestringIntended audience context: “general”, “academic”, “children”, “neophyte”.

Resolution Rules

  1. Backward-compatible — If no variants key is present, the text map value is treated as the sole (default) translation for each language.
  2. Default selection — Applications SHOULD display the variant marked "default": true. If none is marked, the first item in the array is treated as default.
  3. text ↔ variants sync — When variants exists for a given language, the corresponding text entry MUST equal the value of the default variant. This ensures legacy consumers that ignore variants still receive the preferred translation.

Usage in Utterances

Translation variants integrate naturally with existing content types:

{
  "id": "ut-kyrie-deacon",
  "roles": ["role-deacon"],
  "mode": "chanted",
  "text": {
    "gez-Ethi": "ኪርያ ኤሌይሶን",
    "en": "Lord, have mercy"
  },
  "variants": {
    "en": [
      { "value": "Lord, have mercy", "label": "EOTC Official", "default": true },
      { "value": "O Lord, show mercy", "label": "Diaspora Prayer Book" }
    ]
  }
}

When to Use Variants

SDK Implementation Guide

Implementers building libraries or applications that consume OLS data SHOULD follow these patterns:

Parsing

  1. Parse the text map first — this is always safe and backward-compatible.
  2. If variants is present, validate against the rules in JSON Schema & Validation.
  3. Build an internal lookup: Map<languageTag, TranslationVariant[]>.

Selection Algorithm

function resolveVariant(lang, variants, preferences):
  candidates = variants[lang]
  if candidates is empty:
    return text[lang]

  // 1. User preference by label
  if preferences.label exists:
    match = candidates.find(v => v.label == preferences.label)
    if match: return match.value

  // 2. Audience filter
  if preferences.audience exists:
    match = candidates.find(v => v.audience == preferences.audience)
    if match: return match.value

  // 3. Default variant
  defaultVariant = candidates.find(v => v.default == true)
  if defaultVariant: return defaultVariant.value

  // 4. Fallback to first
  return candidates[0].value

Error Handling

SDKs MUST surface validation errors clearly:

BehaviorRequirement
Invalid language tag in variantsReject with OLS_VARIANT_LANG_MISMATCH.
Multiple "default": true in same languageReject with OLS_VARIANT_MULTI_DEFAULT.
Default value ≠ text entryReject with OLS_VARIANT_DEFAULT_SYNC.
Unknown fields in variant objectIgnore (forward-compatible), but log a warning.
Missing variants key entirelyValid — treat text values as sole translations.