La conception de www.colorimo.fr

par Vincent Poulailleau - 9 minutes de lecture - 1728 mots

Pourquoi cette présentation ?

Ma grande fille, qui vient tout juste de fêter ses 6 ans, s’est lancée dans l’apprentissage de la lecture. Voyant ses difficultés à lire des mots compliqués, j’ai créé un site internet pour aider à la découverte de la lecture. Voici quelques étapes de la conception de www.colorimo.fr.

Le concept

Sur www.colorimo.fr, un code couleur aide les lecteurs en herbe à déchiffrer les mots. Par exemple, « doigts » ne se lit pas « dohiguetse » mais tout simplement « doi », car « oigts » est en noir, comme tous les sons « oi ».

Voici le choix de couleurs effectués :

  • En violet : le son « ê » comme dans je fais, la fête…
  • En rouge : le son « ou » comme dans je bouge, les bijoux
  • En noir : le son « oi » comme dans je dois, les doigts
  • En orange : le son « an » comme dans un gant, un faon
  • En vert sapin : le son « in » comme dans la fin, la faim
  • En marron : le son « on » comme dans les bons bonbons
  • En bleu : le son « eu » comme dans le feu, les jeux
  • En rose : le son « o » comme dans le mot, le bateau
  • En encadré : les mots à connaître par cœur

En plus de ce code couleur, il y a la possibilité de lire le texte en majuscules, en minuscules, en script, en manuscrit, en police spéciale dyslexiques…

Éléments clés de conception

Ce site est principalement codé en Python, langage que j’affectionne pour sa simplicité, sa concision, sa clarté.

Dans la pratique :

1
find . -name "*.py" | grep -v "tests" | grep -v "settings" | xargs wc -l

Il y a donc environ 600 lignes de Python (hors tests et configuration), et de manière similaire, 300 lignes d’HTML, 250 lignes de CSS et 20 lignes de JavaScript.

J’utilise le framework Django, dont le slogan est « le cadriciel web pour les perfectionnistes avec des échéances à tenir ». Effectivement, il permet de développer rapidement un site web, en fournissant les briques de base, comme un système de template de page web, la gestion de base de données, la gestion d’utilisateurs, la validation de formulaire, la sécurité, un backend d’administration et tant d’autres. C’est un excellent framework, qui est massivement utilisé dans le monde Python (par exemple pour Instagram ou Pinterest).

J’utilise pytest pour écrire les tests. Ce framework n’est plus à présenter tellement il est un cadriciel de choix pour écrire des tests en Python.

Et enfin, pour ce qui est de la reconnaissance des sons dans le français, je me suis basé sur les expressions régulières

Difficultés du français

C’est en réalisant ce site internet, que je me suis rendu compte de la complexité du français pour les apprenants.

Prenez la séquence « en », elle se prononce :

  • « an » comme dans « ensemble »
  • « ène » comme dans « ennemi »
  • « in » comme dans « bien »

Prenez le son « oi », il peut s’écrire :

  • « oi » comme dans « foi »
  • « ois » comme dans « fois »
  • « oie » comme dans « foie »
  • « oies » comme dans « foies »
  • « oigt » comme dans « doigt »
  • « oigts » comme dans « doigts »
  • « oid » comme dans « froid »
  • « oids » comme dans « froids »
  • « o » comme dans « royal » (c’est un peu tendancieux, je vous l’accorde)
  • « oê » comme dans « poêle »

Je pensais initialement m’en sortir avec quelques expressions régulières, mais j’ai vite compris que c’était impossible de tout gérer comme cela : quelle est la prononciation de « fier » ? Nous ne pouvons pas seulement nous baser sur l’écriture, cela dépend du contexte : « être fier » ou « se fier ».

Pour des raisons de simplification, je n’ai pas géré la prise en compte du contexte, il y a donc quelques rares erreurs dans la coloration. Mais finalement, grâce aux expressions régulières de Python, le résultat est plus que satisfaisant.

Bien sûr, www.colorimo.fr étant récent, il est possible que certains mots soient mal colorés (« fier » à tout hasard…). Mais n’hésitez-pas à me contacter pour proposer des idées d’amélioration.

Parlons Python

Le cœur de l’algorithme est dans la fonction suivante :

1
2
3
4
5
6
7
def _replace(regex: str, class_, case_sensitive=False) -> None:
    flags = 0 if case_sensitive else re.IGNORECASE
    for match in re.finditer(regex, _text, flags):
        if not any(pos in _content for pos in range(match.start(), match.end())):
            span = class_(match[0])
            for pos in range(match.start(), match.end()):
                _content[pos] = span

À la ligne 1 : class_ est la classe qui va être instanciée à partir des résultats de la recherche par l’expression régulière regex. J’ai créé une classe par type de son, cela me permet d’écrire facilement les tests, mais aussi de faire un générateur HTML générique, avec la classe mère suivante (partielle dans l’exemple) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Span:
    """Part of a text."""
    name = "normal"

    def __init__(self, content: str) -> None:
        self.content = content

    def to_html(self) -> str:
        """Convert the span to HTML."""
        if self.name == "normal":
            return self.content
        return f<span class="{self.name}">{self.content}</span>


class Eu(Span):  # Il y en a d’autres, mais j’abrège
    name = "eu"

À la ligne 2 de la fonction _replace, je crée un drapeau qui me permet de gérer le cas où je veux prendre en compte la casse (majuscule/minuscule) dans mes recherches. Généralement j’ignore la casse, mais « Jean porte un pantalon en jean. » montre que « Jean » et « jean » ne se prononcent pas de la même façon.

À la ligne 3, je parcours tous les résultats de recherche associés à la regex.

