How does localization work in CheckMK 2.3 (and later) for custom extensions?

Hello,
I would like to provide my own translations for my Wato rules.
With the new API we have to use the Title() class (as example).

title= Title(“Test1”)

I thought I just had to place a *.mo file under
./local/share/check_mk/locale/de/LC_MESSAGES/
and maybe do a omd apache restart ?

The *mo file is compiled from a po-file with a ‘msgid “Test1”’ - entry.
But that doesn’t work - the wato shows only “Test1” :thinking:

Can anyone here help me out?

Thanks,
Joerg

Hey Joerg,
the weblate localization applies at the moment only for source code living in the check_mk repository.
So if you use strings existing in the cmk code base it automatically gets translated. The other strings will stick to the soruce string you provided.

BR
Benni

PS will look up how to fiddle custom translation in.

Translations for MKPs and check plugins

1. Mark your strings as translatable in your plugin code using the normal helpers:

from cmk.gui.i18n import _, _l   # _l = lazy, for module-level Title:/Help:

The string-extraction tooling (locale/output_pot_file) scans for _l, Title:, Help:, Label:, Message:. So also for what your looking for.

2. Ship .po/.mo files named multisite.po / multisite.mo (the domain name is fixed). Compile with msgfmt.

3a. Via an MKP — the MKP format has a dedicated LOCALES part (titled “Localizations”). Put your catalogs under the package’s locale/ source dir. On install they land at:

~/local/share/check_mk/locale/packages/<pkg_name>/<lang>/LC_MESSAGES/multisite.mo

3b. Custom plugin (no MKP) — just drop the files into the local hierarchy directly:

~/local/share/check_mk/locale/packages/<your_name>/de/LC_MESSAGES/multisite.mo
~/local/share/check_mk/locale/packages/<your_name>/de/alias        # optional, language display name

The packages/<name>/ subdirectory is the key: _get_package_language_dirs() iterates every child there and adds it to the translation chain automatically. (You can also override core strings by writing straight into ~/local/share/check_mk/locale/<lang>/LC_MESSAGES/multisite.mo, which takes precedence over built-in — but that’s for patching, not for shipping plugin-specific text.)

This seems to be available in the source code repo of checkmk.

Looked again into it and saw that the mentioned script scans the whole repository.

So I created a wrapper script which provides you with the possibility to just collect the data of your plugin files.

This guide walks through every step.

Copy this bash script code and save it to extratc_pot

#!/bin/bash
# Generate gettext catalogs from a set of Python files.
#
# Usage:
#   extract_pot OUTDIR LANG FILE_OR_DIR [FILE_OR_DIR ...]
#
# Produces, under OUTDIR:
#   multisite.pot                          (template, freshly extracted)
#   <LANG>/LC_MESSAGES/multisite.po        (created if missing, else merged)
#   <LANG>/LC_MESSAGES/multisite.mo        (compiled from the .po)
#
# Dirs are searched recursively for *.py (tests excluded).
# Edit the .po between runs to add translations; re-run to merge + recompile.

set -e
set -o pipefail

if [ "$#" -lt 3 ]; then
    echo "Usage: $0 OUTDIR LANG FILE_OR_DIR [FILE_OR_DIR ...]" >&2
    exit 1
fi

OUTDIR="$1"
LANG_CODE="$2"
shift 2

POT_FILE="${OUTDIR}/multisite.pot"
PO_DIR="${OUTDIR}/${LANG_CODE}/LC_MESSAGES"
PO_FILE="${PO_DIR}/multisite.po"
MO_FILE="${PO_DIR}/multisite.mo"

# Expand args: keep files as-is, recurse into dirs for *.py (skip tests).
FILES=()
for arg in "$@"; do
    if [ -d "$arg" ]; then
        while IFS= read -r f; do
            FILES+=("$f")
        done < <(find -L "$arg" -type f -name "*.py" \
            -not -path "*/tests/*" -not -path "*/test/*")
    else
        FILES+=("$arg")
    fi
done

if [ "${#FILES[@]}" -eq 0 ]; then
    echo "No Python files found." >&2
    exit 1
fi

mkdir -p "${PO_DIR}"

# 1. Extract translatable strings into the .pot template.
xgettext -w 80 --sort-output --force-po -L python --from-code=utf-8 \
    --add-comments=weblate-flags \
    --keyword=_l \
    --keyword=Title:1,1t \
    --keyword=Help:1,1t \
    --keyword=Label:1,1t \
    --keyword=Message:1,1t \
    -o "${POT_FILE}" \
    "${FILES[@]}"
