Project: Taaloefeningen

In het kader van de lessen 'Franse Conversatie' die ik op dit ogenblik volg, heb ik voor mezelf en de medecursisten, een site gebouwd waarbij de aangeleerde woorden kunnen worden ingeoefend. Het spreekt voor zich dat deze site ook vertalingen in andere talen aankan.
Het komt er op neer dat we elke les, teksten bespreken, waarin de voor ons onbekende woorden en termen worden vertaald. Om dit later in te oefenen is het belangrijk dat de context van de vertaling getoond wordt. Zo kan je 'bloem' vertalen in 'fleur' en 'farine'. Je hebt dus altijd de omringende tekst nodig om zinvol een vertaling te kunnen doen.
Een (tijdelijke) link is beschikbaar om dit project te volgen: https://frans.drupal.vlaanderen/
Dit project heeft tot doel om z'on gebruiksvriendelijke oefensite te maken. Tot mijn verbazing is er heel wat Drupal ervaring voor nodig. Het is deze ervaring dat ik met jullie wil delen in deze bijdrage.
We maken eerst een inhoudstype 'Teksten' aan waar we onze bronteksten zullen in onder brengen. Daarna gaan we een inhoudstype 'Vertalingen' aanmaken. De titel zal de Franse tekst bevatten. Je hebt dan de vertaling en de verwijzing naar de tekst. Ook een antwoordveld heb ik reeds klaar staan.
We voegen inhoud toe met titel 'Le prix du lait' en volgende tekst. (inhoudstype=Teksten)
Ce n’est que depuis que les prix des céréales dans le monde – en particulier la farine – ont presque doublé, ce qui a fait augmenter le prix de l’alimentation, et que la pression sur les exploitations agricoles de production croît pour les biocarburants et autres cultures non alimentaires, que certains supermarchés ont finalement commencé à paniquer à la perspective d’une réelle pénurie de lait et se sont, de ce fait, mis à payer aux producteurs laitiers un prix légèrement supérieur.
Nu gaan we een woord toevoegen en vertalen: (hoofdlettergevoelig)
Op een bepaald moment gaan we de rechten aanpassen zodanig dat het anwoordveld kan ingevuld worden. In dat geval krijgt men 'de bloem' te zien en moet die vertaald worden. We gaan nu een nieuw veld moeten aanmaken om dit antwoord te checken met de titel van de node, dat het antwoord bevat. Dit wordt een Twigveld. Eerst kijken we of er wel een antwoord is, daarna controleren we dit antwoord. We schrijven dan ofwel 'juist' ofwel 'verkeerd' en maken er een gekleurd blokje van. Merk op dat de script, mogelijke spaties met trim wegwerkt, alsook mogelijke hoofdletters.
{% if node.field_antwoord is not empty %}
{% if node.title.value|trim|lower== node.field_antwoord.value|trim|lower %}
<span style="padding:2px;color:green;background:green">juist</span>
{% else %}
<span style="padding:2px;color:red;background:red">fout</span>
{% endif %}
{% endif %}
Zie je wat ik fout had?
We voegen nu nog eens 2 vertalingen toe, die in de tekst voorkomen: céréales = granen en pénurie = tekort en we maken een view, waarbij de antwoorden kunnen worden aangebracht.
We gaan nu een view maken waarmee we vlotjes de antwoorden kunnen toevoegen. Om formuliervelden te kunnen gebruiken in de view, installeren we de module Views Entity Form Field: https://www.drupal.org/project/views_entity_form_field. Als je wilt dat dit responsive is kan je altijd de Responsive Table Filter module installeren. Zo kan je dan makkelijk met je smartphone ook de view bekijken. De kolommen worden rijen en omgekeerd.
Pas de toegangsrechten aan zodanig dat anonieme gebruikers ook de antwoorden kunnen ingeven. Als je dit niet wilt, kan je met ingelogden werken. Je kunt zelfs maken dat elke cursist eigen antwoorden kan geven. Je moet daarom elke node dupliceren en als auteur de cursist zetten. Dan kan je de rechten instellen zodanig dat de cursist enkel zijn/haar eigen nodes kan aanpassen. We gaan dit hier niet doen, maar het is mogelijk. Als we later de import gaan doen met Feeds, is dit dan doenbaar.
Waar zit de fout bij de granen? Het zou handig zijn, moesten we kunnen spieken... Nu hebben we het aan onze rekker dat we telkens naar de tekst moeten gaan kijken...
We gaan daarom een spiekveldje bijmaken.. Met Twig natuurlijk..
Jammerlijk genoeg kunnen we dit niet rechtstreeks doen op view niveau. Het Twigveld daar heeft beperkingen qua HTML (onder andere de nodige <select> en <option> tags). Een twigveld in een node heeft die beperking niet. Daarom gaan we dus in het inhoudstype dit veld aanmaken en zodoende tonen in de view. Je zou ook kunnen gebruik maken van deze module: https://www.drupal.org/project/views_html_tags om in een view meer tags toe te laten, maar deze module wordt niet echt onderhouden... We houden het bij onze node aanpak.
De code is ontluisterend simpel..
<select>
<option value="">antwoord</option>
<option value="">{{node.title.value}}</option>
</select>
OK. We gaan verfijnen... Wat gaan we doen als iemand de oefening nog eens wil maken? Hoe verwijder je alle antwoorden? Je zou dit kunnen doen door een Feed te maken waarbij alle antwoorden leeg blijven en de rechten voor het gebruik van deze feeds vast te leggen. Nogal omslachtig. Wij gaan dit programmeren in php.
We installeren daarvoor de php module van Drupal mét composer. (lukt niet anders) https://www.drupal.org/project/php
Daarna gaan we een tekstformaat php script aanmaken. Je zult zien dat er reeds een tekstformaat is.. PHP-code..genaamd. Normale mensen gaan dit formaat kiezen.. maar wij zijn geen normale mensen... Want wij weten dat er een bug bestaat die dit formaat verwijdert... als je gelijk welke andere module desinstalleert. Er is een patch voor, maar wij gaan dit zo oplossen...
Maak een nieuw formaat aan met deze instellingen. (Er zin vóór- en tegenstanders van deze module. Volgens sommigen is het niet veilig. Inderdaad.. als je in je formaat vergeet aan te duiden dat het enkel beheerders zijn, die dit formaat mogen gebruiken, is het niet veilig... )
We maken een node aan (kan zelfs 'eenvoudige pagina' zijn of maak desnoods een inhoudstype aan met een body) . Zet het tekstformaat op PHP script.
Bekijk onderstaande code. <?php en ?> zijn de grenzen van het php gedeelte. We gaan in de database de node storage aanspreken. Daarna gaan we een view uitvoeren. Jawel!! een view, maar deze keer geprogrammeerd. Een Drupal view doet echter niets anders en is gebaseerd op deze werkmethode.
we filteren op het inhoudstype, daarna op de veldwaarde die niet leeg is en tenslotte op enkel de gepubliceerde nodes (status = 1). We doorlopen alle nodes in deze view en zetten het antwoordveld op NULL. Dit staat voor leeg. Om didactische redenen toon ik ook nog eens de nodes die in aanmerking komen.. Ik toon die met de instructie echo., typisch voor php. Bewaren en klaar..
<?php
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$nids = \Drupal::entityQuery('node')
->condition('type', 'vertalingen')
->condition('field_antwoord', 'value', 'IS NOT NULL')
->condition('status', 1)
->execute();
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids);
foreach ($nodes as $node) {
echo $node->get('title')->getString()." ";
$node->set('field_antwoord', NULL);
$node->save();
}
?>
</br>Antwoorden werden met succes verwijderd.
Telkens deze node nu wordt aangeroepen, wordt die ook uitgevoerd...
We brengen nu in de kop van onze drupal view een link aan naar deze node. We maken er ineens een knop van in plaat van een link..
<button onclick="window.location.href='/verwijder_antwoorden'">Verwijder alle antwoorden</button>
Context
Het zwakke punt in gans deze vertaling, op dit ogenblik, is dat de context van het woord niet wordt getoond. Bloem vertalen naar farine en niet naar fleur kan maar als je de tekst er bij hebt. Laat ons dit de context noemen.
We doen dit in de node met een Twig veld. We kijken of het trefwoord voorkomt in de tekst en splitsen die in 2. Bij de splitsing gaan we het trefwoord maskeren en de Nederlandse vertaling tonen.
{% if node.title.value in node.field_tekstverwijzing.entity.body.value|raw %}
{% set tekst = node.field_tekstverwijzing.entity.body.value|split(node.title.value)|raw %}
...{{ tekst[0]|slice(tekst[0]|length-80,80)|raw }}
<b>
{% set antwoordstring = node.title.value|split(' ') %}
{% for woord in antwoordstring %}
{{ woord|first }}
{% if woord|length > 1 %}
{% set aantalletters = woord|length - 2 %}
{% for letter in 0..aantalletters %}
_
{% endfor %}
{% endif %}
{% endfor %}
<i>{{node.field_nederlandse_vertaling.value|raw}} </i></b>
{{ tekst[1]|slice(0,80)|raw}}...
{% else %}
<b>{% set antwoordstring = node.title.value|split(' ') %}
{% for woord in antwoordstring %}
{{ woord|first }}
{% if woord|length > 1 %}
{% set aantalletters = woord|length - 2 %}
{% for letter in 0..aantalletters %}
_
{% endfor %}
{% endif %}
{% endfor %}
<i>{{node.field_nederlandse_vertaling.value|raw}}</i></b>
{% endif %}
Nu moeten we dit veld enkel nog tonen in de view.. De vertaling wordt nu zeer realistisch. De beginletters en het aantal tekens moet daarbij helpen.
Oefenblad
Het zou ook handig zijn om een soort (papieren) oefenblad te hebben.
Om dit oefenblad weer te geven gaan we eerst de tekst aanpassen en alle antwoorden maskeren.
We maken een nieuw veld aan (tekst opgemaakt lang) en zetten die in php formaat.
Daarna voegen we deze code toe. Eerst laden we de brontekst in. Daarna gaan we in het inhoudstype 'vertaling' alle nodes ophalen die deze tekst als referentie hebben. Uiteindelijk gaan we van deze nodes de titel nemen. Desnoods splitsen we ze in stukken als er spaties in voorkomen. We willen namelijk de eerste letter van elk gedeelte tonen als hint. We vervangen daarna elk trefwoord in deze tekst met het vraaggedeelte. Dan krijg je dit:
<?php
$dezenid = \Drupal::routeMatch()->getRawParameter('node');
$dezenode = \Drupal::entityTypeManager()->getStorage('node')->load($dezenid);
$tekst= $dezenode->get('body')->value;
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$nids = \Drupal::entityQuery('node')
->condition('type', 'vertalingen')
->condition('field_tekstverwijzing',$dezenid)
->condition('status', 1)
->execute();
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids);
foreach ($nodes as $node) {
$node_title= $node->get('title')->getString();
$vraag="";
$gedeelten= explode(" ",$node_title);
foreach ($gedeelten as $gedeelte){
$streepjes = " ";
for ($x = 2; $x <= mb_strlen($gedeelte); $x++) {
$streepjes=$streepjes.". ";
}
$vraag= $vraag." ".mb_substr($gedeelte,0,1).$streepjes." ";
}
$vertaling= $node->get('field_nederlandse_vertaling')->value;
$tekst= str_replace($node_title, '<b>'.$vraag." <i>".$vertaling.'</i></b>', $tekst);
}
echo $tekst;
?>
Feeds
OK. We zijn nu in het stadium gekomen dat we de woorden niet meer manueel zullen inputten, maar met Feeds. Maak een Feed importer die in staat is om dit te doen. Je krijgt van mij een tekst en de bijhorende woorden en vertalingen.
Tip: laad alle afbeeldingen in één keer op met IMCE en selecteer dan telkens op de plaats waar je de afbeelding wilt. Gebruik daarvoor de miniatuurweergave van IMCE.
Woordenlijst
Een woordenlijst geeft meestal de eerste letter weer van een woord. Op deze manier kan men makkelijk navigeren. Maak zo'n overzicht. Laat je desnoods inspireren door de reeds bestaande glossary te kopiëren en aan te passen. Maak dat er initieel niet enkel de nodes die beginnen met A worden getoond. Dit is de standaardinstelling.
Features
Omdat we dit ook naar andere sites willen beschikbaar stellen, gaan we een module maken met Features.
Installeer Features en maak een nieuwe module. Maak ook dat je de veldwaarden (=standaardwaarden) van de velden mee in de module steekt, behalve van de body. Het bodyveld heeft geen behoefte aan de standaardwaarde en het bodyveld zou andere inhoudstypes activeren die we niet nodig hebben. Let er ook op dat je enkel de zaken die je nodig hebt aanvinkt.
Ik geef je de module alvast cadeau: vertalingen (1).tar.gz
Extraatje
Ik heb het ook mogelijk gemaakt voor de leerkracht om, in plaats van met Feeds de woorden aan te brengen, rechtstreeks in de tekst de woorden te vertalen. De Franse woorden worden met ?? afgebakend en de vertalingen met !!
Angèle ??dévoile?? !!onthullen!! "Bruxelles, je t'aime", son nouveau single
Un clip très travaillé, un texte engagé et qui parlera aux Belges et aux Bruxellois, une mélodie ??entêtante?? !!oorwurm (blijft plakken)!! : Angèle est bien de retour !
Mijn programma gaat dan zelf de woorden en hun vertaling, detecteren en maakt er nodes van. Een stukje van de code. Kijk eens hoe makkelijk het is om nodes programmatorisch aan te maken...
$node = \Drupal::entityTypeManager()->getStorage('node')->create([
'type' => 'vertalingen',
'title' => trim($trefwoord),
'field_nederlandse_tekst' => trim($trefwoordvert),
'field_tekstverwijzing' => $tekstnid,
]);
$node->save();
CSS
Zorg voor een mooi lettertype en wat opmaak..
Besluit
Dit project bevat een verzameling van Drupal kennis en ervaring uitgewerkt in één project. De grote les van dit project is echter de demonstratie van de kracht én eenvoud van php. Drupal heeft een solide basis maar niet voor alles zijn er modules. Op een bepaald moment kom je terug naar de roots van HTML, CSS en PHP.