Skip to content

translate report texts to user language using haskell-gettext - #2735

Draft
thielema wants to merge 3 commits into
hledgerorg:mainfrom
thielema:gettext
Draft

thielema wants to merge 3 commits into
hledgerorg:mainfrom
thielema:gettext

Conversation

@thielema

@thielema thielema commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

This is a very rough draft.
I translate some report texts using haskell-gettext.
haskell-gettext is a pure Haskell implementation of gettext that does not rely on a global variable for the text dictionary.

I define an enumeration of all translatable texts in Hledger.Cli.Message.
I do not use the original English strings as keys,
because sometimes the same English string needs different treatment.
E.g. I have Average and RightAverage, see below.

You can run it like so:

> msgfmt hledger/po/en.po -o hledger/po/en.mo
> msgfmt hledger/po/de.po -o hledger/po/de.mo
> hledger ... --catalog=hledger/po/en.mo

Currently the tests will fail because of missing English Catalog.

Problems I found:

  • There are small differences in the original strings, like "Commodity" vs. "commodity" and "Total" vs. "Total:". It simplifies translations if we can get rid of such variations.
  • Modules like Hledger.Cli.Commands.Incomestatement define a CompoundBalanceCommandSpec but at this stage we have no Catalog and cannot translate. Thus I turned Text type into Message. However, CompoundBalanceCommandSpec is defined in hledger-lib, where the Message type is not yet available. Thus I added a type parameter msg. I think the other two already existing type parameters serve the same purpose.
  • The user does not choose a language but instead passes a gettext file using the new option --catalog. Currently I load Catalog in compoundBalanceCommandWithCatalog and balance. Maybe there is a more central place.
  • This draft omits translation of intervals and report attributes. Their translation is tricky. In English it is "Monthly Income" and "Monthy Income Statement" but in German it is "Monatliches Einkommen", but "Monatliche Einkommenüberschussrechnung" (note the missing "s"), because "Einkommen" has neutral gender and "Rechnung" has female gender.

ToDo:

  • convert *.po files to *.mo files using msgfmt in Shake.hs
  • Currently I have defined ReportOpts.catalog_ :: Maybe Catalog. I prefer to embed the English catalog in the Hledger executable and use this as default, such that we can turn Maybe Catalog into Catalog.

@thielema
thielema marked this pull request as draft September 17, 2026 17:11
@acinader

Copy link
Copy Markdown
Contributor

got it. I'm trying to finish up a translation pr too. I'm taking a different, more invasive approach. I'll try to get it polished off today or tomorrow so I can post it and we can compare.

https://groups.google.com/g/hledger/c/PmcY3J8WiDc

@acinader

Copy link
Copy Markdown
Contributor

i don't think we need to bother with .mo files. our translation foot print is very small. I can do some performance testing, but i suspect that it's going to be one to two milis even if i 10x what we think we'll see.

@acinader

Copy link
Copy Markdown
Contributor

How can I help? Would you like me to test? Would you like a formal review? Do you want to discuss?

If you want to help me refine #2736 you could build it locally and review the DE translation. Follow TRANSLATING.md, edit a PO file in Poedit, and see the result locally.

I think it is ready to go, but I'd like to get a set of eyes on it before we waste Simon's time, partcularly because I am taking a different approach than what he had in mind, so I could use some puch back :).

@simonmichael

simonmichael commented Sep 18, 2026 via email

Copy link
Copy Markdown
Member

@acinader

Copy link
Copy Markdown
Contributor

Slightly related: we are in the last two weeks of the 6-month AI credit provided by Anthropic. After this, I plan to tap the brakes on AI assistance, at least for a bit, to reflect and digest.

Simon, I will happily cover your Anthropic subscription when the promotion is over. I will petition them to extend it for you, but if they won't, I'll increase my sponsorship to cover it. Well worth the money.

heldger2 is an ideal tool for agents to use for accounting. My codex project uses hledger1.99 to obtain, categorize, and book all of my transactions. I use hledger-web for review and reporting during accounting sessions. It's amazing. I haven't fully fleshed out lot-level tracking, asset allocation, and performance reporting, or what the gap is for hledger-web yet, so there's a lot to do.

Along those lines, my follow-on to translations would be to add skills to help a first-time translator use an agent to prepare a translation.

#2006 would be another super high value add that I'd like to get to.

Working without a coding agent would be like trying to code without online documentation(!), google, or stackoverflow.

You could, but why?

@thielema

Copy link
Copy Markdown
Contributor Author

I added an attempt to perform inflection of interval names without the need to translate every combination individually. The idea is to query the genus of report names and then to query inflected interval names, accordingly. This will work for some languages, however, in order to account for different word order, I think we also need to change, say, "Income Statemnt" to "{interval} Income Statement" and use text interpolation for constructing localized texts.

@acinader

This comment was marked as low quality.