echo "Wrote ${POT_FILE}" >&2

# 2. Create the .po (first run) or merge new strings into the existing one.
if [ -f "${PO_FILE}" ]; then
    msgmerge -U "${PO_FILE}" "${POT_FILE}"
    echo "Merged ${PO_FILE}" >&2
else
    msginit -i "${POT_FILE}" --no-translator -l "${LANG_CODE}" -o "${PO_FILE}"
    echo "Created ${PO_FILE} (edit it to add translations)" >&2
fi

# 3. Compile the .po into the .mo gettext actually loads.
msgfmt "${PO_FILE}" -o "${MO_FILE}"
echo "Compiled ${MO_FILE}" >&2

Make the script executable:

chmod +x extract_pot

Before you start: run all commands as your site user (e.g. su - mysite), not as root. The translation tools (xgettext, msginit, msgmerge, msgfmt) are part of the gettext package — on most systems they’re already installed. If a command complains it’s missing, install gettext with your system package manager.

Throughout this guide I’ll use these example values — replace them with your own:

  • Plugin name: my_plugin (any name you like; just be consistent)
  • Language: de (German). Use fr for French, es for Spanish, etc.
  • Plugin source folder: /path/to/my_plugin (the folder containing the plugin’s .py files)

Step 1 — Generate the translation files

Run the script with three arguments, in this order:

./extract_pot <output-folder> <language> <plugin-source-folder>
Argument What to put Example
1. output-folder Where the translation files should be created ~/local/share/check_mk/locale/packages/my_plugin
2. language The two-letter language code de
3. plugin-source-folder The folder with your plugin’s .py files /path/to/my_plugin

So the full command looks like this:

./extract_pot ~/local/share/check_mk/locale/packages/my_plugin de /path/to/my_plugin

Outcome: the script scans your plugin’s code for translatable texts and creates this folder structure:

~/local/share/check_mk/locale/packages/my_plugin/
├── multisite.pot                          ← list of all texts found (template)
└── de/
    └── LC_MESSAGES/
        ├── multisite.po                   ← the file YOU edit (Step 2)
        └── multisite.mo                   ← the file Checkmk reads (built automatically)

At this point the translations are still empty — the script found the English texts but doesn’t know your language yet. That’s the next step.


Step 2 — Add your translations

Open the .po file in a text editor:

nano ~/local/share/check_mk/locale/packages/my_plugin/de/LC_MESSAGES/multisite.po

Inside you’ll see pairs of lines like this:

msgid "Maximum number of connections"
msgstr ""
  • msgid is the original English text — leave it unchanged.
  • msgstr is your translation — type it between the quotes.

For example, for German:

msgid "Maximum number of connections"
msgstr "Maximale Anzahl an Verbindungen"

Do this for every text you want translated. Any msgstr you leave empty will simply stay in English. Save and close the editor when you’re done (in nano: Ctrl+O, Enter, then Ctrl+X).


Step 3 — Apply the translations

Run the exact same command as in Step 1 again:

./extract_pot ~/local/share/check_mk/locale/packages/my_plugin de /path/to/my_plugin

Outcome: the script rebuilds the multisite.mo file from your edited .po. This .mo file is the one Checkmk actually reads.

You can repeat Steps 2 and 3 as often as you like. The script keeps the translations you already wrote and only adds any new texts — so it’s safe to re-run whenever the plugin is updated.


Step 4 — Reload the GUI

For Checkmk to notice the new translation, reload the web server:

omd reload apache

Then, in the Checkmk GUI, make sure your user’s language is set to the one you translated (top-right user menu → Edit profileLanguage). Your plugin’s texts should now appear in your language.


Good to know

  • What gets translated: only the texts shown in the configuration GUI — rule titles, help texts, and labels. The actual monitoring output of a check (the status summary text on a service) is not affected by this.
  • One language at a time: to add a second language, just run Step 1 again with a different language code (e.g. fr), then translate that language’s .po file.
  • Empty translations stay English: there’s no harm in translating only some of the texts.

Hope this helps anyone who wants their custom plugins in their own language. Happy to answer questions below.

Hi Benni,

thank you very much for this solution and for all the effort you put into the detailed description. It works like a charm.

Best regards,
Joerg

Hello!

If one of the answers helped you solve your question, please mark it as the solution. This way, you thank the person who helped you and also indicate that the question has been resolved. This, in turn, helps others who come across the same question.

Thank you!

Uh. Someone probably should document this in the Official Checkmk User Guide. :thinking:

Thanks for the entry points, @otAAAh !