À la ligne 4, je m’assure de n’avoir pas déjà coloré ces lettres, ce qui me permet d’avoir les premières expressions régulières qui sont prioritaires sur les suivantes.

C’est à la ligne 5 que j’instancie les classes correspondant à chaque son (les sous-classes de Span).

Les lignes 6 et 7 complète la ligne 4, en marquant comme déjà coloré chaque lettre trouvée.

Il ne reste plus qu’à utiliser tout cela. Avec environ 80 expressions régulières judicieusement choisies, j’arrive à couvrir la majorité des mots du français.

Prenons par exemple :

1
2
_replace(r"([ae]in[st]+)\b", Un)
_replace(r"([ae]in)(?![e])", Un)

La première expression régulière permet de considérer que font le son « in » :

  • « ains » en fin de mot, comme dans « mains »
  • « aint » en fin de mot, comme dans « saint »
  • « aints » en fin de mot, comme dans « saints »
  • « eins » en fin de mot, comme dans « reins »
  • « eint » en fin de mot, comme dans « atteint »
  • « eints » en fin de mot, comme dans « atteints »

La seconde ajoute :

  • « ain » comme dans « main »
  • « ein » comme dans « rein »

Mais elle précise que ces lettres ne doivent pas être suivies d’un « e », sinon cela ne fera pas le son « in » :

  • « aine » comme dans « romaine »
  • « eine » comme dans « reine »

Ces expressions régulières seront complétés par exemple par :

1
_replace(r"(ei)(?!ll)", Ai)

Elle dit que « ei » se prononce « ai » si « ei » n’a pas encore été traité par d’autres expressions régulières, et si « ei » n’est pas suivi de « ll » comme dans « abeille », auquel cas « ille » fait un son à part (et donc une expression régulière supplémentaire).

Le son « eu » peut s’écrire « œufs » comme dans « œufs » ou « bœufs », il aura donc fallu mettre :

1
_replace(r"(œufs)\b", Eu)

Pour le son « oi », tel qu’expliqué précédemment, cela nécessite :

1
2
3
4
5
6
_replace(r"(oies?)", Oi)
_replace(r"(oigts?)", Oi)
_replace(r"(oi[dst]+)\b", Oi)
_replace(r"(oi)(?!n)", Oi)
_replace(r"(o)(?=y)", Oi)
_replace(r"(oê)", Oi)

Même le son « ou » est compliqué : chou, choux, verrous, clown, joues, où, août, cool…

Bref, c’est l’aventure de couvrir tout le français, je pense d’ailleurs que c’est impossible, il y a trop d’exceptions. Mais le résultat actuel est bon.

Reste la question de comment tester tout cela. Utilisons pytest:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def assert_same_spelling(spell1: List[Span], spell2: List[Span]) -> None:
    """Assert two lists of spans are equal."""
    for s1, s2 in zip(spell1, spell2):
        assert s1 == s2
    assert len(spell1) == len(spell2)


@pytest.mark.parametrize(
    "word,analyzed_word",
    [
        ("accepte", [Span("acc"), Ai("e"), Span("pte")]),
        ("alimentaire", [Span("alim"), An("en"), Span("t"), Ai("ai"), Span("re")]),
        ("aperçoit", [Span("ap"), Ai("e"), Span("rç"), Oi("oit")]),
        ("bientôt", [Span("bi"), Un("en"), Span("t"), Au("ôt")]),
        ("bijoux", [Span("bij"), Ou("oux")]),
        ("bleux", [Span("bl"), Eu("eux")]),
        ("brun", [Span("br"), Un("un")]),
        ("clown", [Span("cl"), Ou("ow"), Span("n")]),
        ("cœur", [Span("c"), Eu("œu"), Span("r")]),
        ("dépens", [Span("dép"), An("ens")]),
        ("doigts", [Span("d"), Oi("oigts")]),
        ("effroyable", [Ai("e"), Span("ffr"), Oi("o"), Span("yable")]),
        ("eurent", [Span("eurent")]),
        ("excuser", [Ai("e"), Span("xcuser")]),
        ("fonds", [Span("f"), On("onds")]),
        ("galops", [Span("gal"), Au("ops")]),
        ("humain", [Span("hum"), Un("ain")]),
        ("humaine", [Span("hum"), Ai("ai"), Span("ne")]),
        ("innocents", [Span("inn"), Au("o"), Span("c"), An("ents")]),
        ("joncs", [Span("j"), On("oncs")]),
        ("levaient", [Span("lev"), Ai("aient")]),
        ("monsieur", [Span("m"), Eu("on"), Span("si"), Eu("eur")]),
        ("noël", [Span("n"), Au("o"), Ai("ë"), Span("l")]),
        ("œuf", [Eu("œu"), Span("f")]),
        ("œufs", [Eu("œufs")]),
        ("paon", [Span("p"), An("aon")]),
        ("parfums", [Span("parf"), Un("ums")]),
    ],
)
def test_word_analysis(word, analyzed_word):
    assert_same_spelling(analyze_text(word), analyzed_word)

La liste des tests est tronquée, il y a environ 200 tests. Mais l’idée est toujours la même : à un mot est associée une prononciation. Après analyse automatique du mot, le résultat est comparé avec l’attendu. Merci pytest et son @pytest.mark.parametrize !

Documents complémentaires

Formation

Vous aimeriez pouvoir réaliser ce genre de sites web, je vous propose des formations qui pourraient vous aider :

Documents anglais

Documents français

Conclusion

Enfin, n’hésitez pas à commenter à la fin de cette page (tout en bas). Dites si vous avez aimé ou non cet article. Signalez des informations complémentaires, des idées d’exercices. Et si un sujet ne semble pas clair, posez une question.