Changelog

View Source

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

0.8.0 - 2026-07-18

Changed

  • fr indicatif tense order is now level-monotonic — previously grouped by time frame (all pasts, level-sorted, then all futures, level-sorted), which put passe_simple/passe_anterieur (C1) ahead of futur_simple (A2) in conjugation_paradigm("fr"). Reordered to present, passe_compose, imparfait, futur_simple, plus_que_parfait, futur_anterieur, passe_simple, passe_anterieur (A1, A2, A2, A2, B1, B2, C1, C1), matching it.json and every other French mode. Consumers that positionally indexed or snapshot-tested the French indicatif tense order must update. See dev_docs/2026-07-18-q2-french-indicative-tense-order.md.

Fixed

  • Adding a new conjugation file now triggers recompilationBeamlabLanguages.Conjugation globs priv/data/conjugation/*.json at compile time, but only files that already existed were declared as @external_resource, so dropping in a new <code>.json left the compiled module stale until mix compile --force. The directory itself is now registered as an external resource, so adding (or removing) a paradigm file recompiles automatically.
  • Documentation drift — the moduledoc no longer claims Norwegian uses ["c", "n"] genders (the data is ["m", "f", "n"], the standard three-gender analysis) or that French is the only curated paradigm (Italian shipped in v0.6.0); README install version bumped to ~> 0.7; duplicate / missing release-link references tidied.
  • mix precommit now actually runs the tests — aliases execute in the dev environment, where mix test refuses to run, so the alias's final test step always aborted with the "running in the dev environment" error. The suite is now invoked in a subprocess with MIX_ENV=test.
  • persons/2 no longer raises on non-keyword optspersons("fr", :singular) raised FunctionClauseError while every other public function tolerates sloppy input. Anything that isn't a keyword list is now treated as "no filter" and returns the full person list.

Added

  • Deprecated / legacy code aliases in normalize/1 — Java's legacy codes "iw""he" and "in""id" (as emitted by java.util.Locale, e.g. "iw_IL", "in_ID") and the removed ISO code "mo""ro" now collapse to their canonical entries, alongside the existing "nb" / "nn""no". "ji" (Yiddish) and "sh" (Serbo-Croatian) are intentionally unmapped: no curated yi entry exists, and sh is ambiguous between sr and hr.
  • Compile-time validation of conjugation filespriv/data/conjugation/*.json files are validated at compile time via BeamlabLanguages.Conjugation.Validation.validate!/2 before loading. Missing persons / paradigm, missing or empty key / label_native / label_en, duplicate keys, person keys without a grammatical-number suffix, and non-string tense levels now fail the compile with an ArgumentError naming the file and the exact problem, instead of a cryptic function-clause error inside the loader.
  • Paradigm-agnostic conjugation tests — every file in priv/data/conjugation/ is now checked for non-empty key / label_native / label_en on all groups, persons, modes, and tenses; tense levels validated against levels(level_system(code)); person keys required to be identical across paradigms; and every paradigm code must exist in languages.json. Italian also gets pinned shape tests (modes, tense order, verb_groups/1, tense_level/3) matching the French coverage, and the "c"-gender / Norwegian three-gender data is pinned against future doc drift.

0.7.0 - 2026-06-15

Added

  • reflexive?/2BeamlabLanguages.reflexive?(code, lemma) returns true iff a lemma is a reflexive / pronominal verb in the language. Encapsulates per-language morphology that consumers had been hardcoding: French's leading pronoun ("se laver", "s'appeler" — but not "semer"/"sentir") and Italian's enclitic -rsi ending ("chiamarsi", "mettersi"), which a French-only "se "/"s'" rule misses entirely. The lemma is lowercased and trimmed internally; the code is normalized like every other code-taking function. Returns false for languages without a curated rule and for unknown / nil / non-string input.
  • persons/2BeamlabLanguages.persons(code, number: :singular | :plural | :dual) returns only the persons in that grammatical number, in teaching order. With no :number option it equals persons/1. Returns nil for uncurated languages (like persons/1) and [] when no person matches the requested number.

Changed

  • persons/1 entries now carry a :number key (:singular, :plural, or :dual; nil for an unrecognised key suffix), derived from the person key ("1sg":singular, "3pl":plural). Additive — existing :key / :label_native / :label_en keys are unchanged — but code that asserts exact map equality on a person entry must account for the new key. Lets consumers split singular/plural blocks by attribute instead of hardcoding which person keys belong to each number.

Notes

  • No new level API was needed for the language → CEFR-ladder use case: levels/1 already maps a system key to its ladder (levels("cefr")["A1", …, "C2"]) and language_levels/1 (v0.5.0) maps a language code straight to it (language_levels("fr")["A1", …, "C2"]). Calling levels("fr") returns [] because "fr" is a language, not a system key — use language_levels/1 there.

0.6.0 - 2026-06-14

Added

  • Italian verb conjugation paradigmpriv/data/conjugation/it.json adds a full Italian paradigm, so has_verb_conjugation?("it") is now true and conjugation_paradigm("it") returns the four modes (indicativo, congiuntivo, condizionale, imperativo) with CEFR-levelled tenses. Person slots reuse the shared 1sg/2sg/3sg/1pl/2pl/3pl keys (io/tu/lui-lei/noi/voi/loro labels); indicativo/presente and imperativo/presente are levelled A1. Data-only change; no API additions.

0.5.0 - 2026-06-06

Added

  • Language → level-system mappingBeamlabLanguages.level_system/1 returns the proficiency system for a language code ("fr""cefr", "zh""hsk", "ja""jlpt"). CEFR is the default for any known language without a more specific system; Korean ("ko") returns nil because TOPIK isn't among the curated three (a known gap). Input is normalized like every other code-taking function, so "fr-FR", "ZH-Hans-CN", etc. work. Unknown / nil input returns nil.
  • language_levels/1 — convenience for levels(level_system(code)): the level keys for a language in pedagogical order. Returns [] for languages with no curated system and for unknown codes.

0.4.0 - 2026-06-05

Added

  • CEFR level on conjugation tenses — every tense in conjugation_paradigm/1 now carries a :level key (the CEFR / JLPT / HSK key at which the tense/mood is typically taught). It's a property of the tense in the language's curriculum, independent of any specific verb. All 16 French tenses are levelled A1→C2. Purely additive: existing keys are unchanged.
  • tense_level/3 — convenience reader that returns the level for a single (code, mode, tense) without walking the paradigm tree. Returns nil for unknown codes / modes / tenses and for tenses whose level is unknown.

0.3.0 - 2026-05-15

Added

  • Proficiency level systemsBeamlabLanguages.level_systems/0, levels/1, level_system_label/1, and level_info/2 expose curated CEFR, JLPT, and HSK data. Levels are returned in pedagogical order (A1→C2, N5→N1, HSK1→HSK6). Data lives in priv/data/levels.json and is embedded at compile time with zero runtime dependencies.
  • Development tooling — Added credo and dialyxir as dev dependencies. The precommit alias now runs compile, format, credo, dialyzer, and test.

0.2.0 - 2026-05-08

Added

  • Pedagogical verb conjugation metadata — four new functions on BeamlabLanguages:
    • has_verb_conjugation?/1true iff a paradigm is curated for the language. Data-driven contract: stays in sync with conjugation_paradigm/1. Returns false for uncurated languages even when they technically inflect verbs (English, Swedish, etc.) until paradigms are added.
    • verb_groups/1 — pedagogical group system (e.g. French's -er / -ir / -re), or nil when no paradigm exists OR when the language has a paradigm but no meaningful group system.
    • persons/1 — person/pronoun list with stable keys ("1sg""3pl" for French; future languages may add dual or gender-split persons).
    • conjugation_paradigm/1 — modes-and-tenses tree. Order is teaching order, opinionated and stable across versions.
  • label_native + label_en convention — every mode, tense, group, and person carries both labels: the term in the target language ("Indicatif") and the canonical English rendering ("Indicative"). Useful for learner UIs that show either side of a translation.
  • French paradigm in priv/data/conjugation/fr.json: 3 verb groups, 6 persons, 4 modes (indicatif / subjonctif / conditionnel / impératif) covering 16 tenses total.
  • Per-language data files under priv/data/conjugation/<code>.json. Each file is read at compile time as an @external_resource, so edits trigger recompile with no runtime I/O.

Notes

  • v0.2 ships French only. More languages will land as consumers need them; the schema is intentionally not locked in for non-Romance / non-Indo-European paradigms (Russian aspect pairs, Arabic stems, Slovenian dual, etc.) — those may extend the shape.

0.1.0 - 2026-05-07

Initial release.

Added

  • BeamlabLanguages.Language struct with six fields: code, name, native_name, direction, has_gender, genders.
  • Public API on BeamlabLanguages (10 functions, all with @spec and doctests):
    • get/1 — fetch a Language struct by code, or nil
    • list/0 — every known language, sorted by code
    • list_codes/0 — every known 2-letter base code, sorted
    • has_gender?/1 — predicate, false for unknown / nil
    • genders/1 — gender list, [] for unknown / non-gendered languages
    • direction/1:ltr or :rtl, :ltr for unknown
    • name/1 — canonical English name, or nil
    • native_name/1 — endonym, or nil
    • normalize/1 — collapse BCP 47 / sloppy casing to a 2-letter base
    • known?/1 — sugar over get/1
  • BCP 47 normalization runs internally on every code-taking function — "en-US", "FR", " fr " all just work. Underscore separator ("en_US") is also accepted, and "nb" / "nn" (Bokmål / Nynorsk, as emitted by POSIX locales and browsers) collapse to "no".
  • 54 curated languages in priv/data/languages.json, covering the top-spoken languages plus all CEFR / JLPT / HSK targets.
  • Compile-time data loading — no runtime file I/O, no GenServer, no ETS, zero runtime dependencies.