Skip to content

make.py: i18n string extraction (init) only scans top-level *.py — misses nested-package addons #51

Description

@eduralph

Summary

make.py's i18n string extraction (init / po/template.pot generation) only
scans an addon's top-level *.py files. For a nested-package addon — one
that ships a Python package tree (sub-packages under the addon dir) and puts its
translatable strings in those nested modules — the generated template.pot
silently misses every _() string outside the top level.

Background

NameSuite (upstream PR 941) is the first addon submitted as a nested Python
package: a name_processor/ library tree (controllers/, models/,
services/, repositories/, views/, …) plus a parallel tests/ tree, wired
together with absolute imports. The runtime model is valid — Gramps' plugin
loader inserts the addon dir onto sys.path before importing
(gramps/gen/plug/_manager.py:311), so from name_processor… import … resolves.

But its translatable strings live almost entirely in the nested modules. The two
top-level entry files declare none; the .gpr.py has a handful; the rest are in
name_processor/views/*:

module _() strings
views/tool_audit_tab.py 21
views/tool_rename_tab.py 20
views/gramplet.py 11
views/tool.py 3
views/base_tab.py 1

≈56 UI strings, none of which the current extractor sees.

Problem

In the init command, string extraction globs only the addon's top-level .py:

# make.py (init command)
fnames = glob.glob("%s/*.py" % addon)          # ~line 381 (gpr discovery)
...
fnames = " ".join(glob.glob(f"{addon}/*.py"))  # ~line 404 (xgettext input)
system(
    f"xgettext --language=Python --keyword=_ --keyword=_:1,2c --keyword=N_"
    f" --from-code=UTF-8 --add-comments=Translators"
    f' -o "{addon}/po/template.pot" {fnames} '
)

{addon}/*.py matches only files directly under the addon dir, not
{addon}/<pkg>/**/*.py. So for a nested-package addon the resulting
template.pot contains only the top-level / .gpr.py strings and silently
omits the nested-module strings
. Because the structure check only verifies that
po/template.pot exists, a near-empty template still passes — the gap is
invisible. Translators never receive the missing strings.

Proposed fix

Extract over the addon's full package tree (recursively), excluding tests, e.g.:

import os
py_files = [
    os.path.join(root, f)
    for root, _dirs, files in os.walk(addon)
    for f in files
    if f.endswith(".py") and "/tests/" not in os.path.join(root, f).replace(os.sep, "/") + "/"
]

or the shell equivalent already proven to capture the NameSuite strings:

xgettext --language=Python --keyword=_ --keyword=_:1,2c --keyword=N_ \
  --from-code=UTF-8 --add-comments=Translators \
  -o "<Addon>/po/template.pot" \
  $(find <Addon> -name '*.py' -not -path '*/tests/*')

Glade/XML handling stays as-is. Flat addons are unaffected (their *.py is the
whole tree).

Acceptance criteria

  • make.py <ver> init <NestedAddon> produces a template.pot containing the
    _() strings from nested package modules (verify against a nested addon: the
    ≈56 NameSuite view strings appear).
  • Flat addons produce byte-identical templates to today (no spurious churn).
  • Test files are excluded from extraction.

Related / minor

The default build globs ({addon}/*.py) plus a typical MANIFEST like
name_processor/*/* don't capture the top-level package marker
name_processor/__init__.py (only nested files, which do_tar ships correctly
via recursive tar.add). It imports fine anyway as a PEP 420 namespace package,
so this is cosmetic — but a recursive build/manifest helper could ship the
explicit marker too. Out of scope for the extraction fix; noting it as the same
"make.py assumes a flat addon" theme.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions