Lingara Lingara Docs Guides API Libraries Apps Build Web app
Language: English

Cards

A card is a short list of elements that Lingara draws in its own style. There are no inputs, images, scripts or markup. An app cannot put its own code or its own look on a learner’s screen, so every card reads like the rest of Lingara. Your reply is JSON: a card holding its elements, and optionally a tutor_note (see The tutor note).

The elements

Each element has a type, which is one of these eight:

ElementFieldsWhat it shows
headingtext, levelA heading. level is 1 or 2, and any other value becomes 2. Its text is at most 80 characters.
texttext, langA paragraph of at most 600 characters. It is the only place a line break is kept.
termword, reading, gloss, langA word to learn. Only word is required. The word is at most 60 characters, the reading 120 and the gloss 160.
listitemsUp to 20 items. Each item is a text or a term, with the same fields as the element of that name.
progressvalue, labelA progress bar. value runs from 0 to 1, and the label is at most 60 characters.
dividerA line between parts of the card.
buttonlabel, action, styleA button that sends its action to your server. style is primary or secondary, and the label is at most 32 characters.
linklabel, urlA link the learner opens in their browser, after Lingara asks them. The label is at most 60 characters.

A lang is a language tag such as ja, so that Lingara draws the right script. A value that is not a language tag is removed.

The limits

  • A card holds at most 24 elements. Elements after the 24th are dropped.
  • A list holds at most 20 items. Items after the 20th are dropped, and an empty list is dropped.
  • A card holds at most 4 buttons. Buttons after the 4th are dropped.
  • Text longer than its limit is cut to fit and ends in …. Characters are counted as Unicode scalar values.
  • Control characters and direction overrides are removed from every string.
  • A link whose address is not HTTPS, carries a user name, or points at an IP address is dropped.
  • A button whose action is longer than 64 characters, or uses anything but letters, digits, _, ., : and -, is dropped.
  • An element with no text left after these rules is dropped.

These rules are forgiving: Lingara cuts or drops what does not fit and draws the rest. The kits apply the same rules before you reply, so you can see what would change.

Strict parsing

The vocabulary is closed. An unknown element type, a field an element does not have, a value of the wrong JSON type, or a reply that is not JSON at all turns the whole reply into a fallback. Lingara never draws part of a card it could not read.

Size and time

A reply may be at most 32 KiB. Lingara gives your server 3 seconds to answer a render and 5 seconds to answer an action, and retries neither. A reply must have status 200 and a JSON content type. Redirects are not followed.

Buttons and actions

When the learner presses a button, Lingara sends your server an action request carrying that button’s action. Your server answers with a new card, which replaces the old one. The same press can arrive twice, so make each action safe to repeat.

When a card fails

When Lingara cannot get a card it can draw, the learner sees Lingara’s own “this app didn’t answer” card in their language, or your last good card marked as not up to date. The learner is never told why. Only you, as the app’s owner, see the reason:

  • Your reply: invalid (not a card Lingara can read), empty (nothing left to show after the limits) or too_large (more than 32 KiB).
  • Reaching your server: timeout, http_error (any status other than 200), transport (a connection or TLS failure, such as an expired certificate) or blocked (a render_url that leads to a private or IP-literal address).
  • Lingara’s side: unavailable (Lingara could not sign the request) or busy (too many requests already in flight).
Code sample language

TypeScript

function todayCard(streak: number): Card {
  return card()
    .heading("Today's five", 1)
    .term({ word: "雨", reading: "yǔ", gloss: "rain", lang: "zh" })
    .list([item.text("Review 3 words"), item.term({ word: "二", reading: "èr" })])
    .button(`Done (${streak})`, "done")
    .build(); // Throws CardLimitError naming the rule the relay would clamp.
}

Rust

fn today_card(title: &str, lang: &str) -> Result<lingara_apps::Card, BoxError> {
    Ok(card()
        .heading(title, 1)
        .term(Term::new("雨").reading("yǔ").gloss("rain").lang(lang))
        .list([item::text("Say it aloud"), item::term(Term::new("下雨").gloss("to rain"))])
        .button("Next word", "next")
        .build()?)
}

Go

card, err := lingaraapps.NewCard().
	Heading("Today", 1).
	Term(lingaraapps.Term{Word: "雨", Reading: "yǔ", Gloss: "rain", Lang: "zh"}).
	List(
		lingaraapps.Item.Text("Say it aloud three times."),
		lingaraapps.Item.Term(lingaraapps.Term{Word: "雨天", Gloss: "rainy day", Lang: "zh"}),
	).
	Button("Next", "next").
	Build()
if err != nil {
	// A *lingaraapps.CardLimitError names the rule the card breaks,
	// such as text_length or list_items. Nothing is cut for you.
	return lingaraapps.Card{}, err
}

Java

import com.getlingara.apps.Card;
import com.getlingara.apps.Item;

Card card =
    Card.card()
        .heading("Today's five", 1)
        .term("雨", "yǔ", "rain", "zh")
        .list(Item.text("Say it aloud"), Item.term("二", "èr", "two", "zh"))
        .button("Next", "next")
        .build(); // a CardLimitException names the first rule the card breaks

Kotlin

import com.getlingara.apps.kotlin.Card
import com.getlingara.apps.kotlin.card

val c =
    card {
        heading("Today's five", 1)
        term("雨", reading = "yǔ", gloss = "rain", lang = "zh")
        list(item.text("Say it aloud"), item.term("二", reading = "èr", gloss = "two", lang = "zh"))
        button("Next", "next")
    } // a CardLimitException names the first rule the card breaks

Ruby

def self.today_card(streak)
  Lingara::Apps.card
    .heading("Today's five", 1)
    .term(word: "雨", reading: "yǔ", gloss: "rain", lang: "zh")
    .list([Lingara::Apps.item.text("Review 3 words"), Lingara::Apps.item.term(word: "二", reading: "èr")])
    .button("Done (#{streak})", "done")
    .build # Raises Lingara::Apps::CardLimitError naming the rule Lingara would clamp.
end

PHP

use Lingara\Apps\Card;
use Lingara\Apps\Generated\Card as CardModel;
use Lingara\Apps\Item;

function todayCard(int $streak): CardModel
{
    return Card::create()
        ->heading("Today's five", 1)
        ->term('雨', reading: 'yǔ', gloss: 'rain', lang: 'zh')
        ->list([Item::text('Review 3 words'), Item::term('二', reading: 'èr')])
        ->button("Done ({$streak})", 'done')
        ->build(); // Throws CardLimitException naming the rule the relay would clamp.
}