DELETED gc_lang/fr/modules/lexicographe.py
Index: gc_lang/fr/modules/lexicographe.py
==================================================================
--- gc_lang/fr/modules/lexicographe.py
+++ /dev/null
@@ -1,234 +0,0 @@
-"""
-Grammalecte - Lexicographe
-"""
-
-# License: MPL 2
-
-
-import re
-import traceback
-
-
-_dTAGS = {
-    ':N': (" nom,", "Nom"),
-    ':A': (" adjectif,", "Adjectif"),
-    ':M1': (" prénom,", "Prénom"),
-    ':M2': (" patronyme,", "Patronyme, matronyme, nom de famille…"),
-    ':MP': (" nom propre,", "Nom propre"),
-    ':W': (" adverbe,", "Adverbe"),
-    ':J': (" interjection,", "Interjection"),
-    ':B': (" nombre,", "Nombre"),
-    ':T': (" titre,", "Titre de civilité"),
-
-    ':e': (" épicène", "épicène"),
-    ':m': (" masculin", "masculin"),
-    ':f': (" féminin", "féminin"),
-    ':s': (" singulier", "singulier"),
-    ':p': (" pluriel", "pluriel"),
-    ':i': (" invariable", "invariable"),
-
-    ':V1': (" verbe (1ᵉʳ gr.),", "Verbe du 1ᵉʳ groupe"),
-    ':V2': (" verbe (2ᵉ gr.),", "Verbe du 2ᵉ groupe"),
-    ':V3': (" verbe (3ᵉ gr.),", "Verbe du 3ᵉ groupe"),
-    ':V0e': (" verbe,", "Verbe auxiliaire être"),
-    ':V0a': (" verbe,", "Verbe auxiliaire avoir"),
-
-    ':Y': (" infinitif,", "infinitif"),
-    ':P': (" participe présent,", "participe présent"),
-    ':Q': (" participe passé,", "participe passé"),
-    ':Ip': (" présent,", "indicatif présent"),
-    ':Iq': (" imparfait,", "indicatif imparfait"),
-    ':Is': (" passé simple,", "indicatif passé simple"),
-    ':If': (" futur,", "indicatif futur"),
-    ':K': (" conditionnel présent,", "conditionnel présent"),
-    ':Sp': (" subjonctif présent,", "subjonctif présent"),
-    ':Sq': (" subjonctif imparfait,", "subjonctif imparfait"),
-    ':E': (" impératif,", "impératif"),
-
-    ':1s': (" 1ʳᵉ p. sg.,", "verbe : 1ʳᵉ personne du singulier"),
-    ':1ŝ': (" présent interr. 1ʳᵉ p. sg.,", "verbe : 1ʳᵉ personne du singulier (présent interrogatif)"),
-    ':1ś': (" présent interr. 1ʳᵉ p. sg.,", "verbe : 1ʳᵉ personne du singulier (présent interrogatif)"),
-    ':2s': (" 2ᵉ p. sg.,", "verbe : 2ᵉ personne du singulier"),
-    ':3s': (" 3ᵉ p. sg.,", "verbe : 3ᵉ personne du singulier"),
-    ':1p': (" 1ʳᵉ p. pl.,", "verbe : 1ʳᵉ personne du pluriel"),
-    ':2p': (" 2ᵉ p. pl.,", "verbe : 2ᵉ personne du pluriel"),
-    ':3p': (" 3ᵉ p. pl.,", "verbe : 3ᵉ personne du pluriel"),
-    ':3p!': (" 3ᵉ p. pl.,", "verbe : 3ᵉ personne du pluriel (prononciation distinctive)"),
-
-    ':G': ("", "Mot grammatical"),
-    ':X': (" adverbe de négation,", "Adverbe de négation"),
-    ':U': (" adverbe interrogatif,", "Adverbe interrogatif"),
-    ':R': (" préposition,", "Préposition"),
-    ':Rv': (" préposition verbale,", "Préposition verbale"),
-    ':D': (" déterminant,", "Déterminant"),
-    ':Dd': (" déterminant démonstratif,", "Déterminant démonstratif"),
-    ':De': (" déterminant exclamatif,", "Déterminant exclamatif"),
-    ':Dp': (" déterminant possessif,", "Déterminant possessif"),
-    ':Di': (" déterminant indéfini,", "Déterminant indéfini"),
-    ':Dn': (" déterminant négatif,", "Déterminant négatif"),
-    ':Od': (" pronom démonstratif,", "Pronom démonstratif"),
-    ':Oi': (" pronom indéfini,", "Pronom indéfini"),
-    ':On': (" pronom indéfini négatif,", "Pronom indéfini négatif"),
-    ':Ot': (" pronom interrogatif,", "Pronom interrogatif"),
-    ':Or': (" pronom relatif,", "Pronom relatif"),
-    ':Ow': (" pronom adverbial,", "Pronom adverbial"),
-    ':Os': (" pronom personnel sujet,", "Pronom personnel sujet"),
-    ':Oo': (" pronom personnel objet,", "Pronom personnel objet"),
-    ':Ov': (" préverbe,", "Préverbe (pronom personnel objet, +ne)"),
-    ':O1': (" 1ʳᵉ pers.,", "Pronom : 1ʳᵉ personne"),
-    ':O2': (" 2ᵉ pers.,", "Pronom : 2ᵉ personne"),
-    ':O3': (" 3ᵉ pers.,", "Pronom : 3ᵉ personne"),
-    ':C': (" conjonction,", "Conjonction"),
-    ':Ĉ': (" conjonction (él.),", "Conjonction (élément)"),
-    ':Cc': (" conjonction de coordination,", "Conjonction de coordination"),
-    ':Cs': (" conjonction de subordination,", "Conjonction de subordination"),
-    ':Ĉs': (" conjonction de subordination (él.),", "Conjonction de subordination (élément)"),
-
-    ':Ñ': (" locution nominale (él.),", "Locution nominale (élément)"),
-    ':Â': (" locution adjectivale (él.),", "Locution adjectivale (élément)"),
-    ':Ṽ': (" locution verbale (él.),", "Locution verbale (élément)"),
-    ':Ŵ': (" locution adverbiale (él.),", "Locution adverbiale (élément)"),
-    ':Ŕ': (" locution prépositive (él.),", "Locution prépositive (élément)"),
-    ':Ĵ': (" locution interjective (él.),", "Locution interjective (élément)"),
-
-    ':Zp': (" préfixe,", "Préfixe"),
-    ':Zs': (" suffixe,", "Suffixe"),
-
-    ':H': ("", "<Hors-norme, inclassable>"),
-
-    ':@': ("", "<Caractère non alpha-numérique>"),
-    ':@p': ("signe de ponctuation", "Signe de ponctuation"),
-    ':@s': ("signe", "Signe divers"),
-
-    ';S': (" : symbole (unité de mesure)", "Symbole (unité de mesure)"),
-
-    '/*': ("", "Sous-dictionnaire <Commun>"),
-    '/C': (" <classique>", "Sous-dictionnaire <Classique>"),
-    '/M': ("", "Sous-dictionnaire <Moderne>"),
-    '/R': (" <réforme>", "Sous-dictionnaire <Réforme 1990>"),
-    '/A': ("", "Sous-dictionnaire <Annexe>"),
-    '/X': ("", "Sous-dictionnaire <Contributeurs>")
-}
-
-_dPFX = {
-    'd': "(de), déterminant épicène invariable",
-    'l': "(le/la), déterminant masculin/féminin singulier",
-    'j': "(je), pronom personnel sujet, 1ʳᵉ pers., épicène singulier",
-    'm': "(me), pronom personnel objet, 1ʳᵉ pers., épicène singulier",
-    't': "(te), pronom personnel objet, 2ᵉ pers., épicène singulier",
-    's': "(se), pronom personnel objet, 3ᵉ pers., épicène singulier/pluriel",
-    'n': "(ne), adverbe de négation",
-    'c': "(ce), pronom démonstratif, masculin singulier/pluriel",
-    'ç': "(ça), pronom démonstratif, masculin singulier",
-    'qu': "(que), conjonction de subordination",
-    'lorsqu': "(lorsque), conjonction de subordination",
-    'puisqu': "(puisque), conjonction de subordination",
-    'quoiqu': "(quoique), conjonction de subordination",
-    'jusqu': "(jusque), préposition",
-}
-
-_dAD = {
-    'je': " pronom personnel sujet, 1ʳᵉ pers. sing.",
-    'tu': " pronom personnel sujet, 2ᵉ pers. sing.",
-    'il': " pronom personnel sujet, 3ᵉ pers. masc. sing.",
-    'on': " pronom personnel sujet, 3ᵉ pers. sing. ou plur.",
-    'elle': " pronom personnel sujet, 3ᵉ pers. fém. sing.",
-    'nous': " pronom personnel sujet/objet, 1ʳᵉ pers. plur.",
-    'vous': " pronom personnel sujet/objet, 2ᵉ pers. plur.",
-    'ils': " pronom personnel sujet, 3ᵉ pers. masc. plur.",
-    'elles': " pronom personnel sujet, 3ᵉ pers. masc. plur.",
-
-    "là": " particule démonstrative",
-    "ci": " particule démonstrative",
-
-    'le': " COD, masc. sing.",
-    'la': " COD, fém. sing.",
-    'les': " COD, plur.",
-
-    'moi': " COI (à moi), sing.",
-    'toi': " COI (à toi), sing.",
-    'lui': " COI (à lui ou à elle), sing.",
-    'nous2': " COI (à nous), plur.",
-    'vous2': " COI (à vous), plur.",
-    'leur': " COI (à eux ou à elles), plur.",
-
-    'y': " pronom adverbial",
-    "m'y": " (me) pronom personnel objet + (y) pronom adverbial",
-    "t'y": " (te) pronom personnel objet + (y) pronom adverbial",
-    "s'y": " (se) pronom personnel objet + (y) pronom adverbial",
-
-    'en': " pronom adverbial",
-    "m'en": " (me) pronom personnel objet + (en) pronom adverbial",
-    "t'en": " (te) pronom personnel objet + (en) pronom adverbial",
-    "s'en": " (se) pronom personnel objet + (en) pronom adverbial",
-}
-
-
-class Lexicographe:
-    "Lexicographer - word analyzer"
-
-    def __init__ (self, oSpellChecker):
-        self.oSpellChecker = oSpellChecker
-        self._zElidedPrefix = re.compile("(?i)^([dljmtsncç]|quoiqu|lorsqu|jusqu|puisqu|qu)['’](.+)")
-        self._zCompoundWord = re.compile("(?i)(\\w+)-((?:les?|la)-(?:moi|toi|lui|[nv]ous|leur)|t-(?:il|elle|on)|y|en|[mts][’'](?:y|en)|les?|l[aà]|[mt]oi|leur|lui|je|tu|ils?|elles?|on|[nv]ous)$")
-        self._zTag = re.compile("[:;/][\\w*][^:;/]*")
-
-    def analyzeWord (self, sWord):
-        "returns a tuple (a list of morphologies, a set of verb at infinitive form)"
-        try:
-            if not sWord:
-                return (None, None)
-            if sWord.count("-") > 4:
-                return (["élément complexe indéterminé"], None)
-            if sWord.isdigit():
-                return (["nombre"], None)
-
-            aMorph = []
-            # préfixes élidés
-            m = self._zElidedPrefix.match(sWord)
-            if m:
-                sWord = m.group(2)
-                aMorph.append( "{}’ : {}".format(m.group(1), _dPFX.get(m.group(1).lower(), "[?]")) )
-            # mots composés
-            m2 = self._zCompoundWord.match(sWord)
-            if m2:
-                sWord = m2.group(1)
-            # Morphologies
-            lMorph = self.oSpellChecker.getMorph(sWord)
-            if len(lMorph) > 1:
-                # sublist
-                aMorph.append( (sWord, [ self.formatTags(s)  for s in lMorph  if ":" in s ]) )
-            elif len(lMorph) == 1:
-                aMorph.append( "{} : {}".format(sWord, self.formatTags(lMorph[0])) )
-            else:
-                aMorph.append( "{} :  inconnu du dictionnaire".format(sWord) )
-            # suffixe d’un mot composé
-            if m2:
-                aMorph.append( "-{} : {}".format(m2.group(2), self._formatSuffix(m2.group(2).lower())) )
-            # Verbes
-            aVerb = { s[1:s.find("/")]  for s in lMorph  if ":V" in s }
-            return (aMorph, aVerb)
-        except (IndexError, TypeError):
-            traceback.print_exc()
-            return (["#erreur"], None)
-
-    def formatTags (self, sTags):
-        "returns string: readable tags"
-        sRes = ""
-        sTags = re.sub("(?<=V[1-3])[itpqnmr_eaxz]+", "", sTags)
-        sTags = re.sub("(?<=V0[ea])[itpqnmr_eaxz]+", "", sTags)
-        for m in self._zTag.finditer(sTags):
-            sRes += _dTAGS.get(m.group(0), " [{}]".format(m.group(0)))[0]
-        if sRes.startswith(" verbe") and not sRes.endswith("infinitif"):
-            sRes += " [{}]".format(sTags[1:sTags.find("/")])
-        return sRes.rstrip(",")
-
-    def _formatSuffix (self, s):
-        if s.startswith("t-"):
-            return "“t” euphonique +" + _dAD.get(s[2:], "[?]")
-        if not "-" in s:
-            return _dAD.get(s.replace("’", "'"), "[?]")
-        if s.endswith("ous"):
-            s += '2'
-        nPos = s.find("-")
-        return "%s +%s" % (_dAD.get(s[:nPos], "[?]"), _dAD.get(s[nPos+1:], "[?]"))

