2025, Sep 22 13:00

Translate Flask/Jinja tooltip title attributes reliably: disable PyBabel wrapping with --no-wrap

Having trouble translating tooltip title attributes in Flask/Jinja? Learn why PyBabel wrapping breaks gettext extraction and how --no-wrap fixes .pot catalogs.

Translating UI tooltips in Flask/Jinja templates can look deceptively simple until the strings bound to title attributes stubbornly refuse to appear in your catalogs. You wrap them in a gettext call, run your extraction pipeline, and still nothing shows up—or worse, the resulting .pot is malformed and untranslatable. The fix is not in the template; it’s in how pybabel is invoked.

Problem

Strings embedded in HTML attributes, such as Bootstrap tooltip titles, are not being picked up for translation. Even when the text is wrapped in a gettext call inside the Jinja template, the extraction fails to produce a usable entry in the .pot file.

Example

Here’s a minimal illustration with a tooltip whose title needs localization. The markup uses a translation function in a multi-line attribute, which is common for longer tooltip copy.

<div class="col col-lg-3">
    <label for="machineIrrNew">
        {{ _l('Ideal Run Rate (unit/min)') }}
        <button type="button" id="irrHint" class="fa-solid fa-circle-info"
            data-bs-toggle="tooltip" data-bs-placement="top" data-bs-delay="0" 
            title="{{ _l('Used to
            specify an optional default ideal run rate for continuous products. It has no effect on
            batch products. Setting a default does not prevent you from specifiying custom run rates
            for particular products') }}">
        </button>
    </label>
</div>

Why it happens

The extraction doesn’t fail because of the template. The root cause is pybabel’s default behavior: it wraps long strings during both extract and init steps. Even if you enforce no wrapping in your templates, pybabel will still wrap, and the result is a .pot file that’s broken and can’t be translated properly. When the catalog is malformed, attribute-bound strings like the tooltip title won’t be recognized correctly.

Solution

Disable wrapping at the tool level. Add the --no-wrap flag to both pybabel extract and pybabel init in your automation pipeline. Once wrapping is off end-to-end, the strings in title attributes are recognized and extracted as expected.

import subprocess
subprocess.run(
    [
        "pybabel",
        "extract",
        "--no-wrap",
        "-F",
        "babel.cfg",
        "-k",
        "_l",
        "-o",
        "messages.pot",
        ".",
    ],
    check=True,
    text=True,
)
import subprocess
locale_tag = lang  # assume this is provided upstream
subprocess.run(
    [
        "pybabel",
        "init",
        "--no-wrap",
        "-i",
        "messages.pot",
        "-d",
        "src/pro_machina/translations",
        "-l",
        locale_tag,
    ],
    check=True,
    text=True,
)

With wrapping disabled in both commands, pybabel picks up and recognizes the text placed in the title attribute.

Why this matters

Tooltip content and other attribute-based strings tend to be longer and often span multiple lines for readability. If your extraction step silently wraps and corrupts them, you end up with catalogs that can’t be reliably translated. Fixing the pipeline avoids brittle template workarounds and keeps the i18n process predictable.

Takeaways

If attribute-bound strings aren’t making it into your catalogs, don’t fight the template. Ensure pybabel runs with --no-wrap for both extract and init. Keep your gettext calls in place in the HTML, regenerate the catalogs, and the tooltips will be ready for translation without resorting to client-side hacks.

The article is based on a question from StackOverflow by roganjosh and an answer by roganjosh.