diff --git a/hledger-lib/Hledger/Reports/MultiBalanceReport.hs b/hledger-lib/Hledger/Reports/MultiBalanceReport.hs index 54be162a292..3dd35db4d27 100644 --- a/hledger-lib/Hledger/Reports/MultiBalanceReport.hs +++ b/hledger-lib/Hledger/Reports/MultiBalanceReport.hs @@ -38,6 +38,7 @@ where import Control.Applicative (liftA2) #endif import Control.Monad (guard) +import Data.Default (Default, def) import Data.Foldable (toList) import Data.HashSet qualified as HS import Data.List (sortOn) @@ -115,15 +116,17 @@ multiBalanceReportWith rspec' j priceoracle = report -- | Generate a compound balance report from a list of CBCSubreportSpec. This -- shares postings between the subreports. -compoundBalanceReport :: ReportSpec -> Journal -> [CBCSubreportSpec a] - -> CompoundPeriodicReport a MixedAmount +compoundBalanceReport :: (Default msg) + => ReportSpec -> Journal -> [CBCSubreportSpec msg a] + -> CompoundPeriodicReport msg a MixedAmount compoundBalanceReport rspec j = compoundBalanceReportWith rspec j (journalPriceOracle infer j) where infer = infer_prices_ $ _rsReportOpts rspec -- | A helper for compoundBalanceReport, similar to multiBalanceReportWith. -compoundBalanceReportWith :: ReportSpec -> Journal -> PriceOracle - -> [CBCSubreportSpec a] - -> CompoundPeriodicReport a MixedAmount +compoundBalanceReportWith :: (Default msg) + => ReportSpec -> Journal -> PriceOracle + -> [CBCSubreportSpec msg a] + -> CompoundPeriodicReport msg a MixedAmount compoundBalanceReportWith rspec' j priceoracle subreportspecs = cbr where -- Queries, report/column dates. @@ -162,7 +165,7 @@ compoundBalanceReportWith rspec' j priceoracle subreportspecs = cbr subreportTotal (_, sr, increasestotal) = (if increasestotal then id else fmap maNegate) $ prTotals sr - cbr = CompoundPeriodicReport "" (maybeDayPartitionToDateSpans colspans) subreports overalltotals + cbr = CompoundPeriodicReport def (maybeDayPartitionToDateSpans colspans) subreports overalltotals -- | Remove any date queries and insert queries from the report span. diff --git a/hledger-lib/Hledger/Reports/ReportOptions.hs b/hledger-lib/Hledger/Reports/ReportOptions.hs index 7945d6e7b86..beaee8c0663 100644 --- a/hledger-lib/Hledger/Reports/ReportOptions.hs +++ b/hledger-lib/Hledger/Reports/ReportOptions.hs @@ -85,6 +85,7 @@ import Data.List (partition) import Data.List.Extra (find, isPrefixOf, nubSort, stripPrefix) import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe) import Data.Text qualified as T +import Data.Gettext (Catalog) import Data.Time.Calendar (Day, addDays) import Data.Default (Default(..)) import Safe (lastDef, lastMay, maximumMay, readMay) @@ -195,6 +196,7 @@ data ReportOpts = ReportOpts { -- TERM and existence of NO_COLOR environment variables. ,transpose_ :: Bool ,layout_ :: Layout + ,catalog_ :: Maybe Catalog ,period_titles_ :: PeriodTitles -- | Explicit --title value if given (possibly empty to -- suppress); otherwise Nothing, in which case each report falls @@ -247,6 +249,7 @@ defreportopts = ReportOpts , color_ = False , transpose_ = False , layout_ = LayoutWide Nothing + , catalog_ = Nothing , period_titles_ = PTCompact , title_ = Nothing , subreport_titles_ = Nothing diff --git a/hledger-lib/Hledger/Reports/ReportTypes.hs b/hledger-lib/Hledger/Reports/ReportTypes.hs index 3ce03ab3517..8d896713872 100644 --- a/hledger-lib/Hledger/Reports/ReportTypes.hs +++ b/hledger-lib/Hledger/Reports/ReportTypes.hs @@ -39,7 +39,6 @@ import Data.Aeson (ToJSON(..)) import Data.Bifunctor (Bifunctor(..)) import Data.Decimal (Decimal) import Data.Maybe (mapMaybe) -import Data.Text (Text) import GHC.Generics (Generic) import Hledger.Data @@ -181,27 +180,27 @@ prrMapMaybeName f row = case f $ prrName row of -- -- It is used in compound balance report commands like balancesheet, -- cashflow and incomestatement. -data CompoundPeriodicReport a b = CompoundPeriodicReport - { cbrTitle :: Text +data CompoundPeriodicReport msg a b = CompoundPeriodicReport + { cbrTitle :: msg , cbrDates :: [DateSpan] - , cbrSubreports :: [(Text, PeriodicReport a b, Bool)] + , cbrSubreports :: [(msg, PeriodicReport a b, Bool)] , cbrTotals :: PeriodicReportRow () b } deriving (Show, Functor, Generic, ToJSON) -instance HasAmounts b => HasAmounts (CompoundPeriodicReport a b) where +instance HasAmounts b => HasAmounts (CompoundPeriodicReport msg a b) where styleAmounts styles cpr@CompoundPeriodicReport{cbrSubreports, cbrTotals} = cpr{ cbrSubreports = styleAmounts styles cbrSubreports , cbrTotals = styleAmounts styles cbrTotals } -instance HasAmounts b => HasAmounts (Text, PeriodicReport a b, Bool) where +instance HasAmounts b => HasAmounts (msg, PeriodicReport a b, Bool) where styleAmounts styles (a,b,c) = (a,styleAmounts styles b,c) -- | Description of one subreport within a compound balance report. -- Part of a "CompoundBalanceCommandSpec", but also used in hledger-lib. -data CBCSubreportSpec a = CBCSubreportSpec - { cbcsubreporttitle :: Text -- ^ The title to use for the subreport +data CBCSubreportSpec msg a = CBCSubreportSpec + { cbcsubreporttitle :: msg -- ^ The title to use for the subreport , cbcsubreportquery :: Query -- ^ The Query to use for the subreport , cbcsubreportoptions :: ReportOpts -> ReportOpts -- ^ A function to transform the ReportOpts used to produce the subreport , cbcsubreporttransform :: PeriodicReport DisplayName MixedAmount -> PeriodicReport a MixedAmount -- ^ A function to transform the result of the subreport diff --git a/hledger-lib/hledger-lib.cabal b/hledger-lib/hledger-lib.cabal index c56dbc6bd14..a5f52a67643 100644 --- a/hledger-lib/hledger-lib.cabal +++ b/hledger-lib/hledger-lib.cabal @@ -153,6 +153,7 @@ library , file-embed >=0.0.10 , filepath , hashtables >=1.2.3.1 && <1.3 || >=1.4.0 + , haskell-gettext >=0.1.2.0 && <0.2 , megaparsec >=7.0.0 && <9.8 || >=9.8.1 && <9.9 , microlens >=0.4 , microlens-th >=0.4 @@ -215,6 +216,7 @@ test-suite doctest , file-embed >=0.0.10 , filepath , hashtables >=1.2.3.1 && <1.3 || >=1.4.0 + , haskell-gettext >=0.1.2.0 && <0.2 , megaparsec >=7.0.0 && <9.8 || >=9.8.1 && <9.9 , microlens >=0.4 , microlens-th >=0.4 @@ -276,6 +278,7 @@ test-suite unittest , file-embed >=0.0.10 , filepath , hashtables >=1.2.3.1 && <1.3 || >=1.4.0 + , haskell-gettext >=0.1.2.0 && <0.2 , hledger-lib , megaparsec >=7.0.0 && <9.8 || >=9.8.1 && <9.9 , microlens >=0.4 diff --git a/hledger-lib/package.yaml b/hledger-lib/package.yaml index b6df7d0ec88..4048d72ee5b 100644 --- a/hledger-lib/package.yaml +++ b/hledger-lib/package.yaml @@ -69,6 +69,7 @@ dependencies: - file-embed >=0.0.10 - filepath - hashtables >=1.2.3.1 && <1.3 || >=1.4.0 +- haskell-gettext >=0.1.2.0 && <0.2 - megaparsec >=7.0.0 && <9.8 || >=9.8.1 && <9.9 # avoid https://github.com/mrkkrp/megaparsec/issues/572 - microlens >=0.4 - microlens-th >=0.4 diff --git a/hledger/Hledger/Cli/CliOptions.hs b/hledger/Hledger/Cli/CliOptions.hs index 9a91f8c42ba..12850ff7b98 100644 --- a/hledger/Hledger/Cli/CliOptions.hs +++ b/hledger/Hledger/Cli/CliOptions.hs @@ -283,6 +283,7 @@ helpflags = [ ,flagNone ["man"] (setboolopt "man") "show this command's manual with man" ,flagNone ["webman"] (setboolopt "webman") "show this command's manual on the web" ,flagNone ["examples"] (setboolopt "examples") "show examples for this command" + ,flagReq ["catalog"] (\s opts -> Right $ setopt "catalog" s opts) "CATALOGFILE" "File containing custom translations." ,flagNone ["version"] (setboolopt "version") "show version information" -- flagOpt would be more correct for --debug, showing --debug[=LVL] rather than --debug=[LVL] in help. -- But flagReq plus special handling in Cli.hs makes the = optional, removing a source of confusion. @@ -597,6 +598,7 @@ data CliOpts = CliOpts { ,reportspec_ :: ReportSpec ,output_file_ :: Maybe FilePath ,output_format_ :: Maybe String + ,catalog_file_ :: Maybe FilePath ,pageropt_ :: Maybe Bool -- ^ --pager ,coloropt_ :: Maybe YNA -- ^ --color. Controls use of ANSI color and ANSI styles. ,debug_ :: Int -- ^ debug level, set by @--debug[=N]@. See also 'Hledger.Utils.debugLevel'. @@ -619,6 +621,7 @@ defcliopts = CliOpts , reportspec_ = def , output_file_ = Nothing , output_format_ = Nothing + , catalog_file_ = Nothing , pageropt_ = Nothing , coloropt_ = Nothing , debug_ = 0 @@ -675,6 +678,7 @@ rawOptsToCliOpts rawopts = do ,reportspec_ = rspec ,output_file_ = maybestringopt "output-file" rawopts ,output_format_ = maybestringopt "output-format" rawopts + ,catalog_file_ = maybestringopt "catalog" rawopts ,pageropt_ = maybeynopt "pager" rawopts ,coloropt_ = maybeynaopt "color" rawopts ,debug_ = posintopt "debug" rawopts diff --git a/hledger/Hledger/Cli/Commands/Balance.hs b/hledger/Hledger/Cli/Commands/Balance.hs index 9f5a2e23636..fca9a09e381 100644 --- a/hledger/Hledger/Cli/Commands/Balance.hs +++ b/hledger/Hledger/Cli/Commands/Balance.hs @@ -301,6 +301,7 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Builder qualified as TB +import Data.Gettext (loadCatalog) import Data.Time (addDays, fromGregorian) import System.Console.CmdArgs.Explicit as C (flagNone, flagReq, flagOpt) import Safe (headMay, maximumMay) @@ -321,6 +322,7 @@ import Hledger.Write.Spreadsheet (rawTableContent, headerCell, addHeaderBorders, addRowSpanHeader, addRowSpanHeaderNE, cellFromMixedAmount, cellsFromMixedAmount, cellFromAmount) import Hledger.Write.Spreadsheet qualified as Ods +import Hledger.Cli.Message qualified as Msg -- | Command line options for this command. @@ -399,7 +401,18 @@ balancemode = hledgerCommandMode -- | The balance command, prints a balance report. balance :: CliOpts -> Journal -> IO () -balance opts@CliOpts{reportspec_=rspec} j = case balancecalc_ ropts of +balance opts@CliOpts{reportspec_=rspec} j = do + let ropts0 = _rsReportOpts rspec + maybeCat <- traverse loadCatalog $ catalog_file_ opts + let ropts = + ropts0 { + -- tidy csv is defined externally and must not include totals or averages + no_total_ = no_total_ ropts0 || layout_ ropts0 == LayoutTidy, + catalog_ = maybeCat + } + -- Tidy csv/tsv should be consistent between single period and multiperiod reports. + let multiperiod = interval_ ropts /= NoInterval || (layout_ ropts == LayoutTidy && delimited) + case balancecalc_ ropts of CalcBudget -> do -- single or multi period budget report let rspan = fst $ reportSpan j rspec budgetreport = styleAmounts styles $ budgetReport rspec (balancingopts_ $ inputopts_ opts) rspan j @@ -440,14 +453,6 @@ balance opts@CliOpts{reportspec_=rspec} j = case balancecalc_ ropts of writeOutputLazyText opts $ render report where styles = journalCommodityStylesWith HardRounding j - ropts = - let ropts0 = _rsReportOpts rspec in - ropts0 { - -- tidy csv is defined externally and must not include totals or averages - no_total_ = no_total_ ropts0 || layout_ ropts0 == LayoutTidy - } - -- Tidy csv/tsv should be consistent between single period and multiperiod reports. - multiperiod = interval_ ropts /= NoInterval || (layout_ ropts == LayoutTidy && delimited) delimited = fmt == "csv" || fmt == "tsv" fmt = outputFormatFromOpts opts @@ -491,10 +496,10 @@ budgetAverageClass rc = -- What to show as heading for the totals row in balance reports ? -- Currently nothing in terminal, Total: in HTML, FODS and xSV output. -totalRowHeadingText = "" -totalRowHeadingSpreadsheet = "Total:" -totalRowHeadingBudgetText = "" -totalRowHeadingBudgetCsv = "Total:" +totalRowHeadingText = Msg.None +totalRowHeadingSpreadsheet = Msg.Total +totalRowHeadingBudgetText = Msg.None +totalRowHeadingBudgetCsv = Msg.Total -- Single-column balance reports @@ -719,16 +724,17 @@ balanceReportAsSpreadsheetParts fmt opts (items, total) = if no_total_ opts then [] else addTotalBorders $ - rows Total (totalRowHeadingSpreadsheet, totalRowHeadingSpreadsheet, 0, total)) + rows Total (msg totalRowHeadingSpreadsheet, msg totalRowHeadingSpreadsheet, 0, total)) where cell = Ods.defaultCell hCell cls label = (headerCell label) {Ods.cellClass = Ods.Class cls} + msg = Msg.getText (catalog_ opts) headers = addHeaderBorders $ - hCell "account" "account" :| case layout_ opts of + hCell "account" (msg Msg.Account) :| case layout_ opts of LayoutBareWide -> map (hCell "amount") allCommodities - LayoutBare -> [headerCell "commodity", hCell "amount" "balance"] - _ -> [hCell "amount" "balance"] + LayoutBare -> [headerCell $ msg Msg.Commodity, hCell "amount" $ msg Msg.Balance] + _ -> [hCell "amount" $ msg Msg.Balance] allCommodities = S.toAscList $ foldMap (\(_,_,_,ma) -> maCommodities ma) items rows :: @@ -813,9 +819,10 @@ multiBalanceReportAsSpreadsheetParts fmt opts@ReportOpts{..} concatMap (Ods.horizontalSpan allCommodities) dateHeaders, headers] _ -> [headers] + msg = Msg.getText catalog_ headers = addHeaderBorders $ - hCell accountClass "account" : + hCell accountClass (msg Msg.Account) : case layout_ of LayoutTidy -> map headerCell tidyColumnLabels LayoutBareWide -> dateHeaders >> map headerCell allCommodities @@ -826,8 +833,8 @@ multiBalanceReportAsSpreadsheetParts fmt opts@ReportOpts{..} amountHeader c = c{Ods.cellClass = amountClass Value} dateHeaders = (if not summary_only_ then map (amountHeader . headerDateSpanCell period_titles_ balance_base_url_ querystring_) colspans else [] )++ - [hCell (rowTotalClass Value) "total" | multiBalanceHasTotalsColumn opts] ++ - [hCell (rowAverageClass Value) "average" | average_] + [hCell (rowTotalClass Value) (msg Msg.Total) | multiBalanceHasTotalsColumn opts] ++ + [hCell (rowAverageClass Value) (msg Msg.Average) | average_] fullRowAsTexts row = addRowSpanHeader anchorCell $ rowAsText Value (dateSpanCell period_titles_ balance_base_url_ querystring_ acctName) row @@ -838,7 +845,7 @@ multiBalanceReportAsSpreadsheetParts fmt opts@ReportOpts{..} totalrows = if no_total_ then [] - else addRowSpanHeader (accountCell totalRowHeadingSpreadsheet) $ + else addRowSpanHeader (accountCell $ msg totalRowHeadingSpreadsheet) $ rowAsText Total (simpleDateSpanCell period_titles_) tr rowAsText rc dsCell = map (map (fmap wbToText)) . @@ -957,8 +964,9 @@ multiBalanceReportAsPartTable (Group multiColumnTableInterColumnBorder $ map Header colheadings) (concat rows) where + msg = Msg.getText (catalog_ opts) colheadings = - ["Commodity" | layout_ opts == LayoutBare] + [msg Msg.Commodity | layout_ opts == LayoutBare] ++ case layout_ opts of LayoutBareWide -> @@ -968,8 +976,8 @@ multiBalanceReportAsPartTable spanNames = (guard (not summary_only_) >> map (reportPeriodName (period_titles_ opts) balanceaccum_ spans) spans) - ++ [" Total" | multiBalanceHasTotalsColumn opts] - ++ ["Average" | average_] + ++ [msg Msg.RightTotal | multiBalanceHasTotalsColumn opts] + ++ [msg Msg.RightAverage | average_] (accts, rows) = unzip $ fmap fullRowAsTexts items' where isLeaf rs row = not $ any (\r -> T.isPrefixOf (displayFull (prrName row) <> ":") (displayFull (prrName r))) rs @@ -984,7 +992,7 @@ multiBalanceReportAsPartTable | no_total_ opts = id | otherwise = let totalrows = multiBalanceRowAsText opts allCommodities tr - rowhdrs = Group NoLine $ map Header $ totalRowHeadingText : replicate (length totalrows - 1) "" + rowhdrs = Group NoLine $ map Header $ msg totalRowHeadingText : replicate (length totalrows - 1) "" colhdrs = Header [] -- unused, concatTables will discard in (flip (concatTables SingleLine) $ Table rowhdrs colhdrs totalrows) maybetranspose | transpose_ opts = \(Table rh ch vals) -> Table ch rh (transpose vals) @@ -1141,15 +1149,17 @@ budgetReportAsTable ropts@ReportOpts{..} (PeriodicReport spans items totrow) = | no_total_ = id | otherwise = let - rowhdrs = Group NoLine $ map Header $ totalRowHeadingBudgetText : replicate (length totalrows - 1) "" + rowhdrs = Group NoLine $ map Header $ msg totalRowHeadingBudgetText : replicate (length totalrows - 1) "" colhdrs = Header [] -- ignored by concatTables in (flip (concatTables SingleLine) $ Table rowhdrs colhdrs totalrows) -- XXX ? - colheadings = ["Commodity" | layout_ == LayoutBare] + msg = Msg.getText catalog_ + + colheadings = [msg Msg.Commodity | layout_ == LayoutBare] ++ (if not summary_only_ then map (reportPeriodName period_titles_ balanceaccum_ spans) spans else []) - ++ [" Total" | row_total_] - ++ ["Average" | average_] + ++ [msg Msg.RightTotal | row_total_] + ++ [msg Msg.RightAverage | average_] (accts, rows, totalrows) = (accts' @@ -1368,10 +1378,11 @@ budgetReportAsSpreadsheet -- totals row ++ addTotalBorders - (concat [ rowAsTexts Total (cell totalRowHeadingBudgetCsv) totrow | not no_total_ ]) + (concat [ rowAsTexts Total (cell $ msg totalRowHeadingBudgetCsv) totrow | not no_total_ ]) ) where + msg = Msg.getText catalog_ cell = Ods.defaultCell accountCell row = let name = prrFullName row in @@ -1389,13 +1400,13 @@ budgetReportAsSpreadsheet leadingHeaders ++ (dateHeaders >> allCommodities)] _ -> [addHeaderBorders $ map headerCell $ leadingHeaders ++ dateHeaders] leadingHeaders = - "Account" : ["Commodity" | layout_ == LayoutBare ] + msg Msg.Account : [msg Msg.Commodity | layout_ == LayoutBare ] dateHeaders = (if not summary_only_ - then concatMap (\spn -> [renderPeriodHeading period_titles_ spn, "budget"]) colspans + then concatMap (\spn -> [renderPeriodHeading period_titles_ spn, msg Msg.Budget]) colspans else []) - ++ concat [["Total" ,"budget"] | row_total_] - ++ concat [["Average","budget"] | average_] + ++ concat [[msg Msg.Total , msg Msg.Budget] | row_total_] + ++ concat [[msg Msg.Average, msg Msg.Budget] | average_] allCommodities = S.toAscList $ foldMap (foldMap maCommodities . concatMap (\(change,goal) -> maybeToList change ++ maybeToList goal) . prrAmounts) items diff --git a/hledger/Hledger/Cli/Commands/Balancesheet.hs b/hledger/Hledger/Cli/Commands/Balancesheet.hs index c2bca00685f..52bcc4d6e64 100644 --- a/hledger/Hledger/Cli/Commands/Balancesheet.hs +++ b/hledger/Hledger/Cli/Commands/Balancesheet.hs @@ -16,21 +16,22 @@ import System.Console.CmdArgs.Explicit import Hledger import Hledger.Cli.CliOptions +import Hledger.Cli.Message qualified as Msg import Hledger.Cli.CompoundBalanceCommand balancesheetSpec = CompoundBalanceCommandSpec { cbcdoc = $(embedFileRelative "Hledger/Cli/Commands/Balancesheet.txt"), - cbctitle = "Balance Sheet", + cbctitle = Msg.Noun Msg.BalanceSheet, cbcqueries = [ CBCSubreportSpec{ - cbcsubreporttitle="Assets" + cbcsubreporttitle=Msg.Assets ,cbcsubreportquery=Type [Asset] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_=Just NormallyPositive}) ,cbcsubreporttransform=id ,cbcsubreportincreasestotal=True } ,CBCSubreportSpec{ - cbcsubreporttitle="Liabilities" + cbcsubreporttitle=Msg.Liabilities ,cbcsubreportquery=Type [Liability] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_=Just NormallyNegative}) ,cbcsubreporttransform=fmap maNegate diff --git a/hledger/Hledger/Cli/Commands/Balancesheetequity.hs b/hledger/Hledger/Cli/Commands/Balancesheetequity.hs index 00a66430712..0dcc34970f4 100644 --- a/hledger/Hledger/Cli/Commands/Balancesheetequity.hs +++ b/hledger/Hledger/Cli/Commands/Balancesheetequity.hs @@ -17,28 +17,29 @@ import System.Console.CmdArgs.Explicit import Hledger import Hledger.Cli.CliOptions +import Hledger.Cli.Message qualified as Msg import Hledger.Cli.CompoundBalanceCommand balancesheetequitySpec = CompoundBalanceCommandSpec { cbcdoc = $(embedFileRelative "Hledger/Cli/Commands/Balancesheetequity.txt"), - cbctitle = "Balance Sheet With Equity", + cbctitle = Msg.Noun Msg.BalanceSheetWithEquity, cbcqueries = [ CBCSubreportSpec{ - cbcsubreporttitle="Assets" + cbcsubreporttitle=Msg.Assets ,cbcsubreportquery=Type [Asset] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_=Just NormallyPositive}) ,cbcsubreporttransform=id ,cbcsubreportincreasestotal=True } ,CBCSubreportSpec{ - cbcsubreporttitle="Liabilities" + cbcsubreporttitle=Msg.Liabilities ,cbcsubreportquery=Type [Liability] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_=Just NormallyNegative}) ,cbcsubreporttransform=fmap maNegate ,cbcsubreportincreasestotal=False } ,CBCSubreportSpec{ - cbcsubreporttitle="Equity" + cbcsubreporttitle=Msg.Equity ,cbcsubreportquery=Type [Equity] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_=Just NormallyNegative}) ,cbcsubreporttransform=fmap maNegate diff --git a/hledger/Hledger/Cli/Commands/Cashflow.hs b/hledger/Hledger/Cli/Commands/Cashflow.hs index 7f9630f1ec0..a60f3f6c6ec 100644 --- a/hledger/Hledger/Cli/Commands/Cashflow.hs +++ b/hledger/Hledger/Cli/Commands/Cashflow.hs @@ -20,14 +20,15 @@ import System.Console.CmdArgs.Explicit import Hledger import Hledger.Cli.CliOptions +import Hledger.Cli.Message qualified as Msg import Hledger.Cli.CompoundBalanceCommand cashflowSpec = CompoundBalanceCommandSpec { cbcdoc = $(embedFileRelative "Hledger/Cli/Commands/Cashflow.txt"), - cbctitle = "Cashflow Statement", + cbctitle = Msg.Noun Msg.CashflowStatement, cbcqueries = [ CBCSubreportSpec{ - cbcsubreporttitle="Cash flows" + cbcsubreporttitle=Msg.CashFlows ,cbcsubreportquery=Type [Cash] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_= Just NormallyPositive}) ,cbcsubreporttransform=id diff --git a/hledger/Hledger/Cli/Commands/Incomestatement.hs b/hledger/Hledger/Cli/Commands/Incomestatement.hs index 59cb4e032c6..97255a69278 100644 --- a/hledger/Hledger/Cli/Commands/Incomestatement.hs +++ b/hledger/Hledger/Cli/Commands/Incomestatement.hs @@ -16,21 +16,22 @@ import System.Console.CmdArgs.Explicit import Hledger import Hledger.Cli.CliOptions +import Hledger.Cli.Message qualified as Msg import Hledger.Cli.CompoundBalanceCommand incomestatementSpec = CompoundBalanceCommandSpec { cbcdoc = $(embedFileRelative "Hledger/Cli/Commands/Incomestatement.txt"), - cbctitle = "Income Statement", + cbctitle = Msg.Noun Msg.IncomeStatement, cbcqueries = [ CBCSubreportSpec{ - cbcsubreporttitle="Revenues" + cbcsubreporttitle=Msg.Revenues ,cbcsubreportquery=Type [Revenue] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_=Just NormallyNegative}) ,cbcsubreporttransform=fmap maNegate ,cbcsubreportincreasestotal=True } ,CBCSubreportSpec{ - cbcsubreporttitle="Expenses" + cbcsubreporttitle=Msg.Expenses ,cbcsubreportquery=Type [Expense] ,cbcsubreportoptions=(\ropts -> ropts{normalbalance_=Just NormallyPositive}) ,cbcsubreporttransform=id diff --git a/hledger/Hledger/Cli/Commands/Stats.hs b/hledger/Hledger/Cli/Commands/Stats.hs index 624030b3132..2f0f125b2bd 100644 --- a/hledger/Hledger/Cli/Commands/Stats.hs +++ b/hledger/Hledger/Cli/Commands/Stats.hs @@ -172,8 +172,9 @@ showLedgerStats verbose l today spn = showelapsed Nothing = "" showelapsed (Just dys) = printf " (%d %s)" dys' direction where dys' = abs dys - direction | dys >= 0 = "days ago" :: String - | otherwise = "days from now" + unit = if dys' == 1 then "day" else "days" :: String + direction | dys >= 0 = unit ++ " ago" + | otherwise = unit ++ " from now" tnum1 = length ts -- Integer would be better showstart (DateSpan (Just efd) _) = show $ fromEFDay efd showstart _ = "" diff --git a/hledger/Hledger/Cli/CompoundBalanceCommand.hs b/hledger/Hledger/Cli/CompoundBalanceCommand.hs index d454980126d..2e6b58f3ccd 100644 --- a/hledger/Hledger/Cli/CompoundBalanceCommand.hs +++ b/hledger/Hledger/Cli/CompoundBalanceCommand.hs @@ -28,6 +28,7 @@ import Data.List.NonEmpty qualified as NonEmpty import Data.Text qualified as T import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Builder qualified as TB +import Data.Gettext (Catalog, loadCatalog) import Data.Time.Calendar (Day, addDays) import System.Console.CmdArgs.Explicit as C (Mode, flagNone, flagReq) import System.IO qualified as IO @@ -45,6 +46,7 @@ import Hledger.Write.Html (formatRow, formatTitle, htmlAsLazyText, nl, Html, toH import Hledger.Write.Html.Attribute (stylesheet, tableStyle) import Hledger.Write.Ods (printFods) import Hledger.Write.Spreadsheet qualified as Spr +import Hledger.Cli.Message qualified as Msg -- | Description of a compound balance report command, -- from which we generate the command's cmdargs mode and IO action. @@ -61,8 +63,9 @@ import Hledger.Write.Spreadsheet qualified as Spr -- data CompoundBalanceCommandSpec = CompoundBalanceCommandSpec { cbcdoc :: CommandHelpStr, -- ^ the command's name(s) and documentation - cbctitle :: String, -- ^ overall report title - cbcqueries :: [CBCSubreportSpec DisplayName], -- ^ subreport details + cbctitle :: Msg.Message, -- ^ overall report title + cbcqueries :: [CBCSubreportSpec Msg.Message DisplayName], + -- ^ subreport details cbcaccum :: BalanceAccumulation -- ^ how to accumulate balances (per-period, cumulative, historical) -- (overrides command line flags) } @@ -136,8 +139,14 @@ compoundBalanceCommandMode CompoundBalanceCommandSpec{..} = -- | Generate a runnable command from a compound balance command specification. compoundBalanceCommand :: CompoundBalanceCommandSpec -> (CliOpts -> Journal -> IO ()) -compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=rspec, rawopts_=rawopts} j = do - writeOutputLazyText opts $ render $ styleAmounts styles cbr +compoundBalanceCommand spec opts j = + compoundBalanceCommandWithCatalog spec opts j + =<< traverse loadCatalog (catalog_file_ opts) + +compoundBalanceCommandWithCatalog :: + CompoundBalanceCommandSpec -> CliOpts -> Journal -> Maybe Catalog -> IO () +compoundBalanceCommandWithCatalog CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=rspec, rawopts_=rawopts} j maybeCat = do + writeOutputLazyText opts $ render $ styleAmounts styles cbr where styles = journalCommodityStylesWith HardRounding j ropts@ReportOpts{..} = _rsReportOpts rspec @@ -145,11 +154,11 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=r mbalanceAccumulationOverride = balanceAccumulationOverride rawopts balanceaccumulation = fromMaybe cbcaccum mbalanceAccumulationOverride -- Set balance type in the report options. - ropts' = ropts{balanceaccum_=balanceaccumulation} + ropts' = ropts{balanceaccum_=balanceaccumulation, catalog_ = maybeCat} + msg = Msg.getText maybeCat title = - maybe "" (<>" ") mintervalstr - <> T.pack cbctitle + mainTitle <> " " <> titledatestr <> maybe "" (" "<>) mtitleclarification @@ -168,7 +177,12 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=r enddates = map (addDays (-1)) . mapMaybe spanEnd $ cbrDates cbr -- these spans will always have a definite end date requestedspan = fst $ reportSpan j rspec - mintervalstr = showInterval interval_ + mainTitle = + maybe "" + (\iv -> Msg.getInflected maybeCat iv (Msg.getGenus maybeCat cbctitle)) + (intervalMsg interval_) + <> + msg cbctitle -- when user overrides, add an indication to the report title -- Do we need to deal with overridden BalanceCalculation? @@ -203,9 +217,14 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=r -- make a CompoundBalanceReport. The default heading is the auto-generated -- title above; --title=TEXT overrides it (and =empty suppresses). -- --subreport-titles=A|B|... overrides per-subreport titles. + cbr' :: CompoundPeriodicReport Msg.Message DisplayName MixedAmount cbr' = compoundBalanceReport rspec{_rsReportOpts=ropts'} j cbcqueries + cbr :: CompoundPeriodicReport T.Text DisplayName MixedAmount cbr = applySubreportTitles ropts' $ - cbr'{cbrTitle = effectiveTitle ropts' title} + cbr'{cbrTitle = effectiveTitle ropts' title, + cbrSubreports = map (mapFst3 msg) $ cbrSubreports cbr'} + -- ToDo: move to Utils + mapFst3 f (a,b,c) = (f a, b, c) -- render appropriately render = case outputFormatFromOpts opts of @@ -224,7 +243,10 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=r -- A `|`-separated argument overrides the corresponding subreport titles, in -- order; subreports beyond the supplied list keep their default title. An -- explicit empty argument suppresses all default subreport titles. -applySubreportTitles :: ReportOpts -> CompoundPeriodicReport a b -> CompoundPeriodicReport a b +applySubreportTitles :: + ReportOpts -> + CompoundPeriodicReport T.Text a b -> + CompoundPeriodicReport T.Text a b applySubreportTitles ropts cbr@CompoundPeriodicReport{cbrSubreports=subs} = case subreport_titles_ ropts of Nothing -> cbr @@ -236,22 +258,22 @@ applySubreportTitles ropts cbr@CompoundPeriodicReport{cbrSubreports=subs} = in cbr{cbrSubreports = zipWith replace [0..] subs} -- | Show a simplified description of an Interval. -showInterval :: Interval -> Maybe T.Text -showInterval = \case +intervalMsg :: Interval -> Maybe Msg.Interval +intervalMsg = \case NoInterval -> Nothing - Days 1 -> Just "Daily" - Weeks 1 -> Just "Weekly" - Weeks 2 -> Just "Biweekly" - Months 1 -> Just "Monthly" - Months 2 -> Just "Bimonthly" - Months 3 -> Just "Quarterly" - Months 6 -> Just "Half-yearly" - Months 12 -> Just "Yearly" - Quarters 1 -> Just "Quarterly" - Quarters 2 -> Just "Half-yearly" - Years 1 -> Just "Yearly" - Years 2 -> Just "Biannual" - _ -> Just "Periodic" + Days 1 -> Just Msg.Days1 + Weeks 1 -> Just Msg.Weeks1 + Weeks 2 -> Just Msg.Weeks2 + Months 1 -> Just Msg.Months1 + Months 2 -> Just Msg.Months2 + Months 3 -> Just Msg.Months3 + Months 6 -> Just Msg.Months6 + Months 12 -> Just Msg.Years1 + Quarters 1 -> Just Msg.Months3 + Quarters 2 -> Just Msg.Months6 + Years 1 -> Just Msg.Years1 + Years 2 -> Just Msg.Years2 + _ -> Just Msg.Periodic -- | Summarise one or more (inclusive) end dates, in a way that's -- visually different from showDateSpan, suggesting discrete end dates @@ -283,12 +305,14 @@ Balance Sheet Total || 1 1 1 -} -compoundBalanceReportAsText :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> TL.Text +compoundBalanceReportAsText :: + ReportOpts -> CompoundPeriodicReport T.Text DisplayName MixedAmount -> TL.Text compoundBalanceReportAsText ropts (CompoundPeriodicReport title _colspans subreports totalsrow) = TB.toLazyText $ titleBuilder <> multiBalanceReportTableAsText ropts bigtablewithtotalsrow where + msg = Msg.getText (catalog_ ropts) titleBuilder | T.null title = mempty | otherwise = TB.fromText title <> TB.fromText "\n\n" bigtable = @@ -315,7 +339,7 @@ compoundBalanceReportAsText ropts (CompoundPeriodicReport title _colspans subrep -- ] coltotalslines = multiBalanceRowAsText ropts allCommodities totalsrow totalstable = Table - (Group NoLine $ map Header $ "Net:" : replicate (length coltotalslines - 1) "") -- row headers + (Group NoLine $ map Header $ msg Msg.Net : replicate (length coltotalslines - 1) "") -- row headers (Header []) -- column headers, concatTables will discard these coltotalslines -- cell values @@ -343,12 +367,14 @@ compoundBalanceReportAsText ropts (CompoundPeriodicReport title _colspans subrep -- Subreports' CSV is concatenated, with the headings rows replaced by a -- subreport title row, and an overall title row, one headings row, and an -- optional overall totals row is added. -compoundBalanceReportAsCsv :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> CSV +compoundBalanceReportAsCsv :: + ReportOpts -> CompoundPeriodicReport T.Text DisplayName MixedAmount -> CSV compoundBalanceReportAsCsv ropts cbr = let spreadsheet = snd $ snd $ compoundBalanceReportAsSpreadsheet - machineFmt "Account" Nothing ropts cbr + machineFmt (msg Msg.Account) Nothing ropts cbr + msg = Msg.getText (catalog_ ropts) title = cbrTitle cbr titleRows | T.null title = [] | otherwise = @@ -358,7 +384,8 @@ compoundBalanceReportAsCsv ropts cbr = titleRows ++ NonEmpty.toList spreadsheet -- | Render a compound balance report as HTML. -compoundBalanceReportAsHtml :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> Html +compoundBalanceReportAsHtml :: + ReportOpts -> CompoundPeriodicReport T.Text DisplayName MixedAmount -> Html compoundBalanceReportAsHtml ropts cbr = let (title, (_fixed, cells)) = compoundBalanceReportAsSpreadsheet @@ -380,16 +407,17 @@ compoundBalanceReportAsHtml ropts cbr = -- | Render a compound balance report as Spreadsheet. compoundBalanceReportAsSpreadsheet :: AmountFormat -> T.Text -> Maybe T.Text -> - ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> + ReportOpts -> CompoundPeriodicReport T.Text DisplayName MixedAmount -> (T.Text, ((Int, Int), NonEmpty [Spr.Cell Spr.NumLines T.Text])) compoundBalanceReportAsSpreadsheet fmt accountLabel maybeBlank ropts cbr = let CompoundPeriodicReport title colspans subreports totalrow = cbr + msg = Msg.getText (catalog_ ropts) leadingHeaders = Spr.headerCell accountLabel : case layout_ ropts of LayoutTidy -> map Spr.headerCell tidyColumnLabels - LayoutBare -> [Spr.headerCell "Commodity"] + LayoutBare -> [Spr.headerCell $ msg Msg.Commodity] _ -> [] dataHeaders = (guard (layout_ ropts /= LayoutTidy) >>) $ @@ -397,8 +425,8 @@ compoundBalanceReportAsSpreadsheet fmt accountLabel maybeBlank ropts cbr = (reportPeriodName (period_titles_ ropts) (balanceaccum_ ropts) colspans) (if not (summary_only_ ropts) then colspans else []) ++ - (guard (multiBalanceHasTotalsColumn ropts) >> ["Total"]) ++ - (guard (average_ ropts) >> ["Average"]) + (guard (multiBalanceHasTotalsColumn ropts) >> [msg Msg.Total]) ++ + (guard (average_ ropts) >> [msg Msg.Average]) headerrow = leadingHeaders ++ concatMap (Spr.horizontalSpan subColumns . Spr.headerCell) dataHeaders @@ -446,7 +474,7 @@ compoundBalanceReportAsSpreadsheet fmt accountLabel maybeBlank ropts cbr = -- make a table of rendered lines of the report totals row & map (map (fmap wbToText)) & Spr.addRowSpanHeader - ((Spr.defaultCell "Net:") {Spr.cellClass = accountClass}) + ((Spr.defaultCell $ msg Msg.Net) {Spr.cellClass = accountClass}) -- insert a headings column, with Net: on the first line only & addTotalBorders -- marking the first row for special styling diff --git a/hledger/Hledger/Cli/Message.hs b/hledger/Hledger/Cli/Message.hs new file mode 100644 index 00000000000..5a1eb2e2a95 --- /dev/null +++ b/hledger/Hledger/Cli/Message.hs @@ -0,0 +1,96 @@ +{-| +List of all texts the CLI program can output in different languages +and related utilities. +-} + +module Hledger.Cli.Message ( + Message(..) + ,Noun(..) + ,Interval(..) + ,Genus + ,getText + ,getGenus + ,getInflected +) +where + +import Data.Default (Default(def)) +import Data.ByteString.Char8 qualified as B +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Lazy qualified as TL +import Data.Text.Encoding qualified as TE +-- import Text.Printf (printf) + +import Data.Gettext (Catalog, gettext) + + +-- | List of all hledger text messages +data Message = + None + | Account + | Revenues + | Expenses + | Assets + | Liabilities + | Equity + | CashFlows + | Net + | Total + | Average + | RightTotal + | RightAverage + | Commodity + | Balance + | Budget + | Noun Noun + | Interval Interval + deriving (Eq, Ord, Show) + +instance Default Message where + def = None + +data Noun = + BalanceSheet + | BalanceSheetWithEquity + | IncomeStatement + | CashflowStatement + deriving (Eq, Ord, Show, Enum, Bounded) + +data Interval = + Periodic + | Days1 + | Weeks1 + | Weeks2 + | Months1 + | Months2 + | Months3 + | Months6 + | Years1 + | Years2 + deriving (Eq, Ord, Show, Enum, Bounded) + + +-- ToDo: We must ensure that a Catalog is always available, we must embed a standard catalog +getText :: Maybe Catalog -> Message -> Text +getText Nothing = T.pack . makeKey +getText (Just cat) = TL.toStrict . gettext cat . B.pack . makeKey + +newtype Genus = Genus B.ByteString + +getGenus :: Maybe Catalog -> Message -> Genus +getGenus (Just cat) (Noun noun) = + Genus . TE.encodeUtf8 . TL.toStrict . + gettext cat . B.pack $ (show noun ++ "_genus") +getGenus _cat _msg = Genus $ B.empty + +getInflected :: Maybe Catalog -> Interval -> Genus -> Text +getInflected Nothing i _g = T.pack $ show i +getInflected (Just cat) i (Genus g) = + TL.toStrict . gettext cat $ B.pack (show i) <> g + +makeKey :: Message -> String +makeKey (Interval i) = show i +makeKey (Noun noun) = show noun +makeKey msg = show msg + diff --git a/hledger/hledger.cabal b/hledger/hledger.cabal index 8552091aec1..e06a03f9fd6 100644 --- a/hledger/hledger.cabal +++ b/hledger/hledger.cabal @@ -142,6 +142,7 @@ library Hledger.Cli.CompoundBalanceCommand Hledger.Cli.Conf Hledger.Cli.DocFiles + Hledger.Cli.Message Hledger.Cli.Script Hledger.Cli.Utils Hledger.Cli.Version @@ -170,6 +171,7 @@ library , githash >=0.1.6.2 , hashable >=1.2.4 , haskeline >=0.6 + , haskell-gettext , hledger-lib ==1.99.* , http-client , http-types @@ -226,6 +228,7 @@ executable hledger , filepath , githash >=0.1.6.2 , haskeline >=0.6 + , haskell-gettext , hledger , hledger-lib ==1.99.* , http-client @@ -278,6 +281,7 @@ test-suite unittest , filepath , githash >=0.1.6.2 , haskeline >=0.6 + , haskell-gettext , hledger , hledger-lib ==1.99.* , http-client @@ -331,6 +335,7 @@ benchmark bench , filepath , githash >=0.1.6.2 , haskeline >=0.6 + , haskell-gettext , hledger , hledger-lib ==1.99.* , html diff --git a/hledger/package.yaml b/hledger/package.yaml index 74e8a28867a..ce936d1be78 100644 --- a/hledger/package.yaml +++ b/hledger/package.yaml @@ -118,6 +118,7 @@ dependencies: # But for older (or older-GHC) stackage snapshots, we must still allow builds with older haskeline, # even though they will have the add display bug on Windows. I think this is the best we can do for now. - haskeline >=0.6 +- haskell-gettext - http-client - http-types - microlens >=0.4 diff --git a/hledger/po/de.po b/hledger/po/de.po new file mode 100644 index 00000000000..9586522017c --- /dev/null +++ b/hledger/po/de.po @@ -0,0 +1,110 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: de\n" + +msgid "None" +msgstr " " + +msgid "Account" +msgstr "Posten" + +msgid "Revenues" +msgstr "Einnahmen" + +msgid "Expenses" +msgstr "Ausgaben" + +msgid "Assets" +msgstr "Vermögen" + +msgid "Liabilities" +msgstr "Verbindlichkeiten" + +msgid "Equity" +msgstr "Eigenkapital" + +msgid "CashFlows" +msgstr "Kapitalflüsse" + +msgid "Net" +msgstr "Überschuss:" +#msgstr "Gewinn:" + +msgid "Total" +msgstr "Gesamt:" + +msgid "Average" +msgstr "Durchschnitt" + +msgid "RightTotal" +msgstr " Gesamt" + +msgid "RightAverage" +msgstr "Durchschnitt" + + +msgid "Commodity" +msgstr "Einheit" + +msgid "Balance" +msgstr "Bilanz" + +msgid "Budget" +msgstr "Budget" + + +msgid "IncomeStatement" +msgstr "Einnahmenüberschussrechnung" + +msgid "IncomeStatement_genus" +msgstr "_f" + +msgid "BalanceSheet" +msgstr "Bilanz" + +msgid "BalanceSheet_genus" +msgstr "_f" + +msgid "BalanceSheetWithEquity" +msgstr "Bilanz mit Eigenkapital" + +msgid "BalanceSheetWithEquity_genus" +msgstr "_f" + +msgid "CashflowStatement" +msgstr "Kapitalflussrechnung" + +msgid "CashflowStatement_genus" +msgstr "_f" + + +msgid "Periodic_f" +msgstr "Periodische " + +msgid "Days1_f" +msgstr "Tägliche " + +msgid "Weeks1_f" +msgstr "Wöchentliche " + +msgid "Weeks2_f" +msgstr "Zweiwöchentliche " + +msgid "Months1_f" +msgstr "Monatliche " + +msgid "Months2_f" +msgstr "Zweimonatliche " + +msgid "Months3_f" +msgstr "Vierteljährliche " + +msgid "Months6_f" +msgstr "Halbjährliche " + +msgid "Years1_f" +msgstr "Jährliche " + +msgid "Years2_f" +msgstr "Zweijährliche " diff --git a/hledger/po/en.po b/hledger/po/en.po new file mode 100644 index 00000000000..ba99235daa6 --- /dev/null +++ b/hledger/po/en.po @@ -0,0 +1,109 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: de\n" + +msgid "None" +msgstr " " + +msgid "Account" +msgstr "Account" + +msgid "Revenues" +msgstr "Revenues" + +msgid "Expenses" +msgstr "Expenses" + +msgid "Assets" +msgstr "Assets" + +msgid "Liabilities" +msgstr "Liabilities" + +msgid "Equity" +msgstr "Equity" + +msgid "CashFlows" +msgstr "Cash flows" + +msgid "Net" +msgstr "Net:" + +msgid "Total" +msgstr "Total:" + +msgid "Average" +msgstr "Average" + +msgid "RightTotal" +msgstr " Total" + +msgid "RightAverage" +msgstr "Average" + + +msgid "Commodity" +msgstr "Commodity" + +msgid "Balance" +msgstr "Balance" + +msgid "Budget" +msgstr "Budget" + + +msgid "IncomeStatement" +msgstr "Income Statement" + +msgid "IncomeStatement_genus" +msgstr "_" + +msgid "BalanceSheet" +msgstr "Balance Sheet" + +msgid "BalanceSheet_genus" +msgstr "_" + +msgid "BalanceSheetWithEquity" +msgstr "Balance Sheet With Equity" + +msgid "BalanceSheetWithEquity_genus" +msgstr "_" + +msgid "CashflowStatement" +msgstr "Cashflow Statement" + +msgid "CashflowStatement_genus" +msgstr "_" + + +msgid "Periodic_" +msgstr "Periodic " + +msgid "Days1_" +msgstr "Daily " + +msgid "Weeks1_" +msgstr "Weekly " + +msgid "Weeks2_" +msgstr "Biweekly " + +msgid "Months1_" +msgstr "Monthly " + +msgid "Months2_" +msgstr "Bimonthly " + +msgid "Months3_" +msgstr "Quarterly " + +msgid "Months6_" +msgstr "Half-yearly " + +msgid "Years1_" +msgstr "Yearly " + +msgid "Years2_" +msgstr "Biennial " diff --git a/stack.yaml b/stack.yaml index bdfdd7efae9..5d52125805e 100644 --- a/stack.yaml +++ b/stack.yaml @@ -10,4 +10,5 @@ packages: compiler: ghc-9.14 resolver: nightly-2026-09-01 -extra-deps: [] +extra-deps: +- haskell-gettext-0.1.2.0