Changelog
View SourceAll 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
frindicatif tense order is now level-monotonic — previously grouped by time frame (all pasts, level-sorted, then all futures, level-sorted), which putpasse_simple/passe_anterieur(C1) ahead offutur_simple(A2) inconjugation_paradigm("fr"). Reordered topresent, passe_compose, imparfait, futur_simple, plus_que_parfait, futur_anterieur, passe_simple, passe_anterieur(A1, A2, A2, A2, B1, B2, C1, C1), matchingit.jsonand every other French mode. Consumers that positionally indexed or snapshot-tested the French indicatif tense order must update. Seedev_docs/2026-07-18-q2-french-indicative-tense-order.md.
Fixed
- Adding a new conjugation file now triggers recompilation —
BeamlabLanguages.Conjugationglobspriv/data/conjugation/*.jsonat compile time, but only files that already existed were declared as@external_resource, so dropping in a new<code>.jsonleft the compiled module stale untilmix 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 precommitnow actually runs the tests — aliases execute in thedevenvironment, wheremix testrefuses to run, so the alias's finalteststep always aborted with the "running in the dev environment" error. The suite is now invoked in a subprocess withMIX_ENV=test.persons/2no longer raises on non-keyword opts —persons("fr", :singular)raisedFunctionClauseErrorwhile 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 byjava.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 curatedyientry exists, andshis ambiguous betweensrandhr. - Compile-time validation of conjugation files —
priv/data/conjugation/*.jsonfiles are validated at compile time viaBeamlabLanguages.Conjugation.Validation.validate!/2before loading. Missingpersons/paradigm, missing or emptykey/label_native/label_en, duplicate keys, person keys without a grammatical-number suffix, and non-string tenselevels now fail the compile with anArgumentErrornaming 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-emptykey/label_native/label_enon all groups, persons, modes, and tenses; tense levels validated againstlevels(level_system(code)); person keys required to be identical across paradigms; and every paradigm code must exist inlanguages.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?/2—BeamlabLanguages.reflexive?(code, lemma)returnstrueiff 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-rsiending ("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. Returnsfalsefor languages without a curated rule and for unknown /nil/ non-string input.persons/2—BeamlabLanguages.persons(code, number: :singular | :plural | :dual)returns only the persons in that grammatical number, in teaching order. With no:numberoption it equalspersons/1. Returnsnilfor uncurated languages (likepersons/1) and[]when no person matches the requested number.
Changed
persons/1entries now carry a:numberkey (:singular,:plural, or:dual;nilfor an unrecognised key suffix), derived from the person key ("1sg"→:singular,"3pl"→:plural). Additive — existing:key/:label_native/:label_enkeys 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/1already maps a system key to its ladder (levels("cefr")→["A1", …, "C2"]) andlanguage_levels/1(v0.5.0) maps a language code straight to it (language_levels("fr")→["A1", …, "C2"]). Callinglevels("fr")returns[]because"fr"is a language, not a system key — uselanguage_levels/1there.
0.6.0 - 2026-06-14
Added
- Italian verb conjugation paradigm —
priv/data/conjugation/it.jsonadds a full Italian paradigm, sohas_verb_conjugation?("it")is nowtrueandconjugation_paradigm("it")returns the four modes (indicativo, congiuntivo, condizionale, imperativo) with CEFR-levelled tenses. Person slots reuse the shared1sg/2sg/3sg/1pl/2pl/3plkeys (io/tu/lui-lei/noi/voi/loro labels);indicativo/presenteandimperativo/presenteare levelledA1. Data-only change; no API additions.
0.5.0 - 2026-06-06
Added
- Language → level-system mapping —
BeamlabLanguages.level_system/1returns 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") returnsnilbecause 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 /nilinput returnsnil. language_levels/1— convenience forlevels(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/1now carries a:levelkey (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. Returnsnilfor unknown codes / modes / tenses and for tenses whose level is unknown.
0.3.0 - 2026-05-15
Added
- Proficiency level systems —
BeamlabLanguages.level_systems/0,levels/1,level_system_label/1, andlevel_info/2expose curated CEFR, JLPT, and HSK data. Levels are returned in pedagogical order (A1→C2, N5→N1, HSK1→HSK6). Data lives inpriv/data/levels.jsonand is embedded at compile time with zero runtime dependencies. - Development tooling — Added
credoanddialyxiras dev dependencies. Theprecommitalias now runscompile,format,credo,dialyzer, andtest.
0.2.0 - 2026-05-08
Added
- Pedagogical verb conjugation metadata — four new functions on
BeamlabLanguages:has_verb_conjugation?/1—trueiff a paradigm is curated for the language. Data-driven contract: stays in sync withconjugation_paradigm/1. Returnsfalsefor 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), ornilwhen 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_enconvention — 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.Languagestruct with six fields:code,name,native_name,direction,has_gender,genders.- Public API on
BeamlabLanguages(10 functions, all with@specand doctests):get/1— fetch aLanguagestruct by code, ornillist/0— every known language, sorted by codelist_codes/0— every known 2-letter base code, sortedhas_gender?/1— predicate,falsefor unknown /nilgenders/1— gender list,[]for unknown / non-gendered languagesdirection/1—:ltror:rtl,:ltrfor unknownname/1— canonical English name, ornilnative_name/1— endonym, ornilnormalize/1— collapse BCP 47 / sloppy casing to a 2-letter baseknown?/1— sugar overget/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.