From b11e7388c87eed0d7b88429404bcda81e017ccbe Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 12:29:34 +0000 Subject: [PATCH 1/4] wxGUI: Extract menu strings with an ITS definition The main menu labels of the wxGUI were missing from the translation template, because the generated menustrings.py they came from could be left empty when core/menutree.py failed, and 'make pot' only checked that the file exists. Extract the labels, help texts and descriptions of the wxGUI menu definitions directly from the committed XML files with xgettext and an ITS rules file instead. The template then points to the XML files and line numbers in the repository, and no build step has to succeed before 'make pot'. Add translator comments in a key=value form, with the kind of string as "type" (menu-title, menu-item-label, menu-item-help) and its paired help text, label or tool name, built from the XML, so translators can tell, for example, a verb from a noun. Remove the menustrings.py generation from the Autotools and CMake builds accordingly. Translations of module descriptions used as menu items come from the grassmods domain, which is a fallback of the gettext function. Mark the console help text as translatable. Parts of this change were prepared with AI assistance. --- .flake8 | 2 - .gitignore | 1 - gui/wxpython/CMakeLists.txt | 20 ---------- gui/wxpython/Makefile | 10 +---- gui/wxpython/README.md | 8 ++-- .../docs/wxgui_sphinx/src/wxgui_libraries.rst | 1 - .../docs/wxgui_sphinx/src/wxgui_toolboxes.rst | 5 --- gui/wxpython/gui_core/goutput.py | 4 +- locale/Makefile | 9 +++-- locale/its/grass-wxgui.its | 38 +++++++++++++++++++ locale/its/grass-wxgui.loc | 12 ++++++ pyproject.toml | 1 - 12 files changed, 63 insertions(+), 48 deletions(-) create mode 100644 locale/its/grass-wxgui.its create mode 100644 locale/its/grass-wxgui.loc diff --git a/.flake8 b/.flake8 index 058ce00c4c8..fadfa7f9c9c 100644 --- a/.flake8 +++ b/.flake8 @@ -33,8 +33,6 @@ per-file-ignores = gui/wxpython/mapdisp/test_mapdisp.py: E501 gui/wxpython/mapswipe/g.gui.mapswipe.py: E501 gui/wxpython/timeline/g.gui.timeline.py: E501 - # Generated file - gui/wxpython/menustrings.py: E501 # C wrappers call libgis.G_gisinit before importing other modules. # TODO: Is this really needed? python/grass/jupyter/__init__.py: E501 diff --git a/.gitignore b/.gitignore index ac47be4c591..a879be58b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,6 @@ macos/cache/* *.dox.org # Ignore specific files generated during make -gui/wxpython/menustrings.py gui/wxpython/xml/menudata.xml gui/wxpython/xml/module_tree_menudata.xml include/Make/Doxyfile_arch_html diff --git a/gui/wxpython/CMakeLists.txt b/gui/wxpython/CMakeLists.txt index 2261deb385b..24f5a5075c1 100644 --- a/gui/wxpython/CMakeLists.txt +++ b/gui/wxpython/CMakeLists.txt @@ -163,26 +163,6 @@ if(NOT WITH_FHS) ) set_target_properties(build_module_tree_menudata PROPERTIES FOLDER GUI) - - add_custom_target( - build_menustrings - ALL - COMMAND - ${grass_env_command} ${PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/core/menutree.py "manager" >> - ${CMAKE_CURRENT_SOURCE_DIR}/menustrings.py - COMMAND - ${grass_env_command} ${PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/core/menutree.py "module_tree" >> - ${CMAKE_CURRENT_SOURCE_DIR}/menustrings.py - COMMAND - ${grass_env_command} ${PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/core/menutree.py "psmap" >> - ${CMAKE_CURRENT_SOURCE_DIR}/menustrings.py - DEPENDS build_module_tree_menudata gui_images - ) - - set_target_properties(build_menustrings PROPERTIES FOLDER GUI) endif() # NOT WITH_FHS install(DIRECTORY "${OUTDIR}/${WXPYTHON_DIR}/xml" DESTINATION "${WXPYTHON_DIR}") diff --git a/gui/wxpython/Makefile b/gui/wxpython/Makefile index 343599fe01a..3d11870e372 100644 --- a/gui/wxpython/Makefile +++ b/gui/wxpython/Makefile @@ -1,7 +1,7 @@ MODULE_TOPDIR = ../.. SUBDIRS = docs animation datacatalog mapswipe gmodeler rlisetup psmap dbmgr vdigit rdigit iclass gcp timeline tplot photo2image image2target -EXTRA_CLEAN_FILES = menustrings.py build_ext.pyc xml/menudata.xml xml/module_tree_menudata.xml */*.pyc +EXTRA_CLEAN_FILES = build_ext.pyc xml/menudata.xml xml/module_tree_menudata.xml */*.pyc include $(MODULE_TOPDIR)/include/Make/Dir.make include $(MODULE_TOPDIR)/include/Make/Python.make @@ -33,7 +33,6 @@ ifndef CROSS_COMPILING -$(MAKE) $(DSTDIR)/xml/module_items.xml -$(MAKE) xml/menudata.xml -$(MAKE) xml/module_tree_menudata.xml - -$(MAKE) menustrings.py endif $(MAKE) parsubdirs @@ -49,13 +48,6 @@ xml/module_tree_menudata.xml: core/toolboxes.py $(call run_grass,$(PYTHON) $< "module_tree" > $@) $(call run_grass,$(PYTHON) $< "validate" $@) -menustrings.py: core/menutree.py $(DSTDIR)/xml/menudata.xml $(DSTDIR)/xml/module_tree_menudata.xml $(DSTDIR)/xml/menudata_modeler.xml $(DSTDIR)/xml/menudata_psmap.xml - @echo "# This is a generated file.\n# pylint: skip-file\n" > $@ - $(call run_grass,$(PYTHON) $< "manager" >> $@) - $(call run_grass,$(PYTHON) $< "module_tree" >> $@) - $(call run_grass,$(PYTHON) $< "modeler" >> $@) - $(call run_grass,$(PYTHON) $< "psmap" >> $@) - $(DSTDIR)/xml/module_items.xml: tools/build_modules_xml.py @echo "Generating interface description for all modules..." $(call run_grass,$(PYTHON) $< > $@) diff --git a/gui/wxpython/README.md b/gui/wxpython/README.md index 2fc4d78d5ee..edafa106344 100644 --- a/gui/wxpython/README.md +++ b/gui/wxpython/README.md @@ -61,9 +61,9 @@ Update of module description strings in `./xml/menudata.xml`: - in a GRASS session, run `./tools/update_menudata.py` -From this (updated) `./xml/menudata.xml`, the gettext strings are generated -via Makefile and stored into the file `menustrings.py`. - While generating the po files in `../../locale/po/` by `../../locale/Makefile`, all `.py` files are parsed and the strings are stored in -`../../locale/po/grasswxpy_XX.po`. +`../../locale/po/grasswxpy_XX.po`. The labels, help texts and descriptions in +the menu definitions (`./xml/toolboxes.xml`, `./xml/wxgui_items.xml` and +`./xml/menudata_*.xml`) are extracted with the ITS rules in +`../../locale/its/`. diff --git a/gui/wxpython/docs/wxgui_sphinx/src/wxgui_libraries.rst b/gui/wxpython/docs/wxgui_sphinx/src/wxgui_libraries.rst index ec91edafd6f..e7bac32efe9 100644 --- a/gui/wxpython/docs/wxgui_sphinx/src/wxgui_libraries.rst +++ b/gui/wxpython/docs/wxgui_sphinx/src/wxgui_libraries.rst @@ -14,7 +14,6 @@ Libraries location_wizard mapdisp mapwin - menustrings modules nviz vnet diff --git a/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst b/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst index f7b54d9a54d..08afcfe0aa5 100644 --- a/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst +++ b/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst @@ -69,8 +69,6 @@ GRASS directory (``.grass8``) in user home directory wxgui_items [label="wxgui_items.xml in distribution", shape="note", URL="#wxgui-items-file"]; module_items [label="module_items.xml in distribution", shape="note", URL="#module-items-file"]; - menustrings [label="menustrings.py\n(used for translations)", shape="note"]; - module_items -> menudata; wxgui_items -> menudata; main_menu -> menudata; @@ -83,10 +81,7 @@ GRASS directory (``.grass8``) in user home directory umain_menu -> umenudata; utoolboxes -> umenudata; - menudata -> menustrings; - // guimenu [label="Main menu in wxGUI", shape="box3d"]; - // menustrings -> guimenu; // menudata -> guimenu; // umenudata -> guimenu; } diff --git a/gui/wxpython/gui_core/goutput.py b/gui/wxpython/gui_core/goutput.py index f491b576414..e2e8893be9c 100644 --- a/gui/wxpython/gui_core/goutput.py +++ b/gui/wxpython/gui_core/goutput.py @@ -166,7 +166,9 @@ def _layout(self): helpText = StaticText( self.panelPrompt, id=wx.ID_ANY, - label="Press Tab to display command help, Ctrl+Space to autocomplete", + label=_( + "Press Tab to display command help, Ctrl+Space to autocomplete" + ), ) helpText.SetForegroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT) diff --git a/locale/Makefile b/locale/Makefile index d60b091b680..3e466f4360f 100644 --- a/locale/Makefile +++ b/locale/Makefile @@ -34,6 +34,10 @@ DOMAINS = $(LIBDOMAIN) $(MODDOMAIN) $(WXPYDOMAIN) LIB_POTFILES = find ../lib \( -name "*.c" -o -name "*.py" \) | LC_ALL=C sort | xargs grep -l "_(\"\|n_(\"" MOD_POTFILES = find ../ \( -name "*.c" -o -name "*.cpp" \) | grep -v '../lib' | LC_ALL=C sort | xargs grep -l "_(\"\|n_(\"" WXPY_POTFILES = find ../gui/wxpython -name '*.py' | LC_ALL=C sort | xargs grep -l "_(\"\|n_(\"" +#wxGUI menu definitions, extracted with the ITS rules in ./its +WXPY_XMLFILES = ../gui/wxpython/xml/toolboxes.xml ../gui/wxpython/xml/wxgui_items.xml \ + ../gui/wxpython/xml/menudata_modeler.xml ../gui/wxpython/xml/menudata_psmap.xml \ + ../gui/wxpython/xml/menudata_pyedit.xml #For Python script module messages MOD_PYFILES = find ../scripts -name '*.py' | LC_ALL=C sort | xargs grep -l "_(\"\|n_(\"" @@ -48,10 +52,6 @@ endef #The xgettext utility is used to automate the creation of #portable message files (.po) pot: - if [ ! -f ../gui/wxpython/menustrings.py ] ; then \ - echo "Build GRASS before running 'make pot'" >&2 ; \ - exit 1 ; \ - fi @rm -f ./templates/*.pot @echo "Generating $(LIBDOMAIN)..." xgettext $(XGETTEXT_ARGS) --from-code=utf-8 --keyword=_ --keyword=n_:1,2 -cGTC -o ./templates/$(LIBDOMAIN).pot `$(LIB_POTFILES)` @@ -60,6 +60,7 @@ pot: xgettext $(XGETTEXT_ARGS) --from-code=utf-8 -j --keyword=_ --keyword=n_:1,2 -cGTC -o ./templates/$(MODDOMAIN).pot `$(MOD_PYFILES)` @echo "Generating $(WXPYDOMAIN)..." xgettext $(XGETTEXT_ARGS) --from-code=utf-8 --keyword=_ --keyword=n_:1,2 -cGTC -o ./templates/$(WXPYDOMAIN).pot `$(WXPY_POTFILES)` + GETTEXTDATADIRS="$(CURDIR)" xgettext $(XGETTEXT_ARGS) --from-code=utf-8 -j -o ./templates/$(WXPYDOMAIN).pot $(WXPY_XMLFILES) #merge already existing translations with new messages in POT template file, deduplicate it and create new po files: update-po: diff --git a/locale/its/grass-wxgui.its b/locale/its/grass-wxgui.its new file mode 100644 index 00000000000..fc9488cfe6c --- /dev/null +++ b/locale/its/grass-wxgui.its @@ -0,0 +1,38 @@ + + + + + + + + type=menu-title (name of a menu or submenu) + + + + + + + + diff --git a/locale/its/grass-wxgui.loc b/locale/its/grass-wxgui.loc new file mode 100644 index 00000000000..5fca1eee4c9 --- /dev/null +++ b/locale/its/grass-wxgui.loc @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/pyproject.toml b/pyproject.toml index c9b8a7bb24a..55752680c30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -581,7 +581,6 @@ ignore = ["CVS", ".git", ".ruff_cache", ".pytest_cache"] ignore-paths = [ "bin[.].*", "dist[.].*", - "gui/wxpython/menustrings.py", "python/grass/temporal/ply", # External code "python/libgrass_interface_generator", ] From 4749118db561114f1b68b249ad308aa97cf81e3b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:09:14 +0000 Subject: [PATCH 2/4] wxGUI: Remove the strings action of menutree.py The action printed the menu strings as _() calls for the generated menustrings.py, which is no longer generated since the strings are extracted from the XML files with an ITS definition. Make the tree action the default one. Parts of this change were prepared with AI assistance. --- gui/wxpython/core/menutree.py | 41 ++++------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/gui/wxpython/core/menutree.py b/gui/wxpython/core/menutree.py index 1975c99badb..5508870d6e8 100644 --- a/gui/wxpython/core/menutree.py +++ b/gui/wxpython/core/menutree.py @@ -12,8 +12,7 @@ @endcode where action: - - strings (default, used for translations) - - tree (simple tree structure) + - tree (default, simple tree structure) - commands (command names and their place in tree) - dump (tree structure with stored data) @@ -38,8 +37,6 @@ import copy import xml.etree.ElementTree as ET -from typing import TYPE_CHECKING - import wx from core.treemodel import TreeModel, ModuleNode @@ -52,9 +49,6 @@ if not os.getenv("GISBASE"): sys.exit("GRASS is not running. Exiting...") -if TYPE_CHECKING: - from typing_extensions import Writer - # TODO: change the system to remove strange derived classes @@ -158,17 +152,6 @@ def PrintTree(self, fh): for child in self.model.root.children: printTree(node=child, fh=fh) - def PrintStrings(self, fh: Writer[str]): - """Print menu strings to file (used for localization) - - :param fh: file descriptor - """ - className = self.__class__.__name__ - fh.write("menustrings_%s = [\n" % className) - for child in self.model.root.children: - printStrings(child, fh) - fh.write(" '']\n") - def PrintCommands(self, fh): printCommands(self.model.root, fh, itemSep=" | ", menuSep=" > ") @@ -192,20 +175,6 @@ def printTree(node, fh, indent=0): printTree(node=child, fh=fh, indent=indent + 2) -def printStrings(node, fh: Writer[str]): - # node.label - with module in brackets - # node.data['label'] - without module in brackets - if node.label and not node.data: - fh.write(" _(%r),\n" % str(node.label)) - if node.data: - if node.data.get("label"): - fh.write(" _(%r),\n" % str(node.data["label"])) - if node.data.get("description"): - fh.write(" _(%r),\n" % str(node.data["description"])) - for child in node.children: - printStrings(node=child, fh=fh) - - def printCommands(node, fh, itemSep, menuSep): def collectParents(node, parents): parent = node.parent @@ -227,11 +196,11 @@ def collectParents(node, parents): if __name__ == "__main__": - action = "strings" + action = "tree" menu = "manager" for arg in sys.argv: - if arg in {"strings", "tree", "commands", "dump"}: + if arg in {"tree", "commands", "dump"}: action = arg elif arg in {"manager", "module_tree", "modeler", "psmap"}: menu = arg @@ -264,9 +233,7 @@ def collectParents(node, parents): gscore.fatal("Unknown value for parameter menu: %s" % menu) - if action == "strings": - menudata.PrintStrings(sys.stdout) - elif action == "tree": + if action == "tree": menudata.PrintTree(sys.stdout) elif action == "commands": menudata.PrintCommands(sys.stdout) From 3415af182d986d0be76d5c190a5ac7bbf7692595 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:25:50 +0000 Subject: [PATCH 3/4] wxGUI: Keep generating the menu XML in the build, document the menus Removing the menustrings.py rules also removed what made the builds generate and install the menudata.xml and module_tree_menudata.xml files: the CMake build_menustrings target was the only one run by default, and the Autotools rule had the installed copies as prerequisites. Make the CMake build_module_tree_menudata target part of the default build, and copy the generated files into the distribution from the Autotools default target. Add a description of how the wxGUI menus are defined, built and translated. Parts of this change were prepared with AI assistance. --- doc/development/README.md | 4 + doc/development/wxgui_menus.md | 145 +++++++++++++++++++++++++++++++++ gui/wxpython/CMakeLists.txt | 1 + gui/wxpython/Makefile | 2 + locale/its/grass-wxgui.its | 10 +-- 5 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 doc/development/wxgui_menus.md diff --git a/doc/development/README.md b/doc/development/README.md index 08da6f1fc99..9a5888d38b9 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -22,3 +22,7 @@ maintaining GRASS. ## C API - [GRASS Programmer's manual](https://grass.osgeo.org/programming8/) + +## wxGUI + +- [wxGUI menus and their translation](wxgui_menus.md) diff --git a/doc/development/wxgui_menus.md b/doc/development/wxgui_menus.md new file mode 100644 index 00000000000..68e7e8690f8 --- /dev/null +++ b/doc/development/wxgui_menus.md @@ -0,0 +1,145 @@ +# wxGUI menus and their translation + +This note describes how the menus of the wxGUI are defined, built and +translated. It covers the main menu, the tool tree (Tools tab), and the menus +of the Graphical Modeler, the Cartographic Composer and the Python editor. +Paths of wxGUI files are relative to `gui/wxpython/`, other paths to the +repository root. + +## Source files + +| File | Content | +| --- | --- | +| `xml/main_menu.xml` | Root of the main menu: the top-level toolboxes. | +| `xml/module_tree.xml` | Root of the tool tree, with the `` and `` placeholders. | +| `xml/toolboxes.xml` | Toolboxes (menus) with their labels and items: `module-item` (a tool), `wxgui-item`, `subtoolbox`, `separator`. | +| `xml/wxgui_items.xml` | GUI items: label, handler or command, description, keywords. | +| `xml/menudata_modeler.xml`, `xml/menudata_psmap.xml`, `xml/menudata_pyedit.xml` | Complete menus in the final `menudata` format, read as they are. | +| `xml/*.dtd` | Structure of each file type. | + +## Build and runtime + +At build time, with the C locale (English): + +1. `tools/build_modules_xml.py` writes `module_items.xml` with the + description and keywords of every tool, taken from its interface + description. For a tool with a label, the description is the label and + the description joined with a space. +2. `core/toolboxes.py` combines `main_menu.xml` (or `module_tree.xml` with + the `module_tree` argument), `toolboxes.xml`, `wxgui_items.xml` and + `module_items.xml` into `menudata.xml` (or `module_tree_menudata.xml`). + It expands subtoolboxes, fills each item with the data of the item of the + same name (elements already in the item take precedence), adds handlers, + and converts the tags to the `menudata` format (`toolbox` to `menu`, + `description` to `help`, `module` to `command`, and so on). + +All three files are generated and not committed. Autotools writes +`module_items.xml` directly to the `xml/` directory of the distribution and +the other two to `xml/` in the source tree (ignored by Git), from where the +distribution gets copies. CMake writes all of them to the output tree. + +At runtime, `core/menutree.py` (`MenuTreeModelBuilder`) reads a `menudata` +file into a tree model, passing each menu label, item label and item help +through `_()`. Classes in `lmgr/menudata.py`, `gmodeler/menudata.py`, +`psmap/menudata.py` and `gui_core/pyedit.py` choose the file. When the user +config directory has `toolboxes/toolboxes.xml`, `toolboxes/main_menu.xml` or +`toolboxes/module_tree.xml`, `core/toolboxes.py` regenerates the menu data in +that directory when it is missing or older than these files. Each time the +tool tree is loaded, its `` placeholder is replaced by an Addons +menu listing the installed addons. + +```mermaid +flowchart LR + TOOLS["tools
--interface-description"] --> BMX["tools/build_modules_xml.py"] + BMX --> MI["module_items.xml"] + MM["main_menu.xml
module_tree.xml"] --> TBX["core/toolboxes.py"] + TB["toolboxes.xml
wxgui_items.xml"] --> TBX + MI --> TBX + USER["user toolboxes/*.xml"] -. runtime .-> TBX + TBX --> MD["menudata.xml
module_tree_menudata.xml"] + MD --> MT["core/menutree.py"] + MDX["menudata_modeler.xml
menudata_psmap.xml
menudata_pyedit.xml"] --> MT + MT --> GUI["menus and tool tree"] +``` + +To print a menu as a tree or as a list of tools, run in a GRASS session from +the installed `gui/wxpython` directory: + +```bash +PYTHONPATH=. python core/menutree.py tree manager # or module_tree, modeler, psmap +PYTHONPATH=. python core/menutree.py commands manager +``` + +## Translation + +`make pot` in `locale/` writes the templates in `locale/templates/`. For the +`grasswxpy` domain, it extracts the `_()` strings from the Python files of the +wxGUI, then joins (`-j`) the strings of the committed menu XML files listed in +`WXPY_XMLFILES`. xgettext finds the rules for these files through +`GETTEXTDATADIRS=locale/`: `locale/its/grass-wxgui.loc` maps the root +elements `toolboxes`, `wxgui-items` and `menudata` to +`locale/its/grass-wxgui.its`, which marks `label`, `help` and `description` +as translatable and adds translator comments built from the XML itself. + +This part reads no generated file, so it needs no build. The help of a tool +item is the tool description, which is translated from `grassmods` for a tool +without a label, because `_()` in +`python/grass/__init__.py` looks a message up in `grasslibs`, then +`grassmods`, then `grasswxpy`. + +```mermaid +flowchart LR + PY["gui/wxpython/**/*.py"] --> XG1["xgettext"] + XML["xml/toolboxes.xml
xml/wxgui_items.xml
xml/menudata_*.xml"] --> XG2["xgettext +
locale/its/grass-wxgui.*"] + XG1 --> WXPOT["grasswxpy.pot"] + XG2 -- "-j" --> WXPOT + C["C tools,
locale/scriptstrings"] --> XG3["xgettext"] --> MODPOT["grassmods.pot"] + LIB["lib/"] --> XG4["xgettext"] --> LIBPOT["grasslibs.pot"] + WXPOT & MODPOT & LIBPOT -- "make update-po" --> PO["locale/po/*.po
(Weblate)"] + PO -- msgfmt --> MO["GISBASE/locale/LANG/
LC_MESSAGES/*.mo"] + MO --> RT["_(): grasslibs, grassmods,
grasswxpy"] +``` + +## How to add or change a menu entry + +- Tool: add `` + to a toolbox in `xml/toolboxes.xml`. The help text comes from the tool. +- GUI item: define a `wxgui-item` in `xml/wxgui_items.xml` with a handler + method of the main window frame (`lmgr/frame.py` and `main_window/frame.py`) + or a command, and reference it by name from `xml/toolboxes.xml`. +- Menu: add a `toolbox` to `xml/toolboxes.xml` and a `subtoolbox` reference + to its parent toolbox, to `xml/main_menu.xml` or to `xml/module_tree.xml`. +- Modeler, Cartographic Composer or Python editor: edit its + `xml/menudata_*.xml`. +- New XML file with translatable strings: add it to `WXPY_XMLFILES` in + `locale/Makefile` and, for a new root element, a rule to + `locale/its/grass-wxgui.loc`. + +Rebuild to regenerate the menu data. Mark an accelerator key by `&` (written +`&` in XML) before its letter. + +## How translators see it + +Each menu string appears once in `grasswxpy.pot` with references to the +committed XML files and lines and a comment in a `key=value` form, for +example: + +```text +#. type=menu-item-help (description of a menu entry); label="Georectify" +#: ../gui/wxpython/xml/wxgui_items.xml:8 +msgid "Manage Ground Control Points for Georectification" +``` + +The `&` of an accelerator key stays in the message as it is. + +## Known limitations + +- The help of a tool with a label (label and description joined) is not in + any template, so it is shown untranslated. +- Keywords are neither extracted nor passed through `_()`. +- Labels from user toolboxes and descriptions of addons are not in any + template. +- Messages have no context: identical strings share one translation, and the + first domain containing a message wins. +- The descriptions of Python tools reach `grassmods.pot` through + `locale/scriptstrings/*_to_translate.c`, which only a build generates. diff --git a/gui/wxpython/CMakeLists.txt b/gui/wxpython/CMakeLists.txt index 24f5a5075c1..cec3f3f8ed7 100644 --- a/gui/wxpython/CMakeLists.txt +++ b/gui/wxpython/CMakeLists.txt @@ -147,6 +147,7 @@ if(NOT WITH_FHS) add_custom_target( build_module_tree_menudata + ALL COMMAND ${grass_env_command} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/core/toolboxes.py "module_tree" > diff --git a/gui/wxpython/Makefile b/gui/wxpython/Makefile index 3d11870e372..393a0141d26 100644 --- a/gui/wxpython/Makefile +++ b/gui/wxpython/Makefile @@ -33,6 +33,8 @@ ifndef CROSS_COMPILING -$(MAKE) $(DSTDIR)/xml/module_items.xml -$(MAKE) xml/menudata.xml -$(MAKE) xml/module_tree_menudata.xml + -$(MAKE) $(DSTDIR)/xml/menudata.xml + -$(MAKE) $(DSTDIR)/xml/module_tree_menudata.xml endif $(MAKE) parsubdirs diff --git a/locale/its/grass-wxgui.its b/locale/its/grass-wxgui.its index fc9488cfe6c..8b30b1350ca 100644 --- a/locale/its/grass-wxgui.its +++ b/locale/its/grass-wxgui.its @@ -12,11 +12,11 @@ From 4ce7a97f3358a6a2e2ce9ada1c00cc46a7a53387 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:28:38 +0000 Subject: [PATCH 4/4] wxGUI: Update the menu documentation and point it to the development note Fix the generator of the menu data and the paths of the XML files in the README files and the toolboxes page, add the files that were missing there, drop the recommendation of the broken update_menudata.py script and link the description of the menu build and translation. Parts of this change were prepared with AI assistance. --- gui/wxpython/README.md | 9 +++--- .../docs/wxgui_sphinx/src/wxgui_toolboxes.rst | 30 ++++++++++++------- gui/wxpython/xml/menudata.README | 6 +++- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/gui/wxpython/README.md b/gui/wxpython/README.md index edafa106344..af4b74df28b 100644 --- a/gui/wxpython/README.md +++ b/gui/wxpython/README.md @@ -55,11 +55,10 @@ clear the display and start from scratch. Notes: - Help part of menu entries is coming from the module descriptions -- The menu is generated by `./tools/build_modules_xml.py` - -Update of module description strings in `./xml/menudata.xml`: - -- in a GRASS session, run `./tools/update_menudata.py` +- The menu data (`./xml/menudata.xml` and `./xml/module_tree_menudata.xml`) + is generated by `./core/toolboxes.py` +- How the menus are built and translated is described in + [wxGUI menus and their translation](../../doc/development/wxgui_menus.md) While generating the po files in `../../locale/po/` by `../../locale/Makefile`, all `.py` files are parsed and the strings are stored in diff --git a/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst b/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst index 08afcfe0aa5..a00f7801598 100644 --- a/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst +++ b/gui/wxpython/docs/wxgui_sphinx/src/wxgui_toolboxes.rst @@ -32,25 +32,31 @@ Files related to toolboxes GRASS source code contains these XML files: * ``gui/wxpython/xml/main_menu.xml`` +* ``gui/wxpython/xml/module_tree.xml`` * ``gui/wxpython/xml/toolboxes.xml`` * ``gui/wxpython/xml/wxgui_items.xml`` -GRASS distribution contains these XML files: +GRASS distribution contains these XML files in ``gui/wxpython/xml`` +(relative to the GRASS installation directory): -* ``etc/gui/wxpython/xml/main_menu.xml`` -* ``etc/gui/wxpython/xml/toolboxes.xml`` -* ``etc/gui/wxpython/xml/wxgui_items.xml`` -* ``etc/gui/wxpython/xml/module_items.xml`` (generated during compilation) -* ``etc/gui/wxpython/xml/menudata.xml`` (generated during compilation) +* ``main_menu.xml`` +* ``module_tree.xml`` +* ``toolboxes.xml`` +* ``wxgui_items.xml`` +* ``module_items.xml`` (generated during compilation) +* ``menudata.xml`` (generated during compilation) +* ``module_tree_menudata.xml`` (generated during compilation) GRASS directory (``.grass8``) in user home directory (i.e., ``$HOME`` on Unix) contains these XML files: * ``toolboxes/main_menu.xml`` (created by user) +* ``toolboxes/module_tree.xml`` (created by user) * ``toolboxes/toolboxes.xml`` (created by user) * ``toolboxes/menudata.xml`` (generated on wxGUI startup) +* ``toolboxes/module_tree_menudata.xml`` (generated on wxGUI startup) .. graphviz:: @@ -92,11 +98,15 @@ Other files GRASS source code contains these XML files: -* ``gui/wxpython/xml/menudata_gmodeler.xml`` +* ``gui/wxpython/xml/menudata_modeler.xml`` * ``gui/wxpython/xml/menudata_psmap.xml`` +* ``gui/wxpython/xml/menudata_pyedit.xml`` -In GRASS distribution these XML files are in the ``etc/gui/wxpython/xml`` -directory. +These files are already in the ``menudata`` format. In GRASS distribution +they are in the same ``gui/wxpython/xml`` directory. + +How the strings of all these files are extracted for translation is described +in ``doc/development/wxgui_menus.md`` in the source code. Generation of files and menu @@ -366,7 +376,7 @@ You should validate your XML before running wxGUI, e.g. using ``xmllint`` xmllint --noout --dtdvalid toolboxes.dtd toolboxes.xml You can find ``toolboxes.dtd`` and ``main_menu.dtd`` in your GRASS directory, -in ``etc/gui/wxpython/xml`` subdirectory. Depending on the validator, +in ``gui/wxpython/xml`` subdirectory. Depending on the validator, you might need to add DTD file to the drectory with the XML file. If you will provide an invalid, not well formed or empty file loading of diff --git a/gui/wxpython/xml/menudata.README b/gui/wxpython/xml/menudata.README index 9ddcad09ab4..24ae66c43a0 100644 --- a/gui/wxpython/xml/menudata.README +++ b/gui/wxpython/xml/menudata.README @@ -1,6 +1,10 @@ MENU - the entries in menudata.xml and module_tree_menudata.xml are autogenerated - via tools/build_modules_xml.py (see ../Makefile) + by core/toolboxes.py from main_menu.xml (or module_tree.xml), toolboxes.xml, + wxgui_items.xml and module_items.xml, which is generated by + tools/build_modules_xml.py (see ../Makefile and ../CMakeLists.txt) +- How the menus are built and translated is described in + ../../../doc/development/wxgui_menus.md - Find more info at https://grass.osgeo.org/grass-devel/manuals/wxGUI.toolboxes.html (../docs/wxgui_sphinx/src/)