balancesheetSpec = CompoundBalanceCommandSpec {
cbcdoc = $(embedFileRelative "Hledger/Cli/Commands/Balancesheet.txt"),
cbctitle = Msg.BalanceSheet,
cbctitle = Msg.Noun Msg.BalanceSheet,

@acinader acinader Sep 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two relevant links from the gettext manual

Context

Context and ambiguities

po

msgctxt "balance-sheet interval"
msgid "Monthly"
msgstr "Monatliche"

hs

trc t "balance-sheet interval" "Monthly"

@acinader acinader Sep 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be interested in implementing a context-based approach in #2736 using cbctitle and cbcintervalcontext or something like that?

For example:

cbctitle           = i18n "Balance Sheet"
cbcintervalcontext = "balance-sheet interval"

@thielema

thielema commented Sep 19, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

simonmichael commented Sep 19, 2026 via email

Copy link
Copy Markdown
Member

@simonmichael

simonmichael commented Sep 19, 2026 via email

Copy link
Copy Markdown
Member

@thielema

thielema commented Sep 19, 2026 via email

Copy link
Copy Markdown
Contributor Author

@thielema

thielema commented Sep 19, 2026 via email

Copy link
Copy Markdown
Contributor Author

@acinader

acinader commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

https://www.gnu.org/software/gettext/manual/html_node/Entire-sentences.html

Here's my concrete suggestion for a plan we should adopt:

For our first translation, let’s defer additional grammar-handling logic and add the Haskell and PO entries needed to make the German translation complete and correct.

The guiding principle should be to prefer more translation entries in the PO file over more grammar-handling logic in Haskell.

I think if we focus our efforts, we could get a correct, shippable German translation very quickly.

We have i18n scaffolding in #2736, including a tag extraction script, documentation, and test coverage. It works right now and is ready for a translator to iterate on the PO file while reviewing the UI.

The missing piece is the first l10n, which you already have a good start on, but it will require more Haskell work to accommodate a gendered language. Can you do this work?

I'd appreciate it if you could review my PR and share your thoughts from a translator's perspective. I think you'll like it. You could start with the PR README and translation doc. Ask me any questions. In the meantime, I will pull your web-balance-report branch and run it so I can give you feedback.

acinader added a commit to acinader/hledger that referenced this pull request Sep 20, 2026
"Monthly Balance Sheet" was an interval word plus a report name, joined
by a space in the code. One adjective form had to fit every report, and
the space could not be dropped. Now each compound report lists its own
title per interval, so a translator renders the whole phrase as the
language needs, with no grammar logic in Haskell. The German catalog
grows from 14 to 44 title entries. English output is unchanged.

this should bring hledgerorg@8ddb03f changes into hledgerorg#2736

should bring hledgerorg#2736 upto parity with hledgerorg#2735

AI usage: drafted with Claude Code, reviewed and edited by the author.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
acinader added a commit to acinader/hledger that referenced this pull request Sep 20, 2026
"Monthly Balance Sheet" was an interval word plus a report name, joined
by a space in the code. One adjective form had to fit every report, and
the space could not be dropped. Now each compound report lists its own
title per interval, so a translator renders the whole phrase as the
language needs, with no grammar logic in Haskell. The German catalog
grows from 14 to 44 title entries. English output is unchanged.

this should bring hledgerorg@8ddb03f changes into hledgerorg#2736

should bring hledgerorg#2736 upto parity with hledgerorg#2735

AI usage: drafted with Claude Code, reviewed and edited by the author.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@acinader

Copy link
Copy Markdown
Contributor

Where I'm coming from: I would rather ship a smaller slower-growing number of translations with perfect grammar & spelling, than a large fast-growing number of sloppy translations.

Agree.

Adding a translation to the distribution adds a real comittment that has the potential to slow down any development that touches the ui. In order to add a locale, we'd have to have some level of confidence that there is a contributor that is repared to own it in the face of changes that require additional translations.

@acinader

Copy link
Copy Markdown
Contributor

After thinking about it some more, it is probably best to release only i18n, the extraction script, and documentation. We should not contemplate releasing a localized PO file in the near term.

A limited release focused on the internationalization (i18n) machinery would let any user copy the template .POT to make their own PO to localize their own hledger. This would be an invitation for potential contributors to open issues where there are translation problems that they can't fix with the current template which would require a code change and a new extraction of the template.

If, after some time, a mature locale has worked through the necessary Haskell changes to support localization (l10n), then it could be part of the distribution.

How does that sound to you?

acinader added a commit to acinader/hledger that referenced this pull request Sep 20, 2026
"Monthly Balance Sheet" was an interval word plus a report name, joined
by a space in the code. One adjective form had to fit every report, and
the space could not be dropped. Now each compound report lists its own
title per interval, so a translator renders the whole phrase as the
language needs, with no grammar logic in Haskell. The German catalog
grows from 14 to 44 title entries. English output is unchanged.

this should bring hledgerorg@8ddb03f changes into hledgerorg#2736

should bring hledgerorg#2736 upto parity with hledgerorg#2735

AI usage: drafted with Claude Code, reviewed and edited by the author.
acinader added a commit to acinader/hledger that referenced this pull request Sep 22, 2026
"Monthly Balance Sheet" was an interval word plus a report name, joined
by a space in the code. One adjective form had to fit every report, and
the space could not be dropped. Now each compound report lists its own
title per interval, so a translator renders the whole phrase as the
language needs, with no grammar logic in Haskell. The German catalog
grows from 14 to 44 title entries. English output is unchanged.

this should bring hledgerorg@8ddb03f changes into hledgerorg#2736

should bring hledgerorg#2736 upto parity with hledgerorg#2735

AI usage: drafted with Claude Code, reviewed and edited by the author.
@thielema
thielema force-pushed the gettext branch 2 times, most recently from 4e35893 to 31c9947 Compare September 22, 2026 21:07
acinader and others added 3 commits September 22, 2026 23:28
…y ago"

"Biannual" means twice a year; the title of a report with a two-year
interval now says "Biennial". And stats no longer says "1 days ago".

The interval word is about to become a translation key, and every later
change to an English string invalidates its translations, so it is fixed
first; the stats wording is fixed while looking at it.

AI usage: drafted with Claude Code, reviewed and edited by the author.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2VXnprjHmXZR8tgWPV3vz
Cli.Message.getGenus, getInflected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants