.gitignore
[comptalang.git] / cli / Hcompta / CLI / Lang.hs
index 772a6b3dfe81094034cef1c7a90274003c269d8e..358ab1e05991fb44bbeb9b4199d088a1aebe554a 100644 (file)
@@ -2,13 +2,14 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 module Hcompta.CLI.Lang where
 
 import           Control.Monad (liftM)
 import qualified Data.List
 import           Data.Maybe (catMaybes, fromMaybe)
-import qualified Data.Text
+import qualified Data.Text as Text
 import           Data.Text (Text)
 import qualified Data.Text.Lazy as TL
 import           Prelude hiding (error)
@@ -17,65 +18,90 @@ import           System.IO.Memoize (once)
 import qualified Text.Parsec as Parsec
 import qualified Text.Parsec.Error as Parsec.Error
 
-import           Hcompta.Amount (Amount)
-import           Hcompta.Amount.Unit (Unit)
-import qualified Hcompta.Amount.Write as Amount.Write
-import qualified Hcompta.Date.Read as Date.Read
+import           Hcompta.Account (Account_Anchor)
+import qualified Hcompta.Format.Ledger       as Ledger
+import qualified Hcompta.Format.Ledger.Write as Ledger
+import qualified Hcompta.Format.JCC as JCC
+import qualified Hcompta.Format.JCC.Amount as JCC.Amount
+import qualified Hcompta.Format.JCC.Amount.Write as JCC.Amount.Write
+import qualified Hcompta.Format.JCC.Date.Write as JCC.Date.Write
+import qualified Hcompta.Format.JCC.Write as JCC.Write
+import           Hcompta.Date (Date)
+import qualified Hcompta.Filter.Date.Read as Date.Read
 import qualified Hcompta.Filter.Read as Filter.Read
 import           Hcompta.Lib.Leijen (ToDoc(..), (<>))
 import qualified Hcompta.Lib.Leijen as W
 import qualified Hcompta.Lib.Parsec as Lib.Parsec
+import           Hcompta.Transaction (Transaction_Anchor(..))
+import qualified Hcompta.Unit as Unit
 
+-- * Type 'Lang'
 data Lang
- = FR
- | EN
+ = EN
+ | FR
  deriving (Show)
 
+-- * Class 'Translate'
+class Translate from to where
+       translate :: Lang -> from -> to
+instance Translate e e where
+       translate _lang = id
+
 -- TODO: check that this is expected behavior
 --       and portability issues
-get_lang :: IO Lang
-get_lang = do
+from_Env :: IO Lang
+from_Env = do
        once getEnvironment
        >>= liftM (\env ->
-               fromMaybe EN $ lang_of_strings $
+               fromMaybe EN $ from_Strings $
                Data.List.concatMap
                 ((\lang ->
-                       let short = takeWhile ('_' /=) lang in
+                       let short = takeWhile (/= '_') lang in
                        if short == lang
                        then [lang]
                        else [lang, short])
-                . Data.List.takeWhile (\c -> c /= '.') ) $
+                . Data.List.takeWhile (/= '.') ) $
                catMaybes
                 [ Data.List.lookup "LC_ALL"   env
                 , Data.List.lookup "LC_CTYPE" env
                 , Data.List.lookup "LANG"     env
                 ])
 
-lang_of_strings :: [String] -> Maybe Lang
-lang_of_strings s =
+from_Strings :: [String] -> Maybe Lang
+from_Strings s =
        case s of
-        ("fr"   :_) -> Just FR
-        ("fr_FR":_) -> Just FR
-        ("en"   :_) -> Just EN
-        ("en_US":_) -> Just EN
-        (_:xs)      -> lang_of_strings xs
+        ("fr"   :_) -> Just FR
+        ("fr_FR":_) -> Just FR
+        ("en"   :_) -> Just EN
+        ("en_US":_) -> Just EN
+        (_:xs)      -> from_Strings xs
         []          -> Nothing
 
 (#) :: ToDoc () a => a -> W.Doc
 (#) = toDoc ()
 
-instance ToDoc m Text where
+instance ToDoc () Text where
        toDoc _ = W.strict_text
-instance ToDoc m String where
-       toDoc _ = W.strict_text . Data.Text.pack
-instance ToDoc m Int where
+instance ToDoc () String where
+       toDoc _ = W.strict_text . Text.pack
+instance ToDoc () Int where
        toDoc _ = W.int
-instance ToDoc m Integer where
+instance ToDoc () Integer where
        toDoc _ = W.integer
-instance ToDoc m Unit where
-       toDoc _ = Amount.Write.unit
-instance ToDoc m Amount where
-       toDoc _ = Amount.Write.amount
+instance ToDoc () JCC.Unit where
+       toDoc _ = JCC.Amount.Write.unit
+instance ToDoc () Account_Anchor where
+       toDoc _ = JCC.Write.account_anchor
+instance ToDoc () Transaction_Anchor where
+       toDoc _ = JCC.Write.transaction_anchor
+instance ToDoc () (JCC.Amount.Styled JCC.Amount) where
+       toDoc _ = JCC.Amount.Write.amount
+instance ToDoc () Ledger.Unit where
+       toDoc _ = Ledger.write_unit
+instance ToDoc () (Ledger.Amount_Styled Ledger.Amount) where
+       toDoc _ = Ledger.write_amount
+instance ToDoc () Date where
+       toDoc _ = JCC.Date.Write.date
 instance ToDoc Lang Date.Read.Error where
        toDoc FR e =
                case e of
@@ -93,44 +119,256 @@ instance ToDoc Lang Date.Read.Error where
                        "invalid date (year " <> (#)year <> ", month " <> (#)month <> ", day " <> (#)day <> ")"
                 Date.Read.Error_invalid_time_of_day (hour, minute, second) ->
                        "invalid time of day (hour " <> (#)hour <> ", minute " <> (#)minute <> ", second " <> (#)second <> ")"
-instance ToDoc Lang Parsec.SourcePos where
-       toDoc EN pos = do
+instance Translate Filter.Read.Error W.Doc where
+       translate lang@FR err =
+               case err of
+                Filter.Read.Error_Unknown -> "erreur"
+                Filter.Read.Error_Filter_Date d -> toDoc lang d
+                Filter.Read.Error_Filter_Date_Interval (l, h) ->
+                       "mauvais intervalle: (" <> toDoc () l <> ", " <> toDoc () h <> ")"
+       translate lang@EN err =
+               case err of
+                Filter.Read.Error_Unknown -> "error"
+                Filter.Read.Error_Filter_Date d -> toDoc lang d
+                Filter.Read.Error_Filter_Date_Interval (l, h) ->
+                       "wrong interval: (" <> toDoc () l <> ", " <> toDoc () h <> ")"
+
+-- * Type 'Account'
+data Account
+ =   Account_Equilibrium
+instance Translate Account Ledger.Account where
+       translate EN t =
+               case t of
+                Account_Equilibrium -> Ledger.account "Equilibrium" []
+       translate FR t =
+               case t of
+                Account_Equilibrium -> Ledger.account "Équilibre" []
+
+-- * Type 'Comment'
+data Comment
+ =   Comment_Equilibrium
+instance Translate Comment Text where
+       translate EN t =
+               case t of
+                Comment_Equilibrium -> "Equilibrium posting"
+       translate FR t =
+               case t of
+                Comment_Equilibrium -> "Mouvement d’équilibre"
+
+-- * Type 'Description'
+data Description
+ =   Description_Exercise Exercise_OC
+data Exercise_OC
+ =   Exercise_Opening
+ |   Exercise_Closing
+ deriving (Eq, Show)
+
+instance Translate Description Text where
+       translate EN t =
+               case t of
+                Description_Exercise oc ->
+                       case oc of
+                        Exercise_Opening -> "Opening balance"
+                        Exercise_Closing -> "Closing balance"
+       translate FR t =
+               case t of
+                Description_Exercise oc ->
+                       case oc of
+                        Exercise_Opening -> "Solde d’ouverture"
+                        Exercise_Closing -> "Solde de fermeture"
+
+-- * Type 'Error'
+data Error
+ =   Error_Account_Anchor_is_not_unique Parsec.SourcePos Account_Anchor
+ |   Error_Account_Anchor_unknown Parsec.SourcePos Account_Anchor
+ |   Error_Failed_to_include_file FilePath
+ |   Error_Failed_to_read_file FilePath
+ |   Error_No_input_file_given
+ |   Error_One_command_is_required
+ |   Error_Option_Balance_Format
+ |   Error_Option_Balance_Heritage
+ |   Error_Option_Balance_Redundant
+ |   Error_Option_Balance_Total
+ |   Error_Option_Color
+ |   Error_Option_Equilibrium
+ |   Error_Option_Equilibrium_Credit
+ |   Error_Option_Equilibrium_Debit
+ |   Error_Option_Tags_Tree
+ |   Error_Option_Verbosity
+ |   Error_Transaction_Anchor_unknown Parsec.SourcePos Transaction_Anchor
+ |   Error_Transaction_Anchor_is_not_unique Parsec.SourcePos Transaction_Anchor
+ |   Error_Transaction_Invalid_date Integer Int Int
+ |   Error_Transaction_Invalid_time_of_day Int Int Integer
+ |   Error_Transaction_The_following_transaction_is_not_equilibrated_because
+ |   Error_Transaction_The_following_virtual_transaction_is_not_equilibrated_because
+ |   Error_Transaction_JCC_Unit_sums_up_to_the_non_null_amount JCC.Unit (JCC.Amount.Styled JCC.Amount)
+ |   Error_Transaction_Ledger_Unit_sums_up_to_the_non_null_amount Ledger.Unit (Ledger.Amount_Styled Ledger.Amount)
+ |   Error_Transaction_Year_or_day_is_missing
+ |   Error_Unkown_command String
+instance Translate Error W.Doc where
+       translate EN t =
+               case t of
+                Error_Account_Anchor_is_not_unique _pos anchor -> "Account anchor is not unique: " <> (#)anchor
+                Error_Account_Anchor_unknown _pos anchor -> "Account anchor unkown: " <> (#)anchor
+                Error_Failed_to_read_file path    -> "failed to read file: " <> (#)path
+                Error_Failed_to_include_file path -> "failed to include file: " <> (#)path
+                Error_No_input_file_given         ->
+                       W.vsep
+                        [ "no input file given, please use:"
+                        , "- either -i $hcompta_journal parameter"
+                        , "- or HCOMPTA_JOURNAL environment variable."
+                        ]
+                Error_One_command_is_required     -> "a COMMAND is required"
+                Error_Option_Balance_Format       -> "--format option expects \"close\", \"open\", or \"table\" as argument"
+                Error_Option_Balance_Heritage     -> "--heritage option expects \"yes\" or \"no\" as argument"
+                Error_Option_Balance_Redundant    -> "--redundant option expects \"yes\" or \"no\" as argument"
+                Error_Option_Balance_Total        -> "--total option expects \"yes\" or \"no\" as argument"
+                Error_Option_Color                -> "--color option expects \"auto\" (default), \"yes\" or \"no\" as argument"
+                Error_Option_Equilibrium          -> "--eq option expects an ACCOUNT"
+                Error_Option_Equilibrium_Credit   -> "--eq-credit option expects an ACCOUNT"
+                Error_Option_Equilibrium_Debit    -> "--eq-debit  option expects an ACCOUNT"
+                Error_Option_Tags_Tree            -> "--tree option expects \"yes\" or \"no\" as value"
+                Error_Option_Verbosity            -> "--verbosity option expects \"error\", \"warn\", \"info\" or \"debug\" as argument"
+                Error_Transaction_Anchor_is_not_unique _pos anchor -> "Transaction anchor is not unique: " <> (#)anchor
+                Error_Transaction_Anchor_unknown _pos anchor -> "Transaction anchor unknown: " <> (#)anchor
+                Error_Transaction_Invalid_date year month dom ->
+                       "invalid date (year " <> (#)year <> ", month " <> (#)month <> ", day " <> (#)dom <> ")"
+                Error_Transaction_Invalid_time_of_day hour minute second ->
+                       "invalid time of day (hour " <> (#)hour <> ", minute " <> (#)minute <> ", second " <> (#)second <> ")"
+                Error_Transaction_The_following_transaction_is_not_equilibrated_because ->
+                       "the following transaction is not equilibrated, because:"
+                Error_Transaction_The_following_virtual_transaction_is_not_equilibrated_because ->
+                       "the following virtual transaction is not equilibrated, because:"
+                Error_Transaction_JCC_Unit_sums_up_to_the_non_null_amount unit amount ->
+                       " - " <>
+                       (if Text.null $ Unit.unit_text unit
+                       then "empty unit"
+                       else "unit " <> (#)unit) <>
+                       " sums up to the non-null amount: " <> (#)amount
+                Error_Transaction_Ledger_Unit_sums_up_to_the_non_null_amount unit amount ->
+                       " - " <>
+                       (if Text.null $ Unit.unit_text unit
+                       then "empty unit"
+                       else "unit " <> (#)unit) <>
+                       " sums up to the non-null amount: " <> (#)amount
+                Error_Transaction_Year_or_day_is_missing ->
+                       "year or day is missing"
+                Error_Unkown_command cmd -> "unkown command: " <> (#)cmd
+       translate FR t =
+               case t of
+                Error_Account_Anchor_is_not_unique _pos anchor -> "Ancre de Compte non-unique : " <> (#)anchor
+                Error_Account_Anchor_unknown _pos anchor -> "Ancre de Compte inconnue : " <> (#)anchor
+                Error_Failed_to_read_file path    -> "échec de la lecture du fichier : " <> (#)path
+                Error_Failed_to_include_file path -> "échec à l’inclusion du fichier : " <> (#)path
+                Error_No_input_file_given         ->
+                       W.vsep
+                        [ "aucun fichier d’entrée indiqué, veuillez utiliser :"
+                        , " - soit le paramètre -i FICHIER_DE_JOURNAL,"
+                        , " - soit la variable d’environnement LEDGER_FILE."
+                        ]
+                Error_One_command_is_required     -> "une COMMANDE est requise"
+                Error_Option_Balance_Format       -> "le paramètre --format s’attend à \"close\", \"open\" ou \"table\" comme argument"
+                Error_Option_Balance_Heritage     -> "le paramètre --heritage s’attend à \"yes\" ou \"no\" comme argument"
+                Error_Option_Balance_Redundant    -> "le paramètre --redundant s’attend à \"yes\" ou \"no\" comme argument"
+                Error_Option_Balance_Total        -> "le paramètre --total s’attend à \"yes\" ou \"no\" comme argument"
+                Error_Option_Color                -> "le paramètre --color s’attend à \"auto\" (défaut), \"yes\" ou \"no\" comme argument"
+                Error_Option_Equilibrium          -> "le paramètre --eq s’attend à un COMPTE"
+                Error_Option_Equilibrium_Credit   -> "le paramètre --eq-credit s’attend à un COMPTE"
+                Error_Option_Equilibrium_Debit    -> "le paramètre --eq-debit s’attend à un COMPTE"
+                Error_Option_Tags_Tree            -> "le paramètre --total s’attend à \"yes\" ou \"no\" comme argument"
+                Error_Option_Verbosity            -> "le paramètre --verbosity s’attend à \"error\", \"warn\", \"info\", or \"debug\" comme argument"
+                Error_Transaction_Anchor_is_not_unique _pos anchor -> "Ancre d’Écriture non-unique : " <> (#)anchor
+                Error_Transaction_Anchor_unknown _pos anchor -> "Ancre d’Écriture inconnue : " <> (#)anchor
+                Error_Transaction_Invalid_date year month dom ->
+                       "date incorrecte (année " <> (#)year <> ", mois " <> (#)month <> ", jour " <> (#)dom <> ")"
+                Error_Transaction_Invalid_time_of_day hour minute second ->
+                       "heure incorrecte (heure " <> (#)hour <> ", minute " <> (#)minute <> ", seconde " <> (#)second <> ")"
+                Error_Transaction_The_following_transaction_is_not_equilibrated_because ->
+                       "la transaction suivante n’est pas équilibrée, car :"
+                Error_Transaction_The_following_virtual_transaction_is_not_equilibrated_because ->
+                       "la transaction virtuelle suivante n’est pas équilibrée, car :"
+                Error_Transaction_JCC_Unit_sums_up_to_the_non_null_amount unit amount ->
+                       " - l’unité " <>
+                       (if Text.null $ Unit.unit_text unit
+                       then "vide"
+                       else (#)unit) <>
+                       " a le solde non-nul : " <> (#)amount
+                Error_Transaction_Ledger_Unit_sums_up_to_the_non_null_amount unit amount ->
+                       " - l’unité " <>
+                       (if Text.null $ Unit.unit_text unit
+                       then "vide"
+                       else (#)unit) <>
+                       " a le solde non-nul : " <> (#)amount
+                Error_Transaction_Year_or_day_is_missing ->
+                       "l’année ou le jour est manquant-e"
+                Error_Unkown_command cmd -> "commande inconnue : " <> (#)cmd
+
+-- * Type 'Error_Parsec'
+data Error_Parsec
+ =   Error_Parsec_Expect W.Doc
+ |   Error_Parsec_Message String
+ |   Error_Parsec_Or
+ |   Error_Parsec_Sysunexpect String
+ |   Error_Parsec_Sysunexpect_EOI
+ |   Error_Parsec_Unexpect W.Doc
+ |   Error_Parsec_Unknown
+instance ToDoc Lang Error_Parsec where
+       toDoc EN t =
+               case t of
+                Error_Parsec_Expect doc      -> "but expect : " <> (#)doc
+                Error_Parsec_Message doc     -> (#)doc
+                Error_Parsec_Or              -> "or"
+                Error_Parsec_Sysunexpect doc -> "is written : " <> (#)doc
+                Error_Parsec_Sysunexpect_EOI -> "end of file unexpected"
+                Error_Parsec_Unexpect doc    -> "found : " <> (#)doc
+                Error_Parsec_Unknown         -> "unkown"
+       toDoc FR t =
+               case t of
+                Error_Parsec_Expect doc      -> "mais s’attend à : " <> (#)doc
+                Error_Parsec_Message doc     -> (#)doc
+                Error_Parsec_Or              -> "ou"
+                Error_Parsec_Sysunexpect doc -> "est écrit : " <> (#)doc
+                Error_Parsec_Sysunexpect_EOI -> "fin de fichier inattendue"
+                Error_Parsec_Unexpect doc    -> "trouve : " <> (#)doc
+                Error_Parsec_Unknown         -> "inconnu"
+instance Translate Parsec.SourcePos W.Doc where
+       translate EN pos = do
                let line = Parsec.sourceLine   pos
                let col  = Parsec.sourceColumn pos
                case Parsec.sourceName pos of
                 ""   -> "(line " <> (#)line <> ", column " <> (#)col <> ")"
                 path -> "(line " <> (#)line <> ", column " <> (#)col <> ") in: " <> (#)path
-       toDoc FR pos = do
+       translate FR pos = do
                let line = Parsec.sourceLine   pos
                let col  = Parsec.sourceColumn pos
                case Parsec.sourceName pos of
                 ""   -> "(ligne " <> (#)line <> ", colonne " <> (#)col <> ")"
                 path -> "(ligne " <> (#)line <> ", colonne " <> (#)col <> ") dans : " <> (#)path
-instance ToDoc Lang e
- => ToDoc Lang [Lib.Parsec.Error e] where
-       toDoc lang errors =
+instance Translate e W.Doc
+ => Translate [Lib.Parsec.Error e] W.Doc where
+       translate lang errors =
                W.vsep $ do
                (flip map) errors $ (\error ->
                        case error of
                         Lib.Parsec.Error_At pos errs -> W.vsep $
-                               [ toDoc lang pos
-                               , toDoc lang errs
+                               [ translate lang pos
+                               , translate lang errs
                                ]
                         Lib.Parsec.Error_Parser err ->
                                W.vsep $
-                                [ toDoc lang (Parsec.errorPos err)
+                                [ translate lang (Parsec.errorPos err)
                                 , showErrorMessages
                                         (Parsec.Error.errorMessages err)
                                 ]
                         Lib.Parsec.Error_Custom pos err -> W.vsep $
-                               [ toDoc lang pos
-                               , toDoc lang err
+                               [ translate lang pos
+                               , translate lang err
                                ]
                 )
                where
                        showErrorMessages :: [Parsec.Error.Message] -> W.Doc
                        showErrorMessages msgs
-                               | null msgs = toDoc lang $ Message_unknown
+                               | null msgs = toDoc lang $ Error_Parsec_Unknown
                                | otherwise = W.vsep $ -- clean $
                                        [showSysUnExpect, showUnExpect, showExpect, showMessages]
                                where
@@ -138,12 +376,12 @@ instance ToDoc Lang e
                                        (unExpect,msgs2)    = span ((Parsec.Error.UnExpect    "") ==) msgs1
                                        (expect,messages)   = span ((Parsec.Error.Expect      "") ==) msgs2
                                        
-                                       showExpect   = showMany (Just (toDoc lang . Message_expect)) expect
-                                       showUnExpect = showMany (Just (toDoc lang . Message_unexpect)) unExpect
+                                       showExpect   = showMany (Just (toDoc lang . Error_Parsec_Expect)) expect
+                                       showUnExpect = showMany (Just (toDoc lang . Error_Parsec_Unexpect)) unExpect
                                        showSysUnExpect
                                         | not (null unExpect) || null sysUnExpect = W.empty
-                                        | null firstMsg = toDoc lang $ Message_sysunexpect_end_of_input
-                                        | otherwise     = toDoc lang $ Message_sysunexpect firstMsg
+                                        | null firstMsg = toDoc lang $ Error_Parsec_Sysunexpect_EOI
+                                        | otherwise     = toDoc lang $ Error_Parsec_Sysunexpect firstMsg
                                                where
                                                firstMsg = Parsec.Error.messageString (head sysUnExpect)
                                        
@@ -163,176 +401,238 @@ instance ToDoc Lang e
                                        commasOr []  = W.empty
                                        commasOr [m] = W.bold $ W.dullblack $ W.text $ TL.pack m
                                        commasOr ms  = commaSep (init ms)
-                                                      <> (W.space <> toDoc lang Message_or <> W.space)
+                                                      <> (W.space <> toDoc lang Error_Parsec_Or <> W.space)
                                                       <> (W.bold $ W.dullblack $ W.text $ TL.pack $ last ms)
                                        commaSep = W.intercalate (W.comma <> W.space)
                                                   (W.bold . W.dullblack . W.text . TL.pack)
                                                   . clean
                                        
                                        clean = Data.List.nub . filter (not . null)
-instance ToDoc Lang Filter.Read.Error where
-       toDoc lang@FR err =
-               case err of
-                Filter.Read.Error_Unknown -> "erreur"
-                Filter.Read.Error_Test_Date d -> toDoc lang d
-                Filter.Read.Error_Test_Date_Interval (l, h) ->
-                       "mauvais intervalle: (" <> toDoc lang l <> ", " <> toDoc lang h <> ")"
-       toDoc lang@EN err =
-               case err of
-                Filter.Read.Error_Unknown -> "error"
-                Filter.Read.Error_Test_Date d -> toDoc lang d
-                Filter.Read.Error_Test_Date_Interval (l, h) ->
-                       "wrong interval: (" <> toDoc lang l <> ", " <> toDoc lang h <> ")"
 
-data Message
- = Message_ERROR
- | Message_no_ledger_file_given
- | Message_failed_to_read_file
-       {message_path :: FilePath}
- | Message_failed_to_include_file
-       {message_path :: FilePath}
- | Message_the_following_transaction_is_not_equilibrated_because {}
- | Message_the_following_virtual_transaction_is_not_equilibrated_because {}
- | Message_unit_sums_up_to_the_non_null_amount
-       {message_Unit   :: Unit
-       ,message_Amount :: Amount}
- | Message_year_or_day_is_missing {}
- | Message_invalid_date
-       {message_Year  :: Integer
-       ,message_Month :: Int
-       ,message_Day   :: Int
-       }
- | Message_invalid_time_of_day
-       { message_Hour   :: Int
-       , message_Month  :: Int
-       , message_Second :: Integer
-       }
- | Message_unexpect {message_Doc :: W.Doc}
- | Message_sysunexpect {message_Msg :: String}
- | Message_expect {message_Doc :: W.Doc}
- | Message_message {message_Msg :: String}
- | Message_sysunexpect_end_of_input {}
- | Message_unknown {}
- | Message_or {}
- | Message_Balance {}
- | Message_Debit {}
- | Message_Credit {}
- | Message_Running_debit {}
- | Message_Running_credit {}
- | Message_Running_balance {}
- | Message_Account {}
- | Message_Date {}
- | Message_Description {}
-instance ToDoc Lang Message where
-       toDoc EN msg =
-               case msg of
-                Message_ERROR ->
-                       "ERROR"
-                Message_no_ledger_file_given ->
-                       "no ledger file given, please use:" <> W.line <>
-                       "- either -i FILE parameter" <> W.line <>
-                       "- or LEDGER_FILE environment variable."
-                Message_failed_to_read_file path ->
-                       "failed to read file: " <> (#)path
-                Message_failed_to_include_file path ->
-                       "failed to include file: " <> (#)path
-                Message_the_following_transaction_is_not_equilibrated_because ->
-                       "the following transaction is not equilibrated, because:"
-                Message_the_following_virtual_transaction_is_not_equilibrated_because ->
-                       "the following virtual transaction is not equilibrated, because:"
-                Message_unit_sums_up_to_the_non_null_amount unit amount ->
-                       " - unit " <> (#)unit <> " sums up to the non-null amount: " <> (#)amount
-                Message_year_or_day_is_missing ->
-                       "year or day is missing"
-                Message_invalid_date year month day ->
-                       "invalid date (year " <> (#)year <> ", month " <> (#)month <> ", day " <> (#)day <> ")"
-                Message_invalid_time_of_day hour minute second ->
-                       "invalid time of day (hour " <> (#)hour <> ", minute " <> (#)minute <> ", second " <> (#)second <> ")"
-                Message_unexpect doc ->
-                       "found : " <> (#)doc
-                Message_sysunexpect doc ->
-                       "is written : " <> (#)doc
-                Message_expect doc ->
-                       "but expect : " <> (#)doc
-                Message_message doc ->
-                       (#)doc
-                Message_sysunexpect_end_of_input ->
-                       "end of file unexpected"
-                Message_unknown ->
-                       "unkown"
-                Message_or ->
-                       "or"
-                Message_Balance ->
-                       "Balance"
-                Message_Debit ->
-                       "Debit"
-                Message_Credit ->
-                       "Credit"
-                Message_Running_debit ->
-                       "Running debit"
-                Message_Running_credit ->
-                       "Running credit"
-                Message_Running_balance ->
-                       "Running balance"
-                Message_Account ->
-                       "Account"
-                Message_Date ->
-                       "Date"
-                Message_Description ->
-                       "Description"
-       toDoc FR msg =
-               case msg of
-                Message_ERROR ->
-                       "ERREUR"
-                Message_no_ledger_file_given ->
-                       "aucun fichier indiqué, veuillez utiliser :" <> W.line <>
-                       " - soit le paramètre -i FICHIER," <> W.line <>
-                       " - soit la variable d’environnement LEDGER_FILE."
-                Message_failed_to_read_file path ->
-                       "échec de la lecture du fichier : " <> (#)path
-                Message_failed_to_include_file path ->
-                       "échec à l’inclusion du fichier : " <> (#)path
-                Message_the_following_transaction_is_not_equilibrated_because ->
-                       "la transaction suivante n’est pas équilibrée, car :"
-                Message_the_following_virtual_transaction_is_not_equilibrated_because ->
-                       "la transaction virtuelle suivante n’est pas équilibrée, car :"
-                Message_unit_sums_up_to_the_non_null_amount unit amount ->
-                       " - l’unité " <> (#)unit <> " a le solde non-nul : " <> (#)amount
-                Message_year_or_day_is_missing ->
-                       "l’année ou le jour est manquant-e"
-                Message_invalid_date year month day ->
-                       "date incorrecte (année " <> (#)year <> ", mois " <> (#)month <> ", jour " <> (#)day <> ")"
-                Message_invalid_time_of_day hour minute second ->
-                       "heure incorrecte (heure " <> (#)hour <> ", minute " <> (#)minute <> ", seconde " <> (#)second <> ")"
-                Message_unexpect doc ->
-                       "trouve : " <> (#)doc
-                Message_sysunexpect doc ->
-                       "est écrit : " <> (#)doc
-                Message_expect doc ->
-                       "mais s’attend à : " <> (#)doc
-                Message_message doc ->
-                       (#)doc
-                Message_sysunexpect_end_of_input ->
-                       "fin de fichier inattendue"
-                Message_unknown ->
-                       "inconnu"
-                Message_or ->
-                       "ou"
-                Message_Balance ->
-                       "Solde"
-                Message_Debit ->
-                       "Débit"
-                Message_Credit ->
-                       "Crédit"
-                Message_Running_debit ->
-                       "Débit cumulé"
-                Message_Running_credit ->
-                       "Crédit cumulé"
-                Message_Running_balance ->
-                       "Solde cumulé"
-                Message_Account ->
-                       "Compte"
-                Message_Date ->
-                       "Date"
-                Message_Description ->
-                       "Libellé"
+-- * Type 'Header'
+data Header
+ =   Header_Accounts
+ |   Header_Accounts_Depth
+ |   Header_Journals
+ |   Header_Tags
+ |   Header_Tags_Distinct
+ |   Header_Transactions
+ |   Header_Transactions_Date
+ |   Header_Units
+instance Translate Header [Text] where
+       translate EN t =
+               case t of
+                Header_Accounts          -> ["Accounts"]
+                Header_Accounts_Depth    -> ["Accounts","Depth"]
+                Header_Journals          -> ["Journals"]
+                Header_Tags              -> ["Tags"]
+                Header_Tags_Distinct     -> ["Tags", "Distinct"]
+                Header_Transactions      -> ["Transactions"]
+                Header_Transactions_Date -> ["Transactions", "Date"]
+                Header_Units             -> ["Unit"]
+       translate FR t =
+               case t of
+                Header_Accounts          -> ["Comptes"]
+                Header_Accounts_Depth    -> ["Comptes","Profondeur"]
+                Header_Journals          -> ["Journaux"]
+                Header_Tags              -> ["Tags"]
+                Header_Tags_Distinct     -> ["Tags", "Distincts"]
+                Header_Transactions      -> ["Écritures"]
+                Header_Transactions_Date -> ["Écritures", "Date"]
+                Header_Units             -> ["Unités"]
+
+-- * Type 'Help'
+data Help
+ =   Help_Command_Balance
+ |   Help_Command_General_Ledger
+ |   Help_Command_Journal
+ |   Help_Command_Journals
+ |   Help_Command_Stats
+ |   Help_Command_Tags
+ |   Help_Option_Balance_Format
+ |   Help_Option_Balance_Heritage
+ |   Help_Option_Balance_Redundant
+ |   Help_Option_Balance_Total
+ |   Help_Option_Color
+ |   Help_Option_Equilibrium
+ |   Help_Option_Equilibrium_Credit
+ |   Help_Option_Equilibrium_Debit
+ |   Help_Option_Filter_Balance
+ |   Help_Option_Filter_General_Ledger
+ |   Help_Option_Filter_Posting
+ |   Help_Option_Filter_Tag
+ |   Help_Option_Filter_Transaction
+ |   Help_Option_Help
+ |   Help_Option_Input
+ |   Help_Option_Lang
+ |   Help_Option_Output
+ |   Help_Option_Overwrite
+ |   Help_Option_Tags_Tree
+ |   Help_Option_Verbosity
+ |   Help_Synopsis
+instance Translate Help String where
+       translate EN t =
+               case t of
+                Help_Command_Balance              -> "List final DEBITs, CREDITs and BALANCEs of ACCOUNTs"
+                Help_Command_General_Ledger       -> "List DEBITs, CREDITs and BALANCEs of ACCOUNTs after each TRANSACTION"
+                Help_Command_Journal              -> "List TRANSACTIONs"
+                Help_Command_Journals             -> "List JOURNAL FILEs"
+                Help_Command_Stats                -> "Show some statistics"
+                Help_Command_Tags                 -> "List TAGs"
+                Help_Option_Balance_Format        -> "Select BALANCE output format"
+                Help_Option_Balance_Heritage      -> "Propagate AMOUNTs to ascending ACCOUNTs"
+                Help_Option_Balance_Redundant     -> "Also show ACCOUNTs with null AMOUNT or the same AMOUNTs than its descending ACCOUNT"
+                Help_Option_Balance_Total         -> "Show transversal DEBIT, CREDIT, and BALANCE by UNIT"
+                Help_Option_Color                 -> "Colorize output"
+                Help_Option_Equilibrium           -> "Specify the ACCOUNT equilibrating an opening or closing BALANCE"
+                Help_Option_Equilibrium_Credit    -> "Like --eq but only when the AMOUNT is a CREDIT"
+                Help_Option_Equilibrium_Debit     -> "Like --eq but only when the AMOUNT is a DEBIT"
+                Help_Option_Filter_Balance        -> "Apply given FILTER_OF_BALANCE, multiple uses are joined with a logical AND"
+                Help_Option_Filter_General_Ledger -> "Apply given FILTER_OF_GENERAL_LEDGER, multiple uses are joined with a logical AND"
+                Help_Option_Filter_Posting        -> "Apply given FILTER_OF_POSTING, multiple uses are joined with a logical AND"
+                Help_Option_Filter_Tag            -> "Apply given FILTER_OF_TAG, multiple uses are joined with a logical AND"
+                Help_Option_Filter_Transaction    -> "Apply given FILTER_OF_TRANSACTION, multiple uses are joined with a logical AND"
+                Help_Option_Help                  -> "Show this help"
+                Help_Option_Input                 -> "Read a JOURNAL from given FILE, multiple uses merge the data"
+                Help_Option_Lang                  -> "RFC1766 / ISO 639-1 language code (fr, en-GB, etc.)"
+                Help_Option_Output                -> "Append output data to given FILE, multiple uses output to multiple FILEs"
+                Help_Option_Overwrite             -> "Overwrite given FILE with output data, multiple uses overwrite to multiple FILEs"
+                Help_Option_Tags_Tree             -> "Show TAGs as a tree"
+                Help_Option_Verbosity             -> "Set verbosity level, or increment it when used multiple times"
+                Help_Synopsis                     -> "[OPTIONS] COMMAND [COMMAND_OPTIONS]"
+       translate FR t =
+               case t of
+                Help_Command_Balance              -> "Liste les DÉBITs, CRÉDITs et SOLDEs finaux des COMPTEs"
+                Help_Command_General_Ledger       -> "Liste les DÉBITs, CRÉDITs et SOLDEs des COMPTEs après chaque ÉCRITURE"
+                Help_Command_Journal              -> "Liste les ÉCRITUREs"
+                Help_Command_Journals             -> "Liste les FICHIERs des JOURNAUX"
+                Help_Command_Stats                -> "Affiche quelques statistiques"
+                Help_Command_Tags                 -> "Liste les TAGs"
+                Help_Option_Balance_Format        -> "Sélectionne le format de BALANCE en sortie"
+                Help_Option_Balance_Heritage      -> "Propage les MONTANTs aux COMPTEs ascendants"
+                Help_Option_Balance_Redundant     -> "Affiche également les COMPTEs dont le MONTANT est nul ou qui ont les mêmes MONTANTs que leur COMPTE descendant"
+                Help_Option_Balance_Total         -> "Affiche les SOLDEs transversaux par UNITÉ"
+                Help_Option_Color                 -> "Colore la sortie"
+                Help_Option_Equilibrium           -> "Indique le COMPTE d’équilibre pour une BALANCE d’ouverture ou de fermeture"
+                Help_Option_Equilibrium_Credit    -> "Comme --eq mais seulement lorsque le MONTANT est un CRÉDIT"
+                Help_Option_Equilibrium_Debit     -> "Comme --eq mais seulement lorsque le MONTANT est un DÉBIT"
+                Help_Option_Filter_Balance        -> "Applique le FILTRE_DE_BALANCE donné, un usage multiple agit comme un ET logique"
+                Help_Option_Filter_General_Ledger -> "Applique le FILTRE_DE_GRAND_LIVRE donné, un usage multiple agit comme un ET logique"
+                Help_Option_Filter_Posting        -> "Applique le FILTRE_DE_MOUVEMENT donné, un usage multiple agit comme un ET logique"
+                Help_Option_Filter_Tag            -> "Applique le FILTRE_DE_TAG donné, un usage multiple agit comme un ET logique"
+                Help_Option_Filter_Transaction    -> "Applique le FILTRE_D’ÉCRITURE donné, un usage multiple agit comme un ET logique"
+                Help_Option_Help                  -> "Affiche cette aide"
+                Help_Option_Input                 -> "Lit un JOURNAL dans le FICHIER donné, un usage multiple fusionne les données"
+                Help_Option_Lang                  -> "Code de langue RFC1766 / ISO 639-1 language code (fr, en-GB, etc.)"
+                Help_Option_Output                -> "Ajoute la sortie au FICHIER donné, un usage multiple écrit dans plusieurs FICHIERs"
+                Help_Option_Overwrite             -> "Écrase le FICHIER donné avec la sortie, un usage multiple écrase plusieurs FICHIERs"
+                Help_Option_Tags_Tree             -> "Affiche les TAGs en arborescence"
+                Help_Option_Verbosity             -> "Indique le niveau de verbosité, ou l’incrémente lorsque utilisé plusieurs fois"
+                Help_Synopsis                     -> "[PARAMÈTRES] COMMANDE [PARAMÈTRES_DE_COMMANDE]"
+
+-- * Type 'Section'
+data Section
+ =   Section_Commands
+ |   Section_Description
+ |   Section_Syntax
+ |   Section_Options
+ deriving (Eq, Show)
+instance Translate Section String where
+       translate EN t =
+               case t of
+                Section_Commands    -> "COMMANDS  (use COMMAND --help for help on COMMAND)"
+                Section_Description -> "DESCRIPTION"
+                Section_Syntax      -> "SYNTAX"
+                Section_Options     -> "OPTIONS"
+       translate FR t =
+               case t of
+                Section_Commands    -> "COMMANDES  (utilisez COMMANDE --help pour une aide sur COMMANDE)"
+                Section_Description -> "DESCRIPTION"
+                Section_Syntax      -> "SYNTAXE"
+                Section_Options     -> "PARAMÈTRES"
+
+-- * Type 'Title'
+data Title
+ =   Title_Account
+ |   Title_Balance
+ |   Title_Credit
+ |   Title_Date
+ |   Title_Debit
+ |   Title_Description
+ |   Title_Running_balance
+ |   Title_Running_credit
+ |   Title_Running_debit
+instance Translate Title Text where
+       translate EN t =
+               case t of
+                Title_Account         -> "Account"
+                Title_Balance         -> "Balance"
+                Title_Credit          -> "Credit"
+                Title_Date            -> "Date"
+                Title_Debit           -> "Debit"
+                Title_Description     -> "Wording"
+                Title_Running_balance -> "Running balance"
+                Title_Running_credit  -> "Running credit"
+                Title_Running_debit   -> "Running debit"
+       translate FR t =
+               case t of
+                Title_Account         -> "Compte"
+                Title_Balance         -> "Solde"
+                Title_Credit          -> "Crédit"
+                Title_Date            -> "Date"
+                Title_Debit           -> "Débit"
+                Title_Description     -> "Libellé"
+                Title_Running_balance -> "Solde cumulé"
+                Title_Running_credit  -> "Crédit cumulé"
+                Title_Running_debit   -> "Débit cumulé"
+
+-- * Type 'Type'
+data Type
+ =   Type_Account
+ |   Type_File
+ |   Type_File_Journal
+ |   Type_Filter_Balance
+ |   Type_Filter_General_Ledger
+ |   Type_Filter_Posting
+ |   Type_Filter_Tag
+ |   Type_Filter_Transaction
+ |   Type_Option
+ deriving (Eq, Show)
+instance Translate Type String where
+       translate EN t =
+               case t of
+                Type_Account               -> "ACCOUNT"
+                Type_File                  -> "FILE"
+                Type_File_Journal          -> "FILE_OF_JOURNAL"
+                Type_Filter_Balance        -> "FILTER_OF_BALANCE"
+                Type_Filter_General_Ledger -> "FILTER_OF_GENERAL_LEDGER"
+                Type_Filter_Posting        -> "FILTER_OF_POSTING"
+                Type_Filter_Tag            -> "FILTER_OF_TAG"
+                Type_Filter_Transaction    -> "FILTER_OF_TRANSACTION"
+                Type_Option                -> "OPTION"
+       translate FR t =
+               case t of
+                Type_Account               -> "COMPTE"
+                Type_File                  -> "FICHIER"
+                Type_File_Journal          -> "FICHIER_DE_JOURNAL"
+                Type_Filter_Balance        -> "FILTRE_DE_BALANCE"
+                Type_Filter_General_Ledger -> "FILTRE_DE_GRAND_LIVRE"
+                Type_Filter_Posting        -> "FILTRE_DE_MOUVEMENT"
+                Type_Filter_Tag            -> "FILTRE_DE_TAG"
+                Type_Filter_Transaction    -> "FILTRE_D’ÉCRITURE"
+                Type_Option                -> "PARAMÈTRE"
+
+-- * Type 'Write'
+data Write
+ =   Write_Debug
+ |   Write_Error
+instance Translate Write W.Doc where
+       translate EN t =
+               case t of
+                Write_Error -> "ERROR"
+                Write_Debug -> "DEBUG"
+       translate FR t =
+               case t of
+                Write_Error -> "ERREUR"
+                Write_Debug -> "DÉBUG"
+