Index: gc_lang/fr/oxt/ContextMenu/ContextMenu.py
==================================================================
--- gc_lang/fr/oxt/ContextMenu/ContextMenu.py
+++ gc_lang/fr/oxt/ContextMenu/ContextMenu.py
@@ -8,30 +8,28 @@
 from com.sun.star.task import XJob
 from com.sun.star.ui import XContextMenuInterceptor
 #from com.sun.star.ui.ContextMenuInterceptorAction import IGNORED
 #from com.sun.star.ui.ContextMenuInterceptorAction import EXECUTE_MODIFIED
 
-import grammalecte.fr.lexicographe as lxg
 from grammalecte.graphspell.spellchecker import SpellChecker
 from grammalecte.graphspell.echo import echo
 import helpers
 
 
 xDesktop = None
 oSpellChecker = None
-oLexicographe = None
 
 
 class MyContextMenuInterceptor (XContextMenuInterceptor, unohelper.Base):
     def __init__ (self, ctx):
         self.ctx = ctx
 
     def notifyContextMenuExecute (self, xEvent):
         sWord = self._getWord()
         try:
-            aItem, aVerb = oLexicographe.analyzeWord(sWord)
-            if not aItem:
+            lWordAndMorph = oSpellChecker.analyze(sWord)
+            if not lWordAndMorph:
                 return uno.Enum("com.sun.star.ui.ContextMenuInterceptorAction", "IGNORED") # don’t work on AOO, have to import the value
                 #return IGNORED
             xContextMenu = xEvent.ActionTriggerContainer
             if xContextMenu:
                 # entries index
@@ -38,30 +36,31 @@
                 i = xContextMenu.Count
                 nUnoConstantLine = uno.getConstantByName("com.sun.star.ui.ActionTriggerSeparatorType.LINE")
 
                 # word analysis
                 i = self._addItemToContextMenu(xContextMenu, i, "ActionTriggerSeparator", SeparatorType=nUnoConstantLine)
-                for item in aItem:
-                    if isinstance(item, str):
-                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text=item, CommandURL="service:net.grammalecte.AppLauncher?None")
-                    elif isinstance(item, tuple):
-                        sRoot, lMorph = item
+                for sWord, lMorph in lWordAndMorph:
+                    if len(lMorph) == 1:
+                        sMorph, sReadableMorph = lMorph[0]
+                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text=sWord + " : " + sReadableMorph, CommandURL="service:net.grammalecte.AppLauncher?None")
+                    elif len(lMorph) >= 1:
                         # submenu
                         xSubMenuContainer = xContextMenu.createInstance("com.sun.star.ui.ActionTriggerContainer")
-                        for j, s in enumerate(lMorph):
-                            self._addItemToContextMenu(xSubMenuContainer, j, "ActionTrigger", Text=s, CommandURL="service:net.grammalecte.AppLauncher?None")
+                        for j, (sMorph, sReadableMorph) in enumerate(lMorph):
+                            self._addItemToContextMenu(xSubMenuContainer, j, "ActionTrigger", Text=sReadableMorph, CommandURL="service:net.grammalecte.AppLauncher?None")
                         # create root menu entry
-                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text=sRoot, SubContainer=xSubMenuContainer)
+                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text=sWord, SubContainer=xSubMenuContainer)
                     else:
-                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text="# erreur : {}".format(item))
+                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text=sWord + " : [erreur] aucun résultat trouvé.")
 
                 # Links to Conjugueur
+                aVerb = { sMorph[1:sMorph.find("/")]  for sMorph in oSpellChecker.getMorph(sWord) if ":V" in sMorph }
                 if aVerb:
                     i = self._addItemToContextMenu(xContextMenu, i, "ActionTriggerSeparator", SeparatorType=nUnoConstantLine)
                     for sVerb in aVerb:
-                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text="Conjuguer “{}”…".format(sVerb),
-                                                       CommandURL="service:net.grammalecte.AppLauncher?CJ/"+sVerb)
+                        i = self._addItemToContextMenu(xContextMenu, i, "ActionTrigger", Text="Conjuguer “{}”…".format(sVerb), \
+                                                        CommandURL="service:net.grammalecte.AppLauncher?CJ/"+sVerb)
 
                 # Search
                 xDoc = xDesktop.getCurrentComponent()
                 xViewCursor = xDoc.CurrentController.ViewCursor
                 if not xViewCursor.isCollapsed():
@@ -116,11 +115,10 @@
 class JobExecutor (XJob, unohelper.Base):
     def __init__ (self, ctx):
         self.ctx = ctx
         global xDesktop
         global oSpellChecker
-        global oLexicographe
         try:
             if not xDesktop:
                 xDesktop = self.ctx.getServiceManager().createInstanceWithContext('com.sun.star.frame.Desktop', self.ctx)
             if not oSpellChecker:
                 xCurCtx = uno.getComponentContext()
@@ -128,12 +126,10 @@
                 if hasattr(oGC, "getSpellChecker"):
                     # https://bugs.documentfoundation.org/show_bug.cgi?id=97790
                     oSpellChecker = oGC.getSpellChecker()
                 else:
                     oSpellChecker = SpellChecker("${lang}", "fr-allvars.bdic")
-            if not oLexicographe:
-                oLexicographe = lxg.Lexicographe(oSpellChecker)
         except:
             traceback.print_exc()
 
     def execute (self, args):
         if not args:

Index: grammalecte-cli.py
==================================================================
--- grammalecte-cli.py
+++ grammalecte-cli.py
@@ -152,11 +152,10 @@
     xParser.add_argument("-d", "--debug", help="debugging mode (only in interactive mode)", action="store_true")
     xArgs = xParser.parse_args()
 
     grammalecte.load()
     oSpellChecker = grammalecte.getSpellChecker()
-    oLexicographer = grammalecte.getLexicographer()
     if xArgs.personal_dict:
         oJSON = loadDictionary(xArgs.personal_dict)
         if oJSON:
             oSpellChecker.setPersonalDictionary(oJSON)
 
@@ -269,12 +268,14 @@
         while True:
             if sText.startswith("?"):
                 for sWord in sText[1:].strip().split():
                     if sWord:
                         echo("* " + sWord)
-                        for sMorph in oSpellChecker.getMorph(sWord):
-                            echo("  {:<32} {}".format(sMorph, oLexicographer.formatTags(sMorph)))
+                        for sElem, aRes in oSpellChecker.analyze(sWord):
+                            echo("  - " + sElem)
+                            for sMorph, sMeaning in aRes:
+                                echo("      {:<40}  {}".format(sMorph, sMeaning))
             elif sText.startswith("!"):
                 for sWord in sText[1:].strip().split():
                     if sWord:
                         for lSugg in oSpellChecker.suggest(sWord):
                             echo(" | ".join(lSugg))

Index: graphspell/fr.py
==================================================================
--- graphspell/fr.py
+++ graphspell/fr.py
@@ -1,9 +1,23 @@
 """
-Default suggestion for French language
+Lexicographer for the French language
 """
 
+# Note:
+# This mode must contains at least:
+#     <dSugg> : a dictionary for default suggestions.
+#     <bLexicographer> : a boolean False
+#       if the boolean is True, 3 functions are required:
+#           split(sWord) -> returns a list of string (that will be analyzed)
+#           analyze(sWord) -> returns a string with the meaning of word
+#           formatTags(sTags) -> returns a string with the meaning of tags
+
+
+import re
+
+#### Suggestions
+
 dSugg = {
     "bcp": "beaucoup",
     "ca": "ça",
     "cad": "c’est-à-dire",
     "cb": "combien|CB",
@@ -82,10 +96,11 @@
     "XXVIième": "XXVIᵉ",
     "XXVIIième": "XXVIIᵉ",
     "XXVIIIième": "XXVIIIᵉ",
     "XXIXième": "XXIXᵉ",
     "XXXième": "XXXᵉ",
+
     "Ier": "Iᵉʳ",
     "Ière": "Iʳᵉ",
     "IIème": "IIᵉ",
     "IIIème": "IIIᵉ",
     "IVème": "IVᵉ",
@@ -114,5 +129,226 @@
     "XXVIIème": "XXVIIᵉ",
     "XXVIIIème": "XXVIIIᵉ",
     "XXIXème": "XXIXᵉ",
     "XXXème": "XXXᵉ"
 }
+
+
+#### Lexicographer
+
+bLexicographer = True
+
+_dTAGS = {
+    ':N': (" nom,", "Nom"),
+    ':A': (" adjectif,", "Adjectif"),
+    ':M1': (" prénom,", "Prénom"),
+    ':M2': (" patronyme,", "Patronyme, matronyme, nom de famille…"),
+    ':MP': (" nom propre,", "Nom propre"),
+    ':W': (" adverbe,", "Adverbe"),
+    ':J': (" interjection,", "Interjection"),
+    ':B': (" nombre,", "Nombre"),
+    ':T': (" titre,", "Titre de civilité"),
+
+    ':e': (" épicène", "épicène"),
+    ':m': (" masculin", "masculin"),
+    ':f': (" féminin", "féminin"),
+    ':s': (" singulier", "singulier"),
+    ':p': (" pluriel", "pluriel"),
+    ':i': (" invariable", "invariable"),
+
+    ':V1': (" verbe (1ᵉʳ gr.),", "Verbe du 1ᵉʳ groupe"),
+    ':V2': (" verbe (2ᵉ gr.),", "Verbe du 2ᵉ groupe"),
+    ':V3': (" verbe (3ᵉ gr.),", "Verbe du 3ᵉ groupe"),
+    ':V0e': (" verbe,", "Verbe auxiliaire être"),
+    ':V0a': (" verbe,", "Verbe auxiliaire avoir"),
+
+    ':Y': (" infinitif,", "infinitif"),
+    ':P': (" participe présent,", "participe présent"),
+    ':Q': (" participe passé,", "participe passé"),
+    ':Ip': (" présent,", "indicatif présent"),
+    ':Iq': (" imparfait,", "indicatif imparfait"),
+    ':Is': (" passé simple,", "indicatif passé simple"),
+    ':If': (" futur,", "indicatif futur"),
+    ':K': (" conditionnel présent,", "conditionnel présent"),
+    ':Sp': (" subjonctif présent,", "subjonctif présent"),
+    ':Sq': (" subjonctif imparfait,", "subjonctif imparfait"),
+    ':E': (" impératif,", "impératif"),
+
+    ':1s': (" 1ʳᵉ p. sg.,", "verbe : 1ʳᵉ personne du singulier"),
+    ':1ŝ': (" présent interr. 1ʳᵉ p. sg.,", "verbe : 1ʳᵉ personne du singulier (présent interrogatif)"),
+    ':1ś': (" présent interr. 1ʳᵉ p. sg.,", "verbe : 1ʳᵉ personne du singulier (présent interrogatif)"),
+    ':2s': (" 2ᵉ p. sg.,", "verbe : 2ᵉ personne du singulier"),
+    ':3s': (" 3ᵉ p. sg.,", "verbe : 3ᵉ personne du singulier"),
+    ':1p': (" 1ʳᵉ p. pl.,", "verbe : 1ʳᵉ personne du pluriel"),
+    ':2p': (" 2ᵉ p. pl.,", "verbe : 2ᵉ personne du pluriel"),
+    ':3p': (" 3ᵉ p. pl.,", "verbe : 3ᵉ personne du pluriel"),
+    ':3p!': (" 3ᵉ p. pl.,", "verbe : 3ᵉ personne du pluriel (prononciation distinctive)"),
+
+    ':G': ("", "Mot grammatical"),
+    ':X': (" adverbe de négation,", "Adverbe de négation"),
+    ':U': (" adverbe interrogatif,", "Adverbe interrogatif"),
+    ':R': (" préposition,", "Préposition"),
+    ':Rv': (" préposition verbale,", "Préposition verbale"),
+    ':D': (" déterminant,", "Déterminant"),
+    ':Dd': (" déterminant démonstratif,", "Déterminant démonstratif"),
+    ':De': (" déterminant exclamatif,", "Déterminant exclamatif"),
+    ':Dp': (" déterminant possessif,", "Déterminant possessif"),
+    ':Di': (" déterminant indéfini,", "Déterminant indéfini"),
+    ':Dn': (" déterminant négatif,", "Déterminant négatif"),
+    ':Od': (" pronom démonstratif,", "Pronom démonstratif"),
+    ':Oi': (" pronom indéfini,", "Pronom indéfini"),
+    ':On': (" pronom indéfini négatif,", "Pronom indéfini négatif"),
+    ':Ot': (" pronom interrogatif,", "Pronom interrogatif"),
+    ':Or': (" pronom relatif,", "Pronom relatif"),
+    ':Ow': (" pronom adverbial,", "Pronom adverbial"),
+    ':Os': (" pronom personnel sujet,", "Pronom personnel sujet"),
+    ':Oo': (" pronom personnel objet,", "Pronom personnel objet"),
+    ':Ov': (" préverbe,", "Préverbe (pronom personnel objet, +ne)"),
+    ':O1': (" 1ʳᵉ pers.,", "Pronom : 1ʳᵉ personne"),
+    ':O2': (" 2ᵉ pers.,", "Pronom : 2ᵉ personne"),
+    ':O3': (" 3ᵉ pers.,", "Pronom : 3ᵉ personne"),
+    ':C': (" conjonction,", "Conjonction"),
+    ':Ĉ': (" conjonction (él.),", "Conjonction (élément)"),
+    ':Cc': (" conjonction de coordination,", "Conjonction de coordination"),
+    ':Cs': (" conjonction de subordination,", "Conjonction de subordination"),
+    ':Ĉs': (" conjonction de subordination (él.),", "Conjonction de subordination (élément)"),
+
+    ':Ñ': (" locution nominale (él.),", "Locution nominale (élément)"),
+    ':Â': (" locution adjectivale (él.),", "Locution adjectivale (élément)"),
+    ':Ṽ': (" locution verbale (él.),", "Locution verbale (élément)"),
+    ':Ŵ': (" locution adverbiale (él.),", "Locution adverbiale (élément)"),
+    ':Ŕ': (" locution prépositive (él.),", "Locution prépositive (élément)"),
+    ':Ĵ': (" locution interjective (él.),", "Locution interjective (élément)"),
+
+    ':Zp': (" préfixe,", "Préfixe"),
+    ':Zs': (" suffixe,", "Suffixe"),
+
+    ':H': ("", "<Hors-norme, inclassable>"),
+
+    ':@': ("", "<Caractère non alpha-numérique>"),
+    ':@p': ("signe de ponctuation", "Signe de ponctuation"),
+    ':@s': ("signe", "Signe divers"),
+
+    ';S': (" : symbole (unité de mesure)", "Symbole (unité de mesure)"),
+
+    '/*': ("", "Sous-dictionnaire <Commun>"),
+    '/C': (" <classique>", "Sous-dictionnaire <Classique>"),
+    '/M': ("", "Sous-dictionnaire <Moderne>"),
+    '/R': (" <réforme>", "Sous-dictionnaire <Réforme 1990>"),
+    '/A': ("", "Sous-dictionnaire <Annexe>"),
+    '/X': ("", "Sous-dictionnaire <Contributeurs>")
+}
+
+_dValues = {
+    'd’': "(de), préposition ou déterminant épicène invariable",
+    'l’': "(le/la), déterminant ou pronom personnel objet, masculin/féminin singulier",
+    'j’': "(je), pronom personnel sujet, 1ʳᵉ pers., épicène singulier",
+    'm’': "(me), pronom personnel objet, 1ʳᵉ pers., épicène singulier",
+    't’': "(te), pronom personnel objet, 2ᵉ pers., épicène singulier",
+    's’': "(se), pronom personnel objet, 3ᵉ pers., épicène singulier/pluriel",
+    'n’': "(ne), adverbe de négation",
+    'c’': "(ce), pronom démonstratif, masculin singulier/pluriel",
+    'ç’': "(ça), pronom démonstratif, masculin singulier",
+    'qu’': "(que), conjonction de subordination",
+    'lorsqu’': "(lorsque), conjonction de subordination",
+    'puisqu’': "(puisque), conjonction de subordination",
+    'quoiqu’': "(quoique), conjonction de subordination",
+    'jusqu’': "(jusque), préposition",
+
+    '-je': " pronom personnel sujet, 1ʳᵉ pers. sing.",
+    '-tu': " pronom personnel sujet, 2ᵉ pers. sing.",
+    '-il': " pronom personnel sujet, 3ᵉ pers. masc. sing.",
+    '-on': " pronom personnel sujet, 3ᵉ pers. sing. ou plur.",
+    '-elle': " pronom personnel sujet, 3ᵉ pers. fém. sing.",
+    '-t-il': " “t” euphonique + pronom personnel sujet, 3ᵉ pers. masc. sing.",
+    '-t-on': " “t” euphonique + pronom personnel sujet, 3ᵉ pers. sing. ou plur.",
+    '-t-elle': " “t” euphonique + pronom personnel sujet, 3ᵉ pers. fém. sing.",
+    '-nous': " pronom personnel sujet/objet, 1ʳᵉ pers. plur.  ou  COI (à nous), plur.",
+    '-vous': " pronom personnel sujet/objet, 2ᵉ pers. plur.  ou  COI (à vous), plur.",
+    '-ils': " pronom personnel sujet, 3ᵉ pers. masc. plur.",
+    '-elles': " pronom personnel sujet, 3ᵉ pers. masc. plur.",
+
+    "-là": " particule démonstrative",
+    "-ci": " particule démonstrative",
+
+    '-le': " COD, masc. sing.",
+    '-la': " COD, fém. sing.",
+    '-les': " COD, plur.",
+
+    '-moi': " COI (à moi), sing.",
+    '-toi': " COI (à toi), sing.",
+    '-lui': " COI (à lui ou à elle), sing.",
+    '-leur': " COI (à eux ou à elles), plur.",
+
+    '-le-moi': " COD, masc. sing. + COI (à moi), sing.",
+    '-le-toi': " COD, masc. sing. + COI (à toi), sing.",
+    '-le-lui': " COD, masc. sing. + COI (à lui ou à elle), sing.",
+    '-le-nous': " COD, masc. sing. + COI (à nous), plur.",
+    '-le-vous': " COD, masc. sing. + COI (à vous), plur.",
+    '-le-leur': " COD, masc. sing. + COI (à eux ou à elles), plur.",
+
+    '-la-moi': " COD, fém. sing. + COI (à moi), sing.",
+    '-la-toi': " COD, fém. sing. + COI (à toi), sing.",
+    '-la-lui': " COD, fém. sing. + COI (à lui ou à elle), sing.",
+    '-la-nous': " COD, fém. sing. + COI (à nous), plur.",
+    '-la-vous': " COD, fém. sing. + COI (à vous), plur.",
+    '-la-leur': " COD, fém. sing. + COI (à eux ou à elles), plur.",
+
+    '-les-moi': " COD, plur. + COI (à moi), sing.",
+    '-les-toi': " COD, plur. + COI (à toi), sing.",
+    '-les-lui': " COD, plur. + COI (à lui ou à elle), sing.",
+    '-les-nous': " COD, plur. + COI (à nous), plur.",
+    '-les-vous': " COD, plur. + COI (à vous), plur.",
+    '-les-leur': " COD, plur. + COI (à eux ou à elles), plur.",
+
+    '-y': " pronom adverbial",
+    "-m’y": " (me) pronom personnel objet + (y) pronom adverbial",
+    "-t’y": " (te) pronom personnel objet + (y) pronom adverbial",
+    "-s’y": " (se) pronom personnel objet + (y) pronom adverbial",
+
+    '-en': " pronom adverbial",
+    "-m’en": " (me) pronom personnel objet + (en) pronom adverbial",
+    "-t’en": " (te) pronom personnel objet + (en) pronom adverbial",
+    "-s’en": " (se) pronom personnel objet + (en) pronom adverbial",
+}
+
+
+_zElidedPrefix = re.compile("(?i)^((?:[dljmtsncç]|quoiqu|lorsqu|jusqu|puisqu|qu)’)(.+)")
+_zCompoundWord = re.compile("(?i)(\\w+)(-(?:(?:les?|la)-(?:moi|toi|lui|[nv]ous|leur)|t-(?:il|elle|on)|y|en|[mts]’(?:y|en)|les?|l[aà]|[mt]oi|leur|lui|je|tu|ils?|elles?|on|[nv]ous))$")
+_zTag = re.compile("[:;/][\\w*][^:;/]*")
+
+def split (sWord):
+    "split word in 3 parts: prefix, root, suffix"
+    sWord = sWord.replace("'", "’")
+    sPrefix = ""
+    sSuffix = ""
+    # préfixe élidé
+    m = _zElidedPrefix.match(sWord)
+    if m:
+        sPrefix = m.group(1)
+        sWord = m.group(2)
+    # mots composés
+    m = _zCompoundWord.match(sWord)
+    if m:
+        sWord = m.group(1)
+        sSuffix = m.group(2)
+    return sPrefix, sWord, sSuffix
+
+
+def analyze (sWord):
+    "return meaning of <sWord> if found else an empty string"
+    sWord = sWord.lower()
+    if sWord in _dValues:
+        return _dValues[sWord]
+    return ""
+
+
+def formatTags (sTags):
+    "returns string: readable tags"
+    sRes = ""
+    sTags = re.sub("(?<=V[1-3])[itpqnmr_eaxz]+", "", sTags)
+    sTags = re.sub("(?<=V0[ea])[itpqnmr_eaxz]+", "", sTags)
+    for m in _zTag.finditer(sTags):
+        sRes += _dTAGS.get(m.group(0), " [{}]".format(m.group(0)))[0]
+    if sRes.startswith(" verbe") and not sRes.endswith("infinitif"):
+        sRes += " [{}]".format(sTags[1:sTags.find("/")])
+    return sRes.rstrip(",")

Index: graphspell/spellchecker.py
==================================================================
--- graphspell/spellchecker.py
+++ graphspell/spellchecker.py
@@ -34,12 +34,12 @@
         self.oPersonalDic = self._loadDictionary(sfPersonalDic)
         self.bCommunityDic = bool(self.oCommunityDic)
         self.bPersonalDic = bool(self.oPersonalDic)
         self.oTokenizer = None
         # Default suggestions
-        self.dDefaultSugg = None
-        self.loadSuggestions(sLangCode)
+        self.lexicographer = None
+        self.loadLang(sLangCode)
         # storage
         self.bStorage = False
         self._dMorphologies = {}        # key: flexion, value: list of morphologies
         self._dLemmas = {}              # key: flexion, value: list of lemmas
 
@@ -100,18 +100,34 @@
         self.bPersonalDic = False
 
 
     # Default suggestions
 
-    def loadSuggestions (self, sLangCode):
+    def loadLang (self, sLangCode):
         "load default suggestion module for <sLangCode>"
         try:
-            suggest = importlib.import_module("."+sLangCode, "grammalecte.graphspell")
+            self.lexicographer = importlib.import_module("."+sLangCode, "grammalecte.graphspell")
         except ImportError:
             print("No suggestion module for language <"+sLangCode+">")
             return
-        self.dDefaultSugg = suggest.dSugg
+
+    def analyze (self, sWord):
+        "returns a list of words and their morphologies"
+        if not self.lexicographer:
+            return []
+        lWordAndMorph = []
+        for sElem in self.lexicographer.split(sWord):
+            if sElem:
+                lMorph = self.getMorph(sElem)
+                sLex = self.lexicographer.analyze(sElem)
+                if sLex:
+                    aRes = [ (" | ".join(lMorph), sLex) ]
+                else:
+                    aRes = [ (sMorph, self.lexicographer.formatTags(sMorph)) for sMorph in lMorph ]
+                if aRes:
+                    lWordAndMorph.append((sElem, aRes))
+        return lWordAndMorph
 
 
     # Storage
 
     def activateStorage (self):
@@ -159,10 +175,11 @@
                         dWord[dToken['sValue']] = dWord.get(dToken['sValue'], 0) + 1
                     else:
                         for sLemma in self.getLemma(dToken['sValue']):
                             dWord[sLemma] = dWord.get(sLemma, 0) + 1
         return dWord
+
 
     # IBDAWG functions
 
     def isValidToken (self, sToken):
         "checks if sToken is valid (if there is hyphens in sToken, sToken is split, each part is checked)"
@@ -216,15 +233,15 @@
             return self._dLemmas[sWord]
         return { s[1:s.find("/")]  for s in self.getMorph(sWord) }
 
     def suggest (self, sWord, nSuggLimit=10):
         "generator: returns 1, 2 or 3 lists of suggestions"
-        if self.dDefaultSugg:
-            if sWord in self.dDefaultSugg:
-                yield self.dDefaultSugg[sWord].split("|")
-            elif sWord.istitle() and sWord.lower() in self.dDefaultSugg:
-                lRes = self.dDefaultSugg[sWord.lower()].split("|")
+        if self.lexicographer.dSugg:
+            if sWord in self.lexicographer.dSugg:
+                yield self.lexicographer.dSugg[sWord].split("|")
+            elif sWord.istitle() and sWord.lower() in self.lexicographer.dSugg:
+                lRes = self.lexicographer.dSugg[sWord.lower()].split("|")
                 yield list(map(lambda sSugg: sSugg[0:1].upper()+sSugg[1:], lRes))
             else:
                 yield self.oMainDic.suggest(sWord, nSuggLimit, True)
         else:
             yield self.oMainDic.suggest(sWord, nSuggLimit, True)