Skip to content

Repository files navigation

mflash

An open, decentralization-friendly flashcard deck format.

Current draft: mflash v6. The reference implementation is the Rust module schema/mflash_v6.rs; the normative contract for non-Rust implementations is the JSON Schema generated from it. Everything older (v1–v5) is kept for history and migration — see Legacy versions.

Design pillars

  • Decentralization & torrent friendly. A deck is a plain directory (deck.json + manifest.json + media/) you can commit to git or seed as a torrent. Media is content-addressed by SHA-256 in the manifest, so identical files dedupe across decks, chunks verify independently, and nothing central is required. Decks carry UUIDs, an SPDX license field, and forked_from lineage so they can be shared, remixed, and re-shared.
  • Polyglot by default. A deck declares default_term_lang and default_def_lang (BCP-47). Any individual piece of text can override its language by writing { "value": "chien", "lang": "fr" } instead of a bare string — one field, per card, per example, per translation.
  • Image occlusion (and five other card kinds) built in. basic, cloze (Anki-compatible {{c1::…}} markers), image_occlusion (rect / ellipse / polygon masks in normalized 0–1 coordinates, so masks survive resizing), multiple_choice, listening, and media_prompt.
  • Content and study state never mix. Review history lives in a separate .progress file with an append-only review log, so studying never rewrites the shared, torrented, version-controlled deck — and any scheduler (SM-2, FSRS, Leitner) can rebuild its state from the raw log.

The 30-second version

deck.json:

{
  "format": "mflash",
  "version": 6,
  "id": "0b2e7c1a-3f4d-4e8a-9c6b-2d1f0a9e5b77",
  "title": "Japanese Core",
  "default_term_lang": "ja",
  "default_def_lang": "en",
  "cards": [
    { "id": "5d9c2a44-7e0f-4b6e-8b1a-c3d2e1f0a9b8",
      "content": { "basic": { "term": "", "definition": "dog" } } },
    { "id": "9e8d7c6b-5a49-4382-9170-ffeeddccbbaa",
      "content": { "basic": {
        "term": "",
        "definition": { "value": "chat", "lang": "fr" } } } }
  ]
}

The second card overrides just its definition language — that is the whole polyglot override mechanism.

How language resolution works

Every learner-facing string is a Text. Resolution is a two-step chain — own tag first, then the deck default for that field's side:

flowchart LR
    T["Text field<br/>(term, definition, hint, …)"] --> Q{"has its own<br/>lang tag?"}
    Q -- "yes — value + lang object" --> OWN["use it<br/>(per-card override)"]
    Q -- "no — bare string" --> SIDE{"which side is<br/>the field on?"}
    SIDE -- "term side:<br/>term · cloze text · transcript<br/>prompt · occlusion answer" --> TL["deck.default_term_lang"]
    SIDE -- "definition side:<br/>definition · translations<br/>answer · hint · notes" --> DL["deck.default_def_lang"]

    style OWN fill:#d5e8d4,stroke:#82b366
    style TL fill:#dae8fc,stroke:#6c8ebf
    style DL fill:#dae8fc,stroke:#6c8ebf
Loading

The card model

One envelope (identity, tags, attachments) around exactly one typed payload:

flowchart TD
    C["Card"] --> ENV["envelope<br/>id · tags · media · extensions"]
    C --> CT["content — exactly one of:"]
    CT --> B["basic<br/>term / definition"]
    CT --> CL["cloze<br/>Anki-style cN markers"]
    CT --> IO["image_occlusion<br/>masks over an image"]
    CT --> MC["multiple_choice<br/>1+ correct choices"]
    CT --> LI["listening<br/>audio → transcript"]
    CT --> MP["media_prompt<br/>media → free answer"]

    style C fill:#ffe6cc,stroke:#d79b00
    style ENV fill:#f5f5f5,stroke:#666666
    style IO fill:#d5e8d4,stroke:#82b366
Loading

Occlusion masks use normalized 0–1 coordinates (rect, ellipse, or polygon), so they survive any image resize. Review history keys on stable per-mask and per-choice ids.

Package layout

mflash v6 package flow: authoring directory, packed .mflash, progress file beside

Authoring / git / torrent source is a loose directory:

Japanese/
├── deck.json             one Deck (canonical JSON; RON supported as an editing view)
├── manifest.json         logical path → { sha256, bytes, mime }
└── media/
    ├── inu.mp3           human-readable names while authoring
    └── cell.png

Japanese.mflash (a ZIP with media STORED uncompressed, files named by their SHA-256) is an export/transport artifact only, produced by pack and consumed by unpack. Study progress lives next to the package in a .progress file, never inside it.

For implementers

You are writing… Start here
A reader/writer in any language schema/mflash-v6.schema.json (deck), …-manifest, …-progress
Rust schema/mflash_v6.rs — serde types, Deck::validate(), Deck::validate_against(&Manifest), round-trip tests
A test suite examples/v6-kitchen-sink/ — every card kind, polyglot overrides, a real content-addressed manifest

The Rust types are the reference implementation; the generated JSON Schemas are the normative contract. They cannot drift: CI regenerates the schemas from the types and fails on any diff.

cargo test                                        # reference impl + example guard
cargo run --features jsonschema --bin gen-schema  # regenerate normative schemas
python scripts/validate.py <deck.json> schema/mflash-v6.schema.json

Reader rules (details in the module docs of schema/mflash_v6.rs):

  • Ignore unknown fields (minor revisions add fields).
  • Refuse decks whose min_reader_version exceeds what you implement, with a clear "needs a newer reader" message (new card kinds are major-version events).
  • Treat .mflash and the loose directory as equivalent after loading.
  • Never write scheduling state into deck.json.

Repository map

schema/     mflash_v6.rs (reference impl) + generated v6 JSON Schemas + legacy schemas
specs/      prose specs (v1–v4 history; v6 is specified by schema/ + module docs)
examples/   example decks; v6-kitchen-sink/ is the canonical one
scripts/    Python validators and v1→v2→v3 migrators
docs/       decision records and diagrams

Legacy versions

Version Form Status
v6 Rust reference + generated JSON Schema current draft
v5 schema/mflash-v5.proto superseded design study (protobuf)
v4 specs/mflash-v4.md superseded draft
v3 specs/mflash-v3.md, schema/mflash-v3-schema.json legacy, examples retained
v2 specs/mflash-v2.md, schema/mflash-v2.schema.json legacy stable
v1 specs/mflash-v1.md, schema/mflash-v1.schema.json legacy

Migration notes: MIGRATION.md. Design history: docs/decisions/, CHANGELOG.md.

License

Public domain (Unlicense). Decks themselves declare their own license via the SPDX license field.

About

Official specification, schemas, examples, and migration tools for the .mflash multilingual flashcard format.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages