feat: breid taalbibliotheek uit met 1000 woorden (v0.4.43-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 59s

This commit is contained in:
Ramon 2026-07-21 18:50:47 +02:00
parent 17ee47dd09
commit ba4483d5d3
6 changed files with 284 additions and 27 deletions

View file

@ -1 +1 @@
0.4.42-beta 0.4.43-beta

View file

@ -2,7 +2,7 @@
"use strict"; "use strict";
/* version shown until /api/version resolves (or if the fetch fails, e.g. offline). /* version shown until /api/version resolves (or if the fetch fails, e.g. offline).
Kept in sync by hand with the VERSION file at the repo root on every release. */ Kept in sync by hand with the VERSION file at the repo root on every release. */
const VERSION = "0.4.42-beta"; const VERSION = "0.4.43-beta";
(function(){ (function(){
const tag = document.getElementById("verTag"); const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION; tag.textContent = "v"+VERSION;

View file

@ -71,7 +71,15 @@ const LANGUAGE_THEME_DEFS = [
{id:"home", icon:"🎒", nl:"Thuis & school", en:"Home & school", colors:["#f1ecff","#d9ccff"]}, {id:"home", icon:"🎒", nl:"Thuis & school", en:"Home & school", colors:["#f1ecff","#d9ccff"]},
{id:"body", icon:"👕", nl:"Lichaam & kleding", en:"Body & clothes", colors:["#ffeaf1","#ffcbdc"]}, {id:"body", icon:"👕", nl:"Lichaam & kleding", en:"Body & clothes", colors:["#ffeaf1","#ffcbdc"]},
{id:"actions", icon:"🏃", nl:"Doen", en:"Actions", colors:["#fff8d9","#f6e8a4"]}, {id:"actions", icon:"🏃", nl:"Doen", en:"Actions", colors:["#fff8d9","#f6e8a4"]},
{id:"feelings", icon:"🙂", nl:"Gevoelens", en:"Feelings", colors:["#e9f5ff","#c8e4fa"]} {id:"feelings", icon:"🙂", nl:"Gevoelens", en:"Feelings", colors:["#e9f5ff","#c8e4fa"]},
{id:"people", icon:"👨‍👩‍👧", nl:"Mensen & familie", en:"People & family", colors:["#fff0f4","#ffd3df"]},
{id:"play", icon:"🏀", nl:"Spelen & sport", en:"Play & sports", colors:["#fff4dc","#ffdca8"]},
{id:"places", icon:"🏘️", nl:"Plekken & beroepen", en:"Places & jobs", colors:["#e8f3ff","#c9e2fb"]},
{id:"time", icon:"📅", nl:"Tijd & feest", en:"Time & celebrations", colors:["#f2edff","#ddd1ff"]},
{id:"arts", icon:"🎨", nl:"Kunst & muziek", en:"Arts & music", colors:["#fff0fa","#f4ccec"]},
{id:"science", icon:"🔬", nl:"Onderzoeken & techniek", en:"Science & technology", colors:["#e7f7f5","#bfe9e3"]},
{id:"learning", icon:"📚", nl:"Taal & rekenen", en:"Language & maths", colors:["#fff7df","#f3dfa6"]},
{id:"society", icon:"🌍", nl:"Samenleven & wereld", en:"Society & world", colors:["#e8f1ff","#c8daf7"]}
]; ];
const LANGUAGE_CONCEPTS = [ const LANGUAGE_CONCEPTS = [
["monkey","animals","aap","monkey","🐵"], ["monkey","animals","aap","monkey","🐵"],
@ -218,9 +226,230 @@ const LANGUAGE_CONCEPTS = [
["proud","feelings","trots","proud","😌"], ["proud","feelings","trots","proud","😌"],
["calm","feelings","rustig","calm","🙂"], ["calm","feelings","rustig","calm","🙂"],
["surprised","feelings","verrast","surprised","😮"], ["surprised","feelings","verrast","surprised","😮"],
["excited","feelings","enthousiast","excited","🤩"] ["excited","feelings","enthousiast","excited","🤩"],
["bird","animals","vogel","bird","🐦"],
["rabbit","animals","konijn","rabbit","🐰"],
["fox","animals","vos","fox","🦊"],
["butterfly","animals","vlinder","butterfly","🦋"],
["snail","animals","slak","snail","🐌"],
["ladybird","animals","lieveheersbeestje","ladybird","🐞"],
["spider","animals","spin","spider","🕷️"],
["turtle","animals","schildpad","turtle","🐢"],
["dolphin","animals","dolfijn","dolphin","🐬"],
["elephant","animals","olifant","elephant","🐘"],
["giraffe","animals","giraf","giraffe","🦒"],
["sheep","animals","schaap","sheep","🐑"],
["banana","food","banaan","banana","🍌"],
["orange","food","sinaasappel","orange","🍊"],
["watermelon","food","watermeloen","watermelon","🍉"],
["cherry","food","kers","cherry","🍒"],
["tomato","food","tomaat","tomato","🍅"],
["potato","food","aardappel","potato","🥔"],
["pancake","food","pannenkoek","pancake","🥞"],
["honey","food","honing","honey","🍯"],
["chocolate","food","chocolade","chocolate","🍫"],
["ice","food","ijs","ice","🍦"],
["sandwich","food","boterham","sandwich","🥪"],
["yoghurt","food","yoghurt","yoghurt","🥣"],
["mountain","nature","berg","mountain","⛰️"],
["island","nature","eiland","island","🏝️"],
["beach","nature","strand","beach","🏖️"],
["river","nature","rivier","river","🏞️"],
["lightning","nature","bliksem","lightning","⚡"],
["fog","nature","mist","fog","🌫️"],
["autumn","nature","herfst","autumn","🍂"],
["spring","nature","lente","spring","🌼"],
["summer","nature","zomer","summer","🌞"],
["winter","nature","winter","winter","⛄"],
["seed","nature","zaad","seed","🌰"],
["motorcycle","travel","motor","motorcycle","🏍️"],
["ambulance","travel","ambulance","ambulance","🚑"],
["firetruck","travel","brandweerauto","firetruck","🚒"],
["policecar","travel","politieauto","policecar","🚓"],
["subway","travel","metro","subway","🚇"],
["canoe","travel","kano","canoe","🛶"],
["bridge","travel","brug","bridge","🌉"],
["tunnel","travel","tunnel","tunnel","🚇"],
["trafficlight","travel","stoplicht","trafficlight","🚦"],
["station","travel","station","station","🚉"],
["airport","travel","vliegveld","airport","🛫"],
["mirror","home","spiegel","mirror","🪞"],
["sofa","home","bank","sofa","🛋️"],
["toilet","home","wc","toilet","🚽"],
["shower","home","douche","shower","🚿"],
["broom","home","bezem","broom","🧹"],
["spoon","home","lepel","spoon","🥄"],
["fork","home","vork","fork","🍴"],
["plate","home","bord","plate","🍽️"],
["computer","home","computer","computer","💻"],
["phone","home","telefoon","phone","📱"],
["window","home","raam","window","🪟"],
["finger","body","vinger","finger","☝️"],
["thumb","body","duim","thumb","👍"],
["knee","body","knie","knee","🦵"],
["heart","body","hart","heart","❤️"],
["brain","body","hersenen","brain","🧠"],
["bone","body","bot","bone","🦴"],
["tongue","body","tong","tongue","👅"],
["blood","body","bloed","blood","🩸"],
["hat","body","hoed","hat","👒"],
["boot","body","laars","boot","🥾"],
["scarf","body","sjaal","scarf","🧣"],
["cook","actions","koken","cook","🧑‍🍳"],
["bake","actions","bakken","bake","🧁"],
["cycle","actions","fietsen","cycle","🚴"],
["drive","actions","rijden","drive","🚙"],
["open","actions","openen","open","📂"],
["close","actions","sluiten","close","📕"],
["wash","actions","wassen","wash","🧼"],
["help","actions","helpen","help","🤝"],
["listen","actions","luisteren","listen","👂"],
["smell","actions","ruiken","smell","👃"],
["shy","feelings","verlegen","shy","😊"],
["curious","feelings","nieuwsgierig","curious","🧐"],
["confused","feelings","verward","confused","😕"],
["lonely","feelings","eenzaam","lonely","😔"],
["friendly","feelings","vriendelijk","friendly","🙂"],
["brave","feelings","dapper","brave","🦸"],
["jealous","feelings","jaloers","jealous","😒"],
["bored","feelings","verveeld","bored","🥱"],
["relieved","feelings","opgelucht","relieved","😌"],
["silly","feelings","grappig","silly","🤪"],
["baby","people","baby","baby","👶"],
["child","people","kind","child","🧒"],
["boy","people","jongen","boy","👦"],
["girl","people","meisje","girl","👧"],
["mother","people","moeder","mother","👩"],
["father","people","vader","father","👨"],
["sister","people","zus","sister","👧"],
["brother","people","broer","brother","👦"],
["grandmother","people","oma","grandmother","👵"],
["grandfather","people","opa","grandfather","👴"],
["family","people","familie","family","👨‍👩‍👧‍👦"],
["friend","people","vriend","friend","🧑‍🤝‍🧑"],
["teacher","people","leerkracht","teacher","🧑‍🏫"],
["pupil","people","leerling","pupil","🧑‍🎓"],
["neighbor","people","buur","neighbor","🏘️"],
["woman","people","vrouw","woman","👩"],
["man","people","man","man","👨"],
["person","people","mens","person","🧑"],
["twins","people","tweeling","twins","👯"],
["group","people","groep","group","👥"],
["football","play","voetbal","football","⚽"],
["basketball","play","basketbal","basketball","🏀"],
["tennis","play","tennis","tennis","🎾"],
["hockey","play","hockey","hockey","🏑"],
["volleyball","play","volleybal","volleyball","🏐"],
["skating","play","schaatsen","skating","⛸️"],
["ski","play","ski","ski","🎿"],
["cycling","play","wielrennen","cycling","🚴"],
["running","play","hardlopen","running","🏃"],
["playground","play","speeltuin","playground","🛝"],
["bowling","play","bowlen","bowling","🎳"],
["slide","play","glijbaan","slide","🛝"],
["kite","play","vlieger","kite","🪁"],
["puzzle","play","puzzel","puzzle","🧩"],
["game","play","spel","game","🎮"],
["dice","play","dobbelsteen","dice","🎲"],
["marble","play","knikker","marble","🔵"],
["teddy","play","knuffel","teddy","🧸"],
["cards","play","kaarten","cards","🃏"],
["chess","play","schaken","chess","♟️"],
["medal","play","medaille","medal","🏅"],
["shop","places","winkel","shop","🏪"],
["bakery","places","bakkerij","bakery","🥐"],
["hospital","places","ziekenhuis","hospital","🏥"],
["doctor","places","dokter","doctor","🧑‍⚕️"],
["police","places","politie","police","👮"],
["firefighter","places","brandweer","firefighter","🧑‍🚒"],
["farm","places","boerderij","farm","🏡"],
["farmer","places","boer","farmer","🧑‍🌾"],
["library","places","bibliotheek","library","📚"],
["librarian","places","bibliothecaris","librarian","📚"],
["park","places","park","park","🏞️"],
["zoo","places","dierentuin","zoo","🦒"],
["museum","places","museum","museum","🏛️"],
["restaurant","places","restaurant","restaurant","🍽️"],
["kitchen","places","keuken","kitchen","🍳"],
["classroom","places","klas","classroom","🏫"],
["office","places","kantoor","office","🏢"],
["garage","places","garage","garage","🚗"],
["postman","places","postbode","postman","📬"],
["nurse","places","verpleegkundige","nurse","🧑‍⚕️"],
["dentist","places","tandarts","dentist","🦷"],
["builder","places","bouwer","builder","👷"],
["chef","places","kok","chef","🧑‍🍳"],
["hairdresser","places","kapper","hairdresser","💇"],
["market","places","markt","market","🛍️"],
["city","places","stad","city","🏙️"],
["village","places","dorp","village","🏘️"],
["church","places","kerk","church","⛪"],
["pool","places","zwembad","pool","🏊"],
["morning","time","ochtend","morning","🌅"],
["afternoon","time","middag","afternoon","🌤️"],
["evening","time","avond","evening","🌇"],
["night","time","nacht","night","🌙"],
["today","time","vandaag","today","📅"],
["tomorrow","time","morgen","tomorrow","➡️"],
["yesterday","time","gisteren","yesterday","⬅️"],
["day","time","dag","day","☀️"],
["week","time","week","week","🗓️"],
["month","time","maand","month","📆"],
["year","time","jaar","year","🎆"],
["birthday","time","verjaardag","birthday","🎂"],
["party","time","feest","party","🥳"],
["candle","time","kaars","candle","🕯️"],
["balloon","time","ballon","balloon","🎈"],
["flag","time","vlag","flag","🚩"],
["crown","time","kroon","crown","👑"],
["christmas","time","kerst","christmas","🎄"],
["easter","time","pasen","easter","🐣"],
["garland","time","slinger","garland","🎊"],
["calendar","time","kalender","calendar","📅"],
["hour","time","uur","hour","🕐"],
["minute","time","minuut","minute","⏱️"],
["season","time","seizoen","season","🍁"]
].map(([id,theme,nl,en,pic])=>({id,theme,nl,en,pic})); ].map(([id,theme,nl,en,pic])=>({id,theme,nl,en,pic}));
/* Ruim duizend aanvullende Nederlandse woorden, geordend in woordfamilies.
Iedere regel loopt bewust van een kort, concreet basiswoord naar langere
afleidingen, samenstellingen en schooltaal. De positie op de regel bepaalt
het eerste leerjaar waarin het woord wordt aangeboden; hogere leerjaren
behouden de eerder aangeboden woorden. Woorden die al in LANGUAGE_CONCEPTS
staan worden hieronder automatisch overgeslagen. */
const LANGUAGE_NL_PROGRESSION_FAMILIES={
animals:"🐈|kat,poes,kitten,kater,katten,poesje,kattenbak,kattenvoer,huiskat,kattenluik,zwerfkat,kattenkwaad\n🐕|hond,pup,reu,teef,honden,pupje,hondenhok,hondenriem,waakhond,hondensport,geleidehond,hondenbezitter\n🐴|paard,pony,veulen,merrie,hengst,paardenstal,paardrijden,renpaard,paardensprong,paardentrailer,paardenverzorging,paardrijwedstrijd\n🐦|vogel,mus,merel,kraai,duif,nestje,vogelvoer,zangvogel,trekvogel,vogelkooi,vogelkenner,vogelbescherming\n🐟|vis,karper,baars,haai,rog,visje,vijvervis,zeedier,visnet,vissersboot,onderwaterdier,waterdierenverblijf\n🐞|mier,bij,wesp,kever,vlinder,rups,insectenhotel,bijenkorf,sprinkhaan,lieveheersbeestje,insectenkenner,insectenonderzoek\n🐄|koe,kalf,geit,schaap,varken,lammetje,kippenhok,melkkoe,boerderijdier,dierenweide,dierenverzorger,veehouderij",
food:"🍞|brood,bol,toast,korst,kruimel,broodje,volkorenbrood,broodmes,bakkerij,broodbeleg,broodtrommel,broodbereiding\n🍎|fruit,appel,peer,pruim,kers,appeltje,fruitsap,fruitmand,boomgaard,vruchtvlees,fruitsoorten,fruitteeltbedrijf\n🥛|melk,karnemelk,room,boter,vla,melkpak,melkglas,zuivel,melkboer,melkproduct,zuivelfabriek,voedingsmiddel\n🧀|kaas,brie,gaten,plak,korst,kaasje,kaasplank,kaasschaaf,kaaswinkel,smeerkaas,kaasbereiding,zuivelproduct\n🥔|aardappel,friet,puree,schil,knol,pieper,aardappelmes,stamppot,aardappelveld,aardappelplant,aardappelgerecht,aardappelverwerking\n🥕|groente,wortel,prei,kool,boon,erwtje,groentesoep,snijplank,moestuin,groentekraam,groentesoorten,groentegerecht\n🍰|taart,koek,snoep,cake,wafel,gebakje,slagroom,taartpunt,bakvorm,koekjesdeeg,feestmaaltijd,suikergehalte",
nature:"🌳|boom,stam,tak,blad,wortel,boompje,boomschors,boomkroon,loofboom,naaldboom,boomsoorten,bomenonderzoek\n🌷|bloem,roos,tulp,stengel,knop,bloempje,bloempot,bloembol,boeket,zonnebloem,bloemenweide,bloembestuiving\n💧|water,druppel,plas,sloot,beek,waterkant,drinkwater,waterput,grondwater,waterleiding,waterzuivering,waterkringloop\n☀|zon,maan,ster,licht,schaduw,zonnetje,zonlicht,maansikkel,sterrenhemel,zonsopgang,zonnestraling,sterrenstelsel\n🌧|regen,wolk,wind,mist,bui,regendruppel,regenjas,wolkbreuk,weersbericht,regenboog,neerslaggebied,weersverwachting\n❄|sneeuw,ijs,vorst,hagel,vlok,sneeuwpop,ijskoud,sneeuwbal,winterweer,sneeuwstorm,temperatuurdaling,winterlandschap\n🌱|aarde,grond,zaad,kiem,plant,zaadje,potgrond,plantengroei,bodemlaag,bodemvruchtbaarheid,voedingsstoffen,kiemingsproces",
travel:"🚗|auto,bus,taxi,rit,band,autootje,autodeur,autoruit,parkeerplek,autoweg,automobilist,verkeersveiligheid\n🚲|fiets,step,pedaal,bel,stuur,fietsje,fietspad,fietspomp,bakfiets,fietstocht,fietsenmaker,fietsverlichting\n🚂|trein,tram,rail,spoor,rit,treintje,perron,station,spoorlijn,treinreis,treinmachinist,spoorwegovergang\n⛵|boot,schip,kano,zeil,roeispaan,bootje,zeilboot,reddingsboei,haven,scheepsdek,binnenvaartschip,scheepvaartverkeer\n✈|vliegtuig,jet,vleugel,piloot,vlucht,vliegveld,landingsbaan,luchtvaart,vliegreis,verkeerstoren,passagiersvliegtuig,luchtvaartmaatschappij\n🛣|weg,pad,brug,tunnel,bocht,wegdek,kruispunt,rotonde,rijbaan,wegwijzer,infrastructuur,verkeersknooppunt\n🚦|licht,bord,streep,file,route,stoplicht,verkeersbord,zebrapad,voorrang,weggebruiker,verkeersregelaar,verkeerssituatie",
home:"🏠|huis,flat,hut,dak,muur,huisje,voordeur,woonkamer,rijtjeshuis,woonwijk,huizenblok,woninginrichting\n🛏|bed,kast,lamp,deken,kussen,bedje,nachtkast,bedlamp,slaapkamer,dekbedovertrek,slaapvertrek,slaapkamerraam\n🪑|stoel,tafel,bank,kruk,kast,stoeltje,tafelblad,eettafel,boekenkast,stoelzitting,meubelwinkel,woonkamermeubel\n🚪|deur,raam,slot,sleutel,bel,deurtje,deurklink,vensterbank,raamkozijn,voordeurbel,inbraakbeveiliging,toegangssysteem\n🍳|pan,pot,kom,bord,lepel,pannetje,koekenpan,keukenkast,aanrecht,afwasborstel,keukengerei,voedselbereiding\n🚿|bad,douche,wc,kraan,zeep,badje,handdoek,wastafel,badkamer,tandenborstel,waterbesparing,badkamertegel\n🧹|stof,vuil,bezem,dweil,spons,stofdoek,afwasmiddel,prullenbak,schoonmaak,schoonmaker,schoonmaakmiddel,huishoudelijkehulp",
body:"✋|hand,duim,vinger,nagel,pols,handje,vingertop,handpalm,wijsvinger,vingerafdruk,handbeweging,handvaardigheid\n🦶|voet,teen,hiel,enkel,knie,voetje,voetzool,knieschijf,onderbeen,voetstap,lichaamshouding,bewegingsapparaat\n🙂|hoofd,wang,kin,nek,keel,hoofdje,voorhoofd,achterhoofd,gezicht,gezichtsvorm,gelaatsuitdrukking,hoofdbescherming\n👁|oog,oor,neus,mond,lip,oogje,ooglid,neusgat,wenkbrauw,zintuig,gehoororgaan,waarnemingsvermogen\n🦷|tand,kies,tong,speeksel,gebit,tandje,tandvlees,tandenborstel,tandarts,mondzorg,tandheelkunde,gebitsverzorging\n👕|jas,shirt,trui,broek,rok,jasje,regenbroek,winterjas,kledingkast,kledingmaat,kledingstuk,kledingindustrie\n❤|hart,long,bloed,bot,spier,hartslag,bloedvat,longinhoud,skelet,spierkracht,bloedsomloop,ademhalingsstelsel",
actions:"🚶|gaan,lopen,stap,staan,zitten,wandelen,voetstap,doorlopen,oversteken,vooruitgaan,verplaatsing,bewegingsrichting\n🏃|rennen,racen,sprinten,haasten,vluchten,hardlopen,renbaan,wedloop,marathon,snelheidsmeter,hardloopwedstrijd,uithoudingsvermogen\n🤸|springen,hupsen,bukken,rollen,draaien,sprongetje,springtouw,koprol,evenwicht,lichaamsbeweging,bewegingsreeks,bewegingsoefening\n📖|lezen,boek,blad,zin,tekst,leesboek,voorlezen,leesbril,leeshoek,leesstrategie,leesvaardigheid,tekstinterpretatie\n✍|schrijven,pen,schrift,letter,woord,opschrijven,handschrift,schrijfboek,schrijfregel,schrijfopdracht,schrijfvaardigheid,tekstproductie\n🗣|praten,zeggen,vragen,roepen,fluisteren,vertellen,spreekbeurt,gesprek,uitspraak,woordkeuze,taalgebruik,gespreksvaardigheid\n🤝|helpen,delen,geven,halen,brengen,hulpje,samenwerken,meehelpen,ondersteunen,hulpverlening,samenwerkingsvorm,verantwoordelijkheid",
feelings:"😀|blij,leuk,fijn,lachen,vrolijk,glimlach,plezier,opgewekt,tevreden,blijheid,enthousiasme,geluksgevoel\n😢|sip,moe,verdriet,pijn,huilen,traantje,teleurgesteld,verdrietig,somber,eenzaamheid,teleurstelling,verdrietverwerking\n😠|boos,kwaad,ruzie,mopperen,grommen,boosheid,driftig,prikkelbaar,woedend,meningsverschil,frustratie,conflictbeheersing\n😨|bang,eng,schrik,zorgen,trillen,angstig,schrikken,onzeker,bezorgd,angstgevoel,onveiligheid,zelfbescherming\n🙂|rust,kalm,stil,zacht,ademen,rustig,ontspannen,geduldig,vertrouwd,ontspanning,zelfbeheersing,ademhalingsoefening\n😌|trots,dapper,sterk,durven,winnen,trotsheid,moedig,zelfstandig,zelfvertrouwen,doorzetter,doorzettingsvermogen,zelfwaardering\n🤩|nieuw,raar,wauw,vragen,kijken,verrast,benieuwd,nieuwsgierig,verwondering,belangstelling,onderzoekshouding,ontdekkingsvreugde",
people:"👨‍👩‍👧|familie,gezin,kind,baby,ouders,gezinnetje,familielid,stiefouder,grootouder,familieband,gezinssituatie,familiegeschiedenis\n👩|mama,moeder,vrouw,tante,dochter,moedertje,schoonmoeder,moederdag,moederfiguur,moederschap,ouderverzorging,gezinsverantwoordelijkheid\n👨|papa,vader,man,oom,zoon,vadertje,schoonvader,vaderdag,vaderfiguur,vaderschap,oudercontact,opvoedingsverantwoordelijkheid\n🧒|kind,jongen,meisje,kleuter,puber,kindje,schoolkind,leeftijdgenoot,jeugd,ontwikkeling,kinderrechten,identiteitsontwikkeling\n🧑🤝🧑|vriend,maat,buur,gast,club,vriendje,vriendschap,buurkind,kennismaking,groepsgenoot,vertrouwenspersoon,vriendschapsrelatie\n🧑🏫|juf,meester,leraar,mentor,coach,leerkracht,lesgever,schoolteam,begeleider,onderwijsassistent,onderwijsprofessional,leerlingbegeleiding\n👥|groep,team,klas,paar,kring,groepje,klasgenoot,teamlid,samenkomst,groepswerk,groepsvorming,groepsdynamiek",
play:"⚽|bal,doel,trap,team,veld,balletje,voetbal,doelman,wedstrijd,voetballer,voetbaltraining,competitiewedstrijd\n🏀|basket,korf,worp,score,punt,gooien,basketbal,dribbelen,balbezit,teamsport,sportvereniging,wedstrijdreglement\n🧩|puzzel,stuk,vorm,rand,plaat,puzzelstuk,legpuzzel,denkspel,oplossing,puzzelboek,probleemoplossing,denkstrategie\n🧸|pop,beer,blok,trein,auto,popje,knuffel,bouwblok,speelgoed,speelkamer,speelgoedwinkel,fantasiespel\n🎲|spel,beurt,dobbelsteen,kaart,pion,spelletje,spelregel,bordspel,tegenstander,winnaar,spelstrategie,gezelschapsspel\n🏊|zwemmen,duiken,bad,plons,slag,zwemband,zwembad,schoolslag,duikplank,zwemles,zwemvaardigheid,waterveiligheid\n🏅|sport,spel,prijs,beker,medaille,sporter,sportdag,training,kampioen,sportprestatie,sportiviteit,wedstrijdorganisatie",
places:"🏫|school,klas,plein,les,gym,schooltje,schoolbank,schoolplein,schoolgebouw,schooljaar,schoolorganisatie,onderwijsomgeving\n🏪|winkel,kassa,kar,mand,klant,winkeltje,winkelier,winkelstraat,boodschap,winkelcentrum,klantenservice,detailhandel\n🏥|arts,dokter,bed,zaal,zorg,ziekenhuis,verpleegkundige,wachtkamer,behandeling,zorgverlening,gezondheidszorg,ziekenhuisafdeling\n🏡|boer,stal,schuur,land,erf,boerderij,landbouwer,akkerbouw,boerenbedrijf,landbouwgrond,landbouwmachine,voedselproductie\n📚|boek,kast,balie,pas,lezer,bibliotheek,boekenkast,bibliothecaris,uitleentermijn,boekenafdeling,informatiedienst,mediatheekbeheer\n🏙|stad,dorp,straat,plein,buurt,stadje,dorpsplein,woonplaats,stadscentrum,gemeentehuis,stedelijkgebied,bevolkingsdichtheid\n🏞|park,bos,veld,pad,vijver,parkje,speelweide,natuurpark,wandelroute,recreatiegebied,landschapsbeheer,natuurvoorziening",
time:"☀️|dag,uur,tijd,klok,middag,dagje,daglicht,dagdeel,dagritme,tijdstip,dagindeling,tijdswaarneming\n🌅|ochtend,vroeg,wakker,ontbijt,zonsopgang,ochtendlicht,ochtendritueel,schoolochtend,dagbegin,ochtendspits,ochtendprogramma,ochtendactiviteit\n🌇|avond,laat,eten,donker,zonsondergang,avondlicht,avondeten,bedtijd,dagafsluiting,avondprogramma,avondschemering,avondvoorstelling\n🗓|week,maand,jaar,datum,agenda,weekje,weekdag,maandblad,jaartal,kalenderjaar,tijdrekening,kalenderindeling\n🎂|feest,taart,kaars,cadeau,ballon,feestje,verjaardag,feestmuts,feestdag,uitnodiging,feestversiering,verjaardagsviering\n🍂|lente,zomer,herfst,winter,seizoen,lentebloem,zomerdag,herfstblad,winterjas,seizoenswissel,jaargetijde,seizoensverschijnsel\n⏱|minuut,seconde,kwart,half,duur,minuutje,tijdsduur,stopwatch,kwartier,wachttijd,tijdseenheid,tijdsberekening",
arts:"🎨|kleur,verf,kwast,doek,palet,kleurtje,verfpot,schilderen,schilderij,kunstschilder,kleurgebruik,schildertechniek\n🖍|tekening,lijn,vorm,punt,vak,tekenen,kleurplaat,schetsboek,omtrek,tekenmateriaal,beeldverhaal,illustratietechniek\n🎵|toon,lied,stem,klank,maat,liedje,muziek,nootbalk,melodie,muzikant,muziekstuk,muziekuitvoering\n🥁|trom,fluit,snaar,toets,bel,trommel,instrument,gitaarspel,slagwerk,muziekles,muziekinstrument,instrumentenfamilie\n💃|dans,stap,sprong,draai,pas,dansje,dansvloer,dansgroep,choreografie,dansvoorstelling,bewegingspatroon,podiumkunst\n🎭|toneel,rol,pak,tekst,podium,toneelstuk,acteur,optreden,voorstelling,toneelspeler,podiumverlichting,theatervoorstelling\n📷|foto,film,beeld,lens,flits,fotootje,camera,fotoboek,filmopname,fotograaf,beeldbewerking,mediaproductie",
science:"🔍|kijken,zoeken,vraag,vondst,spoor,onderzoek,waarnemen,vergrootglas,ontdekking,onderzoeker,waarneming,onderzoeksresultaat\n🧪|proef,test,buis,stof,mengen,proefje,reactie,meetglas,experiment,laboratorium,proefopstelling,onderzoeksverslag\n🧲|magneet,ijzer,pool,kracht,veld,aantrekken,afstoten,magnetisch,kompasnaald,magneetkracht,magnetisme,natuurverschijnsel\n⚙|wiel,as,tand,veer,hefboom,tandwiel,overbrenging,mechaniek,machine,werktuig,bewegingsenergie,machinewerking\n💡|licht,stroom,draad,lamp,stekker,schakelaar,batterij,stroomkring,elektrisch,elektriciteit,energieverbruik,energievoorziening\n💻|computer,muis,toets,scherm,bestand,computerprogramma,toetsenbord,beeldscherm,digitaal,internet,programmeren,informatietechnologie\n🚀|ruimte,maan,planeet,ster,raket,ruimtevaart,satelliet,heelal,astronaut,zonnestelsel,ruimtestation,ruimteonderzoek",
learning:"🔤|letter,klank,woord,naam,teken,alfabet,lettergreep,klinker,medeklinker,lettervolgorde,klankstructuur,spellingspatroon\n📝|zin,punt,komma,vraag,tekst,zinnetje,hoofdletter,leesteken,zinsdeel,zinsbouw,tekstverband,taalbeschouwing\n📖|boek,blad,kaft,titel,prent,boekje,hoofdstuk,inhoudsopgave,schrijver,boekbespreking,literatuur,leeservaring\n🗣|taal,stem,spraak,gesprek,verhaal,vertellen,luisteraar,spreker,spreekbeurt,gespreksregel,taalgebruik,communicatievaardigheid\n🔢|nul,een,twee,drie,getal,cijfer,reeks,getallenlijn,getalbeeld,plaatswaarde,getalstructuur,getalnotatie\n|som,plus,min,keer,delen,optellen,aftrekken,uitkomst,bewerking,rekenregel,rekenstrategie,bewerkingsvolgorde\n📐|vorm,lijn,hoek,vlak,lengte,driehoek,rechthoek,meetlat,oppervlakte,omtrekberekening,meetinstrument,meetkundigevorm",
society:"🤝|samen,hulp,beurt,afspraak,regel,samenwerken,overleggen,taakverdeling,deelname,samenleving,samenwerkingsverband,maatschappijleer\n🏘|buurt,wijk,dorp,stad,straat,buurthuis,woonwijk,gemeente,provincie,stadsbestuur,gemeenteraad,gebiedsindeling\n🌍|wereld,land,zee,kaart,grens,wereldbol,landkaart,continent,werelddeel,bevolking,wereldbevolking,wereldburgerschap\n🗺|noord,zuid,oost,west,plek,richting,kompas,kaartteken,plattegrond,schaalverdeling,herkenningspunt,kaartvaardigheid\n📰|nieuws,krant,bericht,radio,media,nieuwsblad,journalist,nieuwsbron,actualiteit,nieuwsbericht,betrouwbaarheid,mediageletterdheid\n♻|afval,bak,glas,papier,natuur,recyclen,hergebruik,afvalbak,milieuzorg,afvalscheiding,duurzaamheid,grondstoffengebruik\n⚖|recht,plicht,keus,stem,wet,eerlijk,verkiezing,mening,besluit,democratie,grondrecht,besluitvorming"
};
const LANGUAGE_PROGRESSION_YEARS=[1,2,3,3,4,5,5,6,7,8,7,8];
const LANGUAGE_NL_PROGRESSION_WORDS=(()=>{
const seen=new Set(LANGUAGE_CONCEPTS.map(item=>item.nl)),out=[];
Object.entries(LANGUAGE_NL_PROGRESSION_FAMILIES).forEach(([theme,rows])=>rows.split("\n").forEach(row=>{
const [pic,list]=row.split("|");let familyPosition=0;
list.split(",").forEach(raw=>{
const word=raw.trim();if(!word||seen.has(word))return;seen.add(word);
const slug=word.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/[^a-z]/g,"");
const yearGroup=LANGUAGE_PROGRESSION_YEARS[Math.min(familyPosition++,LANGUAGE_PROGRESSION_YEARS.length-1)];
out.push({id:"nl-"+theme+"-"+slug,theme,nl:word,pic,yearGroup});
});
}));
return out
})();
/* De vertrouwde startkaart blijft exact dezelfde 30 woorden houden. */ /* De vertrouwde startkaart blijft exact dezelfde 30 woorden houden. */
const START_WORD_KEYS = { const START_WORD_KEYS = {
nl:["monkey","fish","sun","cat","ball","egg","tree","house","moon","boat","hen","mouse","book","star","rose","fire","bike","cake","coat","glasses","clock","pear","cheese","duck","hand","foot","nose","eye","ear","mouth"], nl:["monkey","fish","sun","cat","ball","egg","tree","house","moon","boat","hen","mouse","book","star","rose","fire","bike","cake","coat","glasses","clock","pear","cheese","duck","hand","foot","nose","eye","ear","mouth"],
@ -233,12 +462,16 @@ const WORDS = Object.fromEntries(["nl","en"].map(lang=>[
}) })
])); ]));
function languageBuiltinWords(lang=LANG){
return lang==="nl"?LANGUAGE_CONCEPTS.concat(LANGUAGE_NL_PROGRESSION_WORDS):LANGUAGE_CONCEPTS
}
function languageThemeList(lang=LANG){ function languageThemeList(lang=LANG){
return LANGUAGE_THEME_DEFS.map(theme=>({ return LANGUAGE_THEME_DEFS.map(theme=>({
...theme, ...theme,
name:theme[lang], name:theme[lang],
words:LANGUAGE_CONCEPTS.filter(item=>item.theme===theme.id).map(item=>[item[lang],item.pic]) words:languageBuiltinWords(lang).filter(item=>item.theme===theme.id)
})); .map(item=>[item[lang]||item.nl,item.pic,item.yearGroup||null])
})).filter(theme=>theme.words.length);
} }
function languageEmojiDataUrl(emoji,colors=["#f4f8ff","#dce9fa"]){ function languageEmojiDataUrl(emoji,colors=["#f4f8ff","#dce9fa"]){
const svg=`<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320"><defs><linearGradient id="g" x2="1" y2="1"><stop stop-color="${colors[0]}"/><stop offset="1" stop-color="${colors[1]}"/></linearGradient></defs><rect width="320" height="320" rx="48" fill="url(#g)"/><circle cx="160" cy="160" r="112" fill="#fff" opacity=".92"/><text x="160" y="177" text-anchor="middle" dominant-baseline="middle" font-family="Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif" font-size="148">${emoji}</text></svg>`; const svg=`<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320"><defs><linearGradient id="g" x2="1" y2="1"><stop stop-color="${colors[0]}"/><stop offset="1" stop-color="${colors[1]}"/></linearGradient></defs><rect width="320" height="320" rx="48" fill="url(#g)"/><circle cx="160" cy="160" r="112" fill="#fff" opacity=".92"/><text x="160" y="177" text-anchor="middle" dominant-baseline="middle" font-family="Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif" font-size="148">${emoji}</text></svg>`;
@ -250,13 +483,14 @@ function languagePictureCatalog(lang=LANG){
languageBuiltin:true,wordPicture:true,icon:theme.icon languageBuiltin:true,wordPicture:true,icon:theme.icon
})); }));
const themeIds=new Map(LANGUAGE_THEME_DEFS.map((theme,index)=>[theme.id,-200-index])); const themeIds=new Map(LANGUAGE_THEME_DEFS.map((theme,index)=>[theme.id,-200-index]));
const images=LANGUAGE_CONCEPTS.map((item,index)=>{ const images=languageBuiltinWords(lang).map((item,index)=>{
const theme=LANGUAGE_THEME_DEFS.find(def=>def.id===item.theme); const theme=LANGUAGE_THEME_DEFS.find(def=>def.id===item.theme);
const name=item[lang]||item.nl;
return { return {
id:-1000-index,name:item[lang],folder:`Taal/Woordenschat/${theme[lang]}`, id:-1000-index,name,folder:`Taal/Woordenschat/${theme[lang]}`,
scope:"global",themes:[themeIds.get(item.theme)],tags:[item.nl,item.en,theme.nl,theme.en], scope:"global",themes:[themeIds.get(item.theme)],tags:[item.nl,item.en,theme.nl,theme.en].filter(Boolean),
src:languageEmojiDataUrl(item.pic,theme.colors),value:item.pic,canManage:false,canDelete:false, src:languageEmojiDataUrl(item.pic,theme.colors),value:item.pic,canManage:false,canDelete:false,
wordPicture:true,builtin:true wordPicture:true,builtin:true,yearGroup:item.yearGroup||null
}; };
}); });
const ownThemes=[]; const ownThemes=[];

View file

@ -76,10 +76,12 @@ function languageWordComplexity(word){
function languageWordsForYear(words,yearGroup,level){ function languageWordsForYear(words,yearGroup,level){
if(!Array.isArray(words)||!words.length)return[]; if(!Array.isArray(words)||!words.length)return[];
const year=learningYearGroup(yearGroup);if(!year)return words; const year=learningYearGroup(yearGroup);if(!year)return words;
const introduced=words.filter(item=>!Array.isArray(item)||item[2]==null||Number(item[2])<=year);
const available=introduced.length?introduced:words;
const caps=[0,5,7,9,12,15,18,22,99],cap=caps[year]+(learningLevel(level)-2)*2; const caps=[0,5,7,9,12,15,18,22,99],cap=caps[year]+(learningLevel(level)-2)*2;
const filtered=words.filter(item=>languageWordComplexity(Array.isArray(item)?item[0]:item)<=cap); const filtered=available.filter(item=>languageWordComplexity(Array.isArray(item)?item[0]:item)<=cap);
if(filtered.length>=Math.min(4,words.length))return filtered; if(filtered.length>=Math.min(4,available.length))return filtered;
return [...words].sort((a,b)=>languageWordComplexity(a[0])-languageWordComplexity(b[0])).slice(0,Math.min(4,words.length)) return [...available].sort((a,b)=>languageWordComplexity(a[0])-languageWordComplexity(b[0])).slice(0,Math.min(4,available.length))
} }
function languageTaskRules(yearGroup,level){ function languageTaskRules(yearGroup,level){
const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level); const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level);

View file

@ -9,6 +9,7 @@ async function languageData(extra=''){
runInNewContext(source+`; ${extra} runInNewContext(source+`; ${extra}
globalThis.__language={ globalThis.__language={
concepts:LANGUAGE_CONCEPTS, concepts:LANGUAGE_CONCEPTS,
progression:LANGUAGE_NL_PROGRESSION_WORDS,
themesNl:languageThemeList("nl"), themesNl:languageThemeList("nl"),
themesEn:languageThemeList("en"), themesEn:languageThemeList("en"),
words:WORDS, words:WORDS,
@ -17,20 +18,34 @@ async function languageData(extra=''){
return JSON.parse(JSON.stringify(context.__language)); return JSON.parse(JSON.stringify(context.__language));
} }
test('taalwidgets delen een ruime tweetalige woordenschat in acht duidelijke themas',async()=>{ test('taalwidgets delen ruim duizend extra Nederlandse woorden over leerjaar en thema',async()=>{
const data=await languageData(); const data=await languageData();
assert.ok(data.concepts.length>=140,'verwacht minimaal 140 vaste woordplaatjes'); assert.ok(data.concepts.length>=300,'verwacht minimaal 300 vaste woordplaatjes');
assert.equal(data.themesNl.length,8); assert.ok(data.progression.length>=1000,'verwacht minimaal 1000 aanvullende Nederlandse woorden');
assert.equal(data.themesEn.length,8); assert.equal(data.themesNl.length,16);
assert.equal(new Set(data.concepts.map(item=>item.id)).size,data.concepts.length); assert.equal(data.themesEn.length,12);
assert.deepEqual(data.themesNl.map(theme=>theme.id),[
'animals','food','nature','travel','home','body','actions','feelings',
'people','play','places','time','arts','science','learning','society'
]);
const all=data.concepts.concat(data.progression);
assert.equal(new Set(all.map(item=>item.id)).size,all.length);
assert.equal(new Set(all.map(item=>item.nl)).size,all.length);
for(const concept of data.concepts){ for(const concept of data.concepts){
assert.ok(concept.nl&&concept.en&&concept.pic,concept.id); assert.ok(concept.nl&&concept.en&&concept.pic,concept.id);
assert.ok(data.themesNl.some(theme=>theme.id===concept.theme),concept.theme); assert.ok(data.themesNl.some(theme=>theme.id===concept.theme),concept.theme);
} }
for(const themes of [data.themesNl,data.themesEn]){ for(const word of data.progression){
for(const theme of themes)assert.ok(theme.words.length>=10,theme.id); assert.ok(word.nl&&word.pic,word.id);
assert.equal(themes.reduce((sum,theme)=>sum+theme.words.length,0),data.concepts.length); assert.ok(word.yearGroup>=1&&word.yearGroup<=8,word.nl);
assert.ok(data.themesNl.some(theme=>theme.id===word.theme),word.theme);
} }
for(let year=1;year<=8;year++)
assert.ok(data.progression.filter(word=>word.yearGroup===year).length>=60,'te weinig nieuwe woorden voor groep '+year);
for(const theme of data.themesNl)assert.ok(theme.words.length>=60,theme.id);
for(const theme of data.themesEn)assert.ok(theme.words.length>=18,theme.id);
assert.equal(data.themesNl.reduce((sum,theme)=>sum+theme.words.length,0),all.length);
assert.equal(data.themesEn.reduce((sum,theme)=>sum+theme.words.length,0),data.concepts.length);
}); });
test('alle oude startwoorden blijven staan en krijgen ook een bibliotheekplaatje',async()=>{ test('alle oude startwoorden blijven staan en krijgen ook een bibliotheekplaatje',async()=>{
@ -46,19 +61,23 @@ test('alle oude startwoorden blijven staan en krijgen ook een bibliotheekplaatje
test('vaste en eigen woordplaatjes worden als herbruikbare catalogusitems aangeboden',async()=>{ test('vaste en eigen woordplaatjes worden als herbruikbare catalogusitems aangeboden',async()=>{
const data=await languageData('GENERAL_EXTRA.nl=[["test","🧪"]]; THEMES.nl=[{name:"Boerderij",folder:"School",words:[["stal","🏚️"]]}];'); const data=await languageData('GENERAL_EXTRA.nl=[["test","🧪"]]; THEMES.nl=[{name:"Boerderij",folder:"School",words:[["stal","🏚️"]]}];');
const builtins=data.catalogNl.images.filter(image=>image.builtin); const builtins=data.catalogNl.images.filter(image=>image.builtin);
assert.equal(builtins.length,data.concepts.length); assert.equal(builtins.length,data.concepts.length+data.progression.length);
assert.equal(data.catalogNl.themes.filter(theme=>theme.languageBuiltin).length,8); assert.equal(data.catalogNl.themes.filter(theme=>theme.languageBuiltin).length,16);
for(const image of builtins){ for(const image of builtins){
assert.ok(image.id<0); assert.ok(image.id<0);
assert.ok(image.src.startsWith('data:image/svg+xml;charset=utf-8,')); assert.ok(image.src.startsWith('data:image/svg+xml;charset=utf-8,'));
assert.ok(image.value&&image.themes.length===1); assert.ok(image.value&&image.themes.length===1);
assert.equal(image.canManage,false); assert.equal(image.canManage,false);
} }
const testPic=data.catalogNl.images.find(image=>image.name==='test'); const testPic=data.catalogNl.images.find(image=>!image.builtin&&image.name==='test');
const stablePic=data.catalogNl.images.find(image=>image.name==='stal'); const stablePic=data.catalogNl.images.find(image=>!image.builtin&&image.name==='stal');
assert.equal(testPic.value,'🧪'); assert.equal(testPic.value,'🧪');
assert.equal(stablePic.value,'🏚️'); assert.equal(stablePic.value,'🏚️');
assert.equal(stablePic.folder,'Taal/Mijn themas/School/Boerderij'); assert.equal(stablePic.folder,'Taal/Mijn themas/School/Boerderij');
for(const word of ['familiegeschiedenis','competitiewedstrijd','mediageletterdheid','waterkringloop']){
const image=data.catalogNl.images.find(item=>item.name===word);
assert.ok(image&&image.yearGroup>=7&&image.src.startsWith('data:image/svg+xml'),word);
}
}); });
test('alle vier taalspellen gebruiken themabronnen en kunnen de bibliotheek openen',async()=>{ test('alle vier taalspellen gebruiken themabronnen en kunnen de bibliotheek openen',async()=>{

View file

@ -52,12 +52,14 @@ test('alleen leerkracht en systeemmanager kunnen het leerjaar op hun eigen bord
test('taalprofiel selecteert eenvoudiger woorden in de onderbouw en bouwt taaksteun af',async()=>{ test('taalprofiel selecteert eenvoudiger woorden in de onderbouw en bouwt taaksteun af',async()=>{
const c=await progressionContext(); const c=await progressionContext();
const words=[['kat','x'],['vis','x'],['pen','x'],['maan','x'],['boek','x'],['fiets','x'], const words=[['kat','x',1],['vis','x',1],['pen','x',1],['maan','x',2],['boek','x',2],['fiets','x',3],
['brandweerauto','x'],['onderzoeker','x'],['verantwoordelijkheid','x']]; ['brandweerauto','x',4],['onderzoeker','x',6],['verantwoordelijkheid','x',8]];
const early=JSON.parse(JSON.stringify(c.__languageWordsForYear(words,2,2))); const early=JSON.parse(JSON.stringify(c.__languageWordsForYear(words,2,2)));
const late=JSON.parse(JSON.stringify(c.__languageWordsForYear(words,8,2))); const late=JSON.parse(JSON.stringify(c.__languageWordsForYear(words,8,2)));
assert.ok(early.length<late.length); assert.ok(early.length<late.length);
assert.ok(early.every(([word])=>!['brandweerauto','verantwoordelijkheid'].includes(word))); assert.ok(early.every(([word])=>!['brandweerauto','verantwoordelijkheid'].includes(word)));
assert.ok(early.every(([, ,introduced])=>introduced<=2));
assert.ok(late.some(([word])=>word==='verantwoordelijkheid'));
assert.equal(c.__languageTaskRules(2,2).flashSeconds,3); assert.equal(c.__languageTaskRules(2,2).flashSeconds,3);
assert.equal(c.__languageTaskRules(7,2).flashSeconds,.5); assert.equal(c.__languageTaskRules(7,2).flashSeconds,.5);
assert.ok(c.__languageTaskRules(2,2).lives>c.__languageTaskRules(7,2).lives); assert.ok(c.__languageTaskRules(2,2).lives>c.__languageTaskRules(7,2).